From f59a5c06ba3bb578bf6a3e444554db4da4d9ebe2 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Wed, 6 Jan 2021 16:12:32 -0500 Subject: [PATCH] Base Flutter App --- web_native/.gitignore | 5 + web_native/.metadata | 4 +- web_native/README.md | 2 +- web_native/android/app/build.gradle | 8 +- .../android/app/src/debug/AndroidManifest.xml | 2 +- .../android/app/src/main/AndroidManifest.xml | 12 +- .../imagini}/MainActivity.kt | 0 .../res/drawable-v21/launch_background.xml | 12 + .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 8 +- .../app/src/profile/AndroidManifest.xml | 2 +- web_native/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- web_native/integration_test/app_test.dart | 36 + web_native/integration_test/driver.dart | 8 + web_native/ios/Flutter/AppFrameworkInfo.plist | 2 +- web_native/ios/Flutter/Debug.xcconfig | 1 + web_native/ios/Flutter/Release.xcconfig | 1 + web_native/ios/Podfile | 41 ++ .../ios/Runner.xcodeproj/project.pbxproj | 30 +- web_native/ios/Runner/Info.plist | 2 +- web_native/lib/bloc/bloc-prov-tree.dart | 68 ++ web_native/lib/bloc/bloc-prov.dart | 60 ++ web_native/lib/bloc/bloc.dart | 1 + web_native/lib/blocs/auth-bloc.dart | 5 + web_native/lib/blocs/blocs.dart | 2 + web_native/lib/blocs/pref-bloc.dart | 5 + .../lib/components/roundedalertdialog.dart | 636 ++++++++++++++++++ web_native/lib/main.dart | 125 +--- web_native/lib/main.dart.bak | 113 ++++ web_native/lib/models/contact.dart | 8 + web_native/lib/routes.dart | 8 + .../lib/screens/example1/components/body.dart | 15 + .../lib/screens/example1/example-bloc.dart | 20 + .../lib/screens/example1/examplescreen1.dart | 40 ++ .../lib/screens/example2/components/body.dart | 15 + .../lib/screens/example2/example2-bloc.dart | 20 + .../lib/screens/example2/examplescreen2.dart | 40 ++ web_native/lib/services/exampleapi.dart | 11 + web_native/lib/theme/style.dart | 13 + web_native/pubspec.lock | 272 +++++++- web_native/pubspec.yaml | 6 +- web_native/test/widget_test.dart | 2 +- web_native/web/favicon.png | Bin 0 -> 917 bytes web_native/web/icons/Icon-192.png | Bin 0 -> 5292 bytes web_native/web/icons/Icon-512.png | Bin 0 -> 8252 bytes web_native/web/index.html | 45 ++ web_native/web/manifest.json | 23 + 48 files changed, 1566 insertions(+), 185 deletions(-) rename web_native/android/app/src/main/kotlin/com/{example/web_native => reichard/imagini}/MainActivity.kt (100%) create mode 100644 web_native/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 web_native/android/app/src/main/res/values-night/styles.xml create mode 100644 web_native/integration_test/app_test.dart create mode 100644 web_native/integration_test/driver.dart create mode 100644 web_native/ios/Podfile create mode 100644 web_native/lib/bloc/bloc-prov-tree.dart create mode 100644 web_native/lib/bloc/bloc-prov.dart create mode 100644 web_native/lib/bloc/bloc.dart create mode 100644 web_native/lib/blocs/auth-bloc.dart create mode 100644 web_native/lib/blocs/blocs.dart create mode 100644 web_native/lib/blocs/pref-bloc.dart create mode 100644 web_native/lib/components/roundedalertdialog.dart create mode 100644 web_native/lib/main.dart.bak create mode 100644 web_native/lib/models/contact.dart create mode 100644 web_native/lib/routes.dart create mode 100644 web_native/lib/screens/example1/components/body.dart create mode 100644 web_native/lib/screens/example1/example-bloc.dart create mode 100644 web_native/lib/screens/example1/examplescreen1.dart create mode 100644 web_native/lib/screens/example2/components/body.dart create mode 100644 web_native/lib/screens/example2/example2-bloc.dart create mode 100644 web_native/lib/screens/example2/examplescreen2.dart create mode 100644 web_native/lib/services/exampleapi.dart create mode 100644 web_native/lib/theme/style.dart create mode 100644 web_native/web/favicon.png create mode 100644 web_native/web/icons/Icon-192.png create mode 100644 web_native/web/icons/Icon-512.png create mode 100644 web_native/web/index.html create mode 100644 web_native/web/manifest.json diff --git a/web_native/.gitignore b/web_native/.gitignore index 9d532b1..0fa6b67 100644 --- a/web_native/.gitignore +++ b/web_native/.gitignore @@ -39,3 +39,8 @@ app.*.symbols # Obfuscation related app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/web_native/.metadata b/web_native/.metadata index 182ccca..687816d 100644 --- a/web_native/.metadata +++ b/web_native/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: 78910062997c3a836feee883712c241a5fd22983 - channel: stable + revision: b0a22998593fc605c723dee8ff4d9315c32cfe2c + channel: beta project_type: app diff --git a/web_native/README.md b/web_native/README.md index 1b636b3..0b980d1 100644 --- a/web_native/README.md +++ b/web_native/README.md @@ -1,4 +1,4 @@ -# Imagini +# imagini A new Flutter project. diff --git a/web_native/android/app/build.gradle b/web_native/android/app/build.gradle index c693a14..805ee17 100644 --- a/web_native/android/app/build.gradle +++ b/web_native/android/app/build.gradle @@ -26,21 +26,17 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' } - lintOptions { - disable 'InvalidPackage' - } - defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.reichard.imagini" minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/web_native/android/app/src/debug/AndroidManifest.xml b/web_native/android/app/src/debug/AndroidManifest.xml index 2e7361c..7a1f425 100644 --- a/web_native/android/app/src/debug/AndroidManifest.xml +++ b/web_native/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.reichard.imagini"> diff --git a/web_native/android/app/src/main/AndroidManifest.xml b/web_native/android/app/src/main/AndroidManifest.xml index 7f2ee25..c3fb35b 100644 --- a/web_native/android/app/src/main/AndroidManifest.xml +++ b/web_native/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,7 @@ - - + + + + + + + + diff --git a/web_native/android/app/src/main/res/values-night/styles.xml b/web_native/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/web_native/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/web_native/android/app/src/main/res/values/styles.xml b/web_native/android/app/src/main/res/values/styles.xml index 1f83a33..d74aa35 100644 --- a/web_native/android/app/src/main/res/values/styles.xml +++ b/web_native/android/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - - diff --git a/web_native/android/app/src/profile/AndroidManifest.xml b/web_native/android/app/src/profile/AndroidManifest.xml index 5b98341..7a1f425 100644 --- a/web_native/android/app/src/profile/AndroidManifest.xml +++ b/web_native/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.reichard.imagini"> diff --git a/web_native/android/build.gradle b/web_native/android/build.gradle index 3100ad2..c505a86 100644 --- a/web_native/android/build.gradle +++ b/web_native/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/web_native/android/gradle/wrapper/gradle-wrapper.properties b/web_native/android/gradle/wrapper/gradle-wrapper.properties index 296b146..bc6a58a 100644 --- a/web_native/android/gradle/wrapper/gradle-wrapper.properties +++ b/web_native/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/web_native/integration_test/app_test.dart b/web_native/integration_test/app_test.dart new file mode 100644 index 0000000..92c5a63 --- /dev/null +++ b/web_native/integration_test/app_test.dart @@ -0,0 +1,36 @@ +// This is a basic Flutter integration test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:imagini/main.dart' as app; + +void main() => run(_testMain); + +void _testMain() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web_native/integration_test/driver.dart b/web_native/integration_test/driver.dart new file mode 100644 index 0000000..a03bca0 --- /dev/null +++ b/web_native/integration_test/driver.dart @@ -0,0 +1,8 @@ +// This file is provided as a convenience for running integration tests via the +// flutter drive command. +// +// flutter drive --driver integration_test/driver.dart --target integration_test/app_test.dart + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/web_native/ios/Flutter/AppFrameworkInfo.plist b/web_native/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f7..9367d48 100644 --- a/web_native/ios/Flutter/AppFrameworkInfo.plist +++ b/web_native/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier diff --git a/web_native/ios/Flutter/Debug.xcconfig b/web_native/ios/Flutter/Debug.xcconfig index 592ceee..e8efba1 100644 --- a/web_native/ios/Flutter/Debug.xcconfig +++ b/web_native/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/web_native/ios/Flutter/Release.xcconfig b/web_native/ios/Flutter/Release.xcconfig index 592ceee..399e934 100644 --- a/web_native/ios/Flutter/Release.xcconfig +++ b/web_native/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/web_native/ios/Podfile b/web_native/ios/Podfile new file mode 100644 index 0000000..1e8c3c9 --- /dev/null +++ b/web_native/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/web_native/ios/Runner.xcodeproj/project.pbxproj b/web_native/ios/Runner.xcodeproj/project.pbxproj index 37010c3..730b881 100644 --- a/web_native/ios/Runner.xcodeproj/project.pbxproj +++ b/web_native/ios/Runner.xcodeproj/project.pbxproj @@ -289,17 +289,9 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.webNative; + PRODUCT_BUNDLE_IDENTIFIER = com.reichard.imagini; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -421,17 +413,9 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.webNative; + PRODUCT_BUNDLE_IDENTIFIER = com.reichard.imagini; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -448,17 +432,9 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.webNative; + PRODUCT_BUNDLE_IDENTIFIER = com.reichard.imagini; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/web_native/ios/Runner/Info.plist b/web_native/ios/Runner/Info.plist index 62ab2ce..2e6ab33 100644 --- a/web_native/ios/Runner/Info.plist +++ b/web_native/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Imagini + imagini CFBundlePackageType APPL CFBundleShortVersionString diff --git a/web_native/lib/bloc/bloc-prov-tree.dart b/web_native/lib/bloc/bloc-prov-tree.dart new file mode 100644 index 0000000..4d6b16b --- /dev/null +++ b/web_native/lib/bloc/bloc-prov-tree.dart @@ -0,0 +1,68 @@ +import 'package:flutter/widgets.dart'; +import 'package:imagini/bloc/bloc-prov.dart'; + +/// A Flutter [Widget] that merges multiple [BlocProvider] widgets into one widget tree. +/// +/// [BlocProviderTree] improves the readability and eliminates the need +/// to nest multiple [BlocProviders]. +/// +/// By using [BlocProviderTree] we can go from: +/// +/// ```dart +/// BlocProvider( +/// bloc: BlocA(), +/// child: BlocProvider( +/// bloc: BlocB(), +/// child: BlocProvider( +/// value: BlocC(), +/// child: ChildA(), +/// ) +/// ) +/// ) +/// ``` +/// +/// to: +/// +/// ```dart +/// BlocProviderTree( +/// blocProviders: [ +/// BlocProvider(bloc: BlocA()), +/// BlocProvider(bloc: BlocB()), +/// BlocProvider(bloc: BlocC()), +/// ], +/// child: ChildA(), +/// ) +/// ``` +/// +/// [BlocProviderTree] converts the [BlocProvider] list +/// into a tree of nested [BlocProvider] widgets. +/// As a result, the only advantage of using [BlocProviderTree] is improved +/// readability due to the reduction in nesting and boilerplate. +class BlocProviderTree extends StatelessWidget { + /// The [BlocProvider] list which is converted into a tree of [BlocProvider] widgets. + /// The tree of [BlocProvider] widgets is created in order meaning the first [BlocProvider] + /// will be the top-most [BlocProvider] and the last [BlocProvider] will be a direct ancestor + /// of the `child` [Widget]. + final List blocProviders; + + /// The [Widget] and its descendants which will have access to every [Bloc] provided by `blocProviders`. + /// This [Widget] will be a direct descendent of the last [BlocProvider] in `blocProviders`. + final Widget child; + + const BlocProviderTree({ + Key key, + @required this.blocProviders, + @required this.child, + }) : assert(blocProviders != null), + assert(child != null), + super(key: key); + + @override + Widget build(BuildContext context) { + Widget tree = child; + for (final blocProvider in blocProviders.reversed) { + tree = blocProvider.copyWith(tree); + } + return tree; + } +} diff --git a/web_native/lib/bloc/bloc-prov.dart b/web_native/lib/bloc/bloc-prov.dart new file mode 100644 index 0000000..7b0d090 --- /dev/null +++ b/web_native/lib/bloc/bloc-prov.dart @@ -0,0 +1,60 @@ +import 'package:flutter/widgets.dart'; +import 'package:imagini/bloc/bloc.dart'; + +/// A Flutter widget which provides a bloc to its children via `BlocProvider.of(context)`. +/// It is used as a DI widget so that a single instance of a bloc can be provided +/// to multiple widgets within a subtree. +class BlocProvider extends InheritedWidget { + /// The [Bloc] which is to be made available throughout the subtree + final T bloc; + + /// The [Widget] and its descendants which will have access to the [Bloc]. + final Widget child; + + BlocProvider({ + Key key, + @required this.bloc, + this.child, + }) : assert(bloc != null), + super(key: key, child: child); + + /// Method that allows widgets to access the bloc as long as their `BuildContext` + /// contains a `BlocProvider` instance. + static T of(BuildContext context) { + final type = _typeOf>(); + final BlocProvider provider = context + .ancestorInheritedElementForWidgetOfExactType(type) + ?.widget as BlocProvider; + + if (provider == null) { + throw FlutterError( + """ + BlocProvider.of() called with a context that does not contain a Bloc of type $T. + No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>(). + This can happen if the context you use comes from a widget above the BlocProvider. + This can also happen if you used BlocProviderTree and didn\'t explicity provide + the BlocProvider types: BlocProvider(bloc: $T()) instead of BlocProvider<$T>(bloc: $T()). + The context used was: $context + """, + ); + } + return provider?.bloc; + } + + /// Clone the current [BlocProvider] with a new child [Widget]. + /// All other values, including [Key] and [Bloc] are preserved. + BlocProvider copyWith(Widget child) { + return BlocProvider( + key: key, + bloc: bloc, + child: child, + ); + } + + /// Necessary to obtain generic [Type] + /// https://github.com/dart-lang/sdk/issues/11923 + static Type _typeOf() => T; + + @override + bool updateShouldNotify(BlocProvider oldWidget) => false; +} diff --git a/web_native/lib/bloc/bloc.dart b/web_native/lib/bloc/bloc.dart new file mode 100644 index 0000000..e026afa --- /dev/null +++ b/web_native/lib/bloc/bloc.dart @@ -0,0 +1 @@ +abstract class Bloc {} \ No newline at end of file diff --git a/web_native/lib/blocs/auth-bloc.dart b/web_native/lib/blocs/auth-bloc.dart new file mode 100644 index 0000000..68b0bc3 --- /dev/null +++ b/web_native/lib/blocs/auth-bloc.dart @@ -0,0 +1,5 @@ +import 'package:imagini/bloc/bloc.dart'; + +class AuthBloc extends Bloc { + AuthBloc(); +} diff --git a/web_native/lib/blocs/blocs.dart b/web_native/lib/blocs/blocs.dart new file mode 100644 index 0000000..6ee93e0 --- /dev/null +++ b/web_native/lib/blocs/blocs.dart @@ -0,0 +1,2 @@ +export 'auth-bloc.dart'; +export 'pref-bloc.dart'; \ No newline at end of file diff --git a/web_native/lib/blocs/pref-bloc.dart b/web_native/lib/blocs/pref-bloc.dart new file mode 100644 index 0000000..a95da4e --- /dev/null +++ b/web_native/lib/blocs/pref-bloc.dart @@ -0,0 +1,5 @@ +import 'package:imagini/bloc/bloc.dart'; + +class PrefBloc extends Bloc { + PrefBloc(); +} diff --git a/web_native/lib/components/roundedalertdialog.dart b/web_native/lib/components/roundedalertdialog.dart new file mode 100644 index 0000000..c881f0d --- /dev/null +++ b/web_native/lib/components/roundedalertdialog.dart @@ -0,0 +1,636 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:flutter/material.dart'; + +// Examples can assume: +// enum Department { treasury, state } + +/// A material design dialog. +/// +/// This dialog widget does not have any opinion about the contents of the +/// dialog. Rather than using this widget directly, consider using [AlertDialog] +/// or [SimpleDialog], which implement specific kinds of material design +/// dialogs. +/// +/// See also: +/// +/// * [AlertDialog], for dialogs that have a message and some buttons. +/// * [SimpleDialog], for dialogs that offer a variety of options. +/// * [showDialog], which actually displays the dialog and returns its result. +/// * +class Dialog extends StatelessWidget { + /// Creates a dialog. + /// + /// Typically used in conjunction with [showDialog]. + const Dialog({ + Key key, + this.child, + this.insetAnimationDuration: const Duration(milliseconds: 100), + this.insetAnimationCurve: Curves.decelerate, + }) : super(key: key); + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget child; + + /// The duration of the animation to show when the system keyboard intrudes + /// into the space that the dialog is placed in. + /// + /// Defaults to 100 milliseconds. + final Duration insetAnimationDuration; + + /// The curve to use for the animation shown when the system keyboard intrudes + /// into the space that the dialog is placed in. + /// + /// Defaults to [Curves.fastOutSlowIn]. + final Curve insetAnimationCurve; + + Color _getColor(BuildContext context) { + return Theme.of(context).dialogBackgroundColor; + } + + @override + Widget build(BuildContext context) { + return new AnimatedPadding( + padding: MediaQuery.of(context).viewInsets + + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), + duration: insetAnimationDuration, + curve: insetAnimationCurve, + child: new MediaQuery.removeViewInsets( + removeLeft: true, + removeTop: true, + removeRight: true, + removeBottom: true, + context: context, + child: new Center( + child: new ConstrainedBox( + constraints: const BoxConstraints(minWidth: 280.0), + child: new Material( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10.0)), side: BorderSide(color: Colors.white, width: 1.0, style: BorderStyle.solid)), + elevation: 30.0, + color: _getColor(context), + type: MaterialType.card, + child: child, + ), + ), + ), + ), + ); + } +} + +/// A material design alert dialog. +/// +/// An alert dialog informs the user about situations that require +/// acknowledgement. An alert dialog has an optional title and an optional list +/// of actions. The title is displayed above the content and the actions are +/// displayed below the content. +/// +/// If the content is too large to fit on the screen vertically, the dialog will +/// display the title and the actions and let the content overflow. Consider +/// using a scrolling widget, such as [ListView], for [content] to avoid +/// overflow. +/// +/// For dialogs that offer the user a choice between several options, consider +/// using a [SimpleDialog]. +/// +/// Typically passed as the child widget to [showDialog], which displays the +/// dialog. +/// +/// ## Sample code +/// +/// This snippet shows a method in a [State] which, when called, displays a dialog box +/// and returns a [Future] that completes when the dialog is dismissed. +/// +/// ```dart +/// Future _neverSatisfied() async { +/// return showDialog( +/// context: context, +/// barrierDismissible: false, // user must tap button! +/// builder: (BuildContext context) { +/// return new AlertDialog( +/// title: new Text('Rewind and remember'), +/// content: new SingleChildScrollView( +/// child: new ListBody( +/// children: [ +/// new Text('You will never be satisfied.'), +/// new Text('You\’re like me. I’m never satisfied.'), +/// ], +/// ), +/// ), +/// actions: [ +/// new FlatButton( +/// child: new Text('Regret'), +/// onPressed: () { +/// Navigator.of(context).pop(); +/// }, +/// ), +/// ], +/// ); +/// }, +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SimpleDialog], which handles the scrolling of the contents but has no [actions]. +/// * [Dialog], on which [AlertDialog] and [SimpleDialog] are based. +/// * [showDialog], which actually displays the dialog and returns its result. +/// * +class CustomAlertDialog extends StatelessWidget { + /// Creates an alert dialog. + /// + /// Typically used in conjunction with [showDialog]. + /// + /// The [contentPadding] must not be null. The [titlePadding] defaults to + /// null, which implies a default that depends on the values of the other + /// properties. See the documentation of [titlePadding] for details. + const CustomAlertDialog({ + Key key, + this.title, + this.titlePadding, + this.content, + this.contentPadding: const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), + this.actions, + this.semanticLabel, + }) : assert(contentPadding != null), + super(key: key); + + /// The (optional) title of the dialog is displayed in a large font at the top + /// of the dialog. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Padding around the title. + /// + /// If there is no title, no padding will be provided. Otherwise, this padding + /// is used. + /// + /// This property defaults to providing 24 pixels on the top, left, and right + /// of the title. If the [content] is not null, then no bottom padding is + /// provided (but see [contentPadding]). If it _is_ null, then an extra 20 + /// pixels of bottom padding is added to separate the [title] from the + /// [actions]. + final EdgeInsetsGeometry titlePadding; + + /// The (optional) content of the dialog is displayed in the center of the + /// dialog in a lighter font. + /// + /// Typically, this is a [ListView] containing the contents of the dialog. + /// Using a [ListView] ensures that the contents can scroll if they are too + /// big to fit on the display. + final Widget content; + + /// Padding around the content. + /// + /// If there is no content, no padding will be provided. Otherwise, padding of + /// 20 pixels is provided above the content to separate the content from the + /// title, and padding of 24 pixels is provided on the left, right, and bottom + /// to separate the content from the other edges of the dialog. + final EdgeInsetsGeometry contentPadding; + + /// The (optional) set of actions that are displayed at the bottom of the + /// dialog. + /// + /// Typically this is a list of [FlatButton] widgets. + /// + /// These widgets will be wrapped in a [ButtonBar], which introduces 8 pixels + /// of padding on each side. + /// + /// If the [title] is not null but the [content] _is_ null, then an extra 20 + /// pixels of padding is added above the [ButtonBar] to separate the [title] + /// from the [actions]. + final List actions; + + /// The semantic label of the dialog used by accessibility frameworks to + /// announce screen transitions when the dialog is opened and closed. + /// + /// If this label is not provided, a semantic label will be infered from the + /// [title] if it is not null. If there is no title, the label will be taken + /// from [MaterialLocalizations.alertDialogLabel]. + /// + /// See also: + /// + /// * [SemanticsConfiguration.isRouteName], for a description of how this + /// value is used. + final String semanticLabel; + + @override + Widget build(BuildContext context) { + final List children = []; + String label = semanticLabel; + + if (title != null) { + children.add(new Padding( + padding: titlePadding ?? + new EdgeInsets.fromLTRB( + 24.0, 24.0, 24.0, content == null ? 20.0 : 0.0), + child: new DefaultTextStyle( + style: Theme.of(context).textTheme.title, + child: new Semantics(child: title, namesRoute: true), + ), + )); + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + label = semanticLabel; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + label = semanticLabel ?? + MaterialLocalizations.of(context)?.alertDialogLabel; + } + } + + if (content != null) { + children.add(new Flexible( + child: new Padding( + padding: contentPadding, + child: new DefaultTextStyle( + style: Theme.of(context).textTheme.subhead, + child: content, + ), + ), + )); + } + + if (actions != null) { + children.add(new ButtonTheme.bar( + child: new ButtonBar( + children: actions, + ), + )); + } + + Widget dialogChild = new IntrinsicWidth( + child: new Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ), + ); + + if (label != null) + dialogChild = + new Semantics(namesRoute: true, label: label, child: dialogChild); + + return new Dialog(child: dialogChild); + } +} + +/// An option used in a [SimpleDialog]. +/// +/// A simple dialog offers the user a choice between several options. This +/// widget is commonly used to represent each of the options. If the user +/// selects this option, the widget will call the [onPressed] callback, which +/// typically uses [Navigator.pop] to close the dialog. +/// +/// The padding on a [SimpleDialogOption] is configured to combine with the +/// default [SimpleDialog.contentPadding] so that each option ends up 8 pixels +/// from the other vertically, with 20 pixels of spacing between the dialog's +/// title and the first option, and 24 pixels of spacing between the last option +/// and the bottom of the dialog. +/// +/// ## Sample code +/// +/// ```dart +/// new SimpleDialogOption( +/// onPressed: () { Navigator.pop(context, Department.treasury); }, +/// child: const Text('Treasury department'), +/// ) +/// ``` +/// +/// See also: +/// +/// * [SimpleDialog], for a dialog in which to use this widget. +/// * [showDialog], which actually displays the dialog and returns its result. +/// * [FlatButton], which are commonly used as actions in other kinds of +/// dialogs, such as [AlertDialog]s. +/// * +class SimpleDialogOption extends StatelessWidget { + /// Creates an option for a [SimpleDialog]. + const SimpleDialogOption({ + Key key, + this.onPressed, + this.child, + }) : super(key: key); + + /// The callback that is called when this option is selected. + /// + /// If this is set to null, the option cannot be selected. + /// + /// When used in a [SimpleDialog], this will typically call [Navigator.pop] + /// with a value for [showDialog] to complete its future with. + final VoidCallback onPressed; + + /// The widget below this widget in the tree. + /// + /// Typically a [Text] widget. + final Widget child; + + @override + Widget build(BuildContext context) { + return new InkWell( + onTap: onPressed, + child: new Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0), + child: child), + ); + } +} + +/// A simple material design dialog. +/// +/// A simple dialog offers the user a choice between several options. A simple +/// dialog has an optional title that is displayed above the choices. +/// +/// Choices are normally represented using [SimpleDialogOption] widgets. If +/// other widgets are used, see [contentPadding] for notes regarding the +/// conventions for obtaining the spacing expected by Material Design. +/// +/// For dialogs that inform the user about a situation, consider using an +/// [AlertDialog]. +/// +/// Typically passed as the child widget to [showDialog], which displays the +/// dialog. +/// +/// ## Sample code +/// +/// In this example, the user is asked to select between two options. These +/// options are represented as an enum. The [showDialog] method here returns +/// a [Future] that completes to a value of that enum. If the user cancels +/// the dialog (e.g. by hitting the back button on Android, or tapping on the +/// mask behind the dialog) then the future completes with the null value. +/// +/// The return value in this example is used as the index for a switch statement. +/// One advantage of using an enum as the return value and then using that to +/// drive a switch statement is that the analyzer will flag any switch statement +/// that doesn't mention every value in the enum. +/// +/// ```dart +/// Future _askedToLead() async { +/// switch (await showDialog( +/// context: context, +/// builder: (BuildContext context) { +/// return new SimpleDialog( +/// title: const Text('Select assignment'), +/// children: [ +/// new SimpleDialogOption( +/// onPressed: () { Navigator.pop(context, Department.treasury); }, +/// child: const Text('Treasury department'), +/// ), +/// new SimpleDialogOption( +/// onPressed: () { Navigator.pop(context, Department.state); }, +/// child: const Text('State department'), +/// ), +/// ], +/// ); +/// } +/// )) { +/// case Department.treasury: +/// // Let's go. +/// // ... +/// break; +/// case Department.state: +/// // ... +/// break; +/// } +/// } +/// ``` +/// +/// See also: +/// +/// * [SimpleDialogOption], which are options used in this type of dialog. +/// * [AlertDialog], for dialogs that have a row of buttons below the body. +/// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based. +/// * [showDialog], which actually displays the dialog and returns its result. +/// * +class SimpleDialog extends StatelessWidget { + /// Creates a simple dialog. + /// + /// Typically used in conjunction with [showDialog]. + /// + /// The [titlePadding] and [contentPadding] arguments must not be null. + const SimpleDialog({ + Key key, + this.title, + this.titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0), + this.children, + this.contentPadding: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0), + this.semanticLabel, + }) : assert(titlePadding != null), + assert(contentPadding != null), + super(key: key); + + /// The (optional) title of the dialog is displayed in a large font at the top + /// of the dialog. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Padding around the title. + /// + /// If there is no title, no padding will be provided. + /// + /// By default, this provides the recommend Material Design padding of 24 + /// pixels around the left, top, and right edges of the title. + /// + /// See [contentPadding] for the conventions regarding padding between the + /// [title] and the [children]. + final EdgeInsetsGeometry titlePadding; + + /// The (optional) content of the dialog is displayed in a + /// [SingleChildScrollView] underneath the title. + /// + /// Typically a list of [SimpleDialogOption]s. + final List children; + + /// Padding around the content. + /// + /// By default, this is 12 pixels on the top and 16 pixels on the bottom. This + /// is intended to be combined with children that have 24 pixels of padding on + /// the left and right, and 8 pixels of padding on the top and bottom, so that + /// the content ends up being indented 20 pixels from the title, 24 pixels + /// from the bottom, and 24 pixels from the sides. + /// + /// The [SimpleDialogOption] widget uses such padding. + /// + /// If there is no [title], the [contentPadding] should be adjusted so that + /// the top padding ends up being 24 pixels. + final EdgeInsetsGeometry contentPadding; + + /// The semantic label of the dialog used by accessibility frameworks to + /// announce screen transitions when the dialog is opened and closed. + /// + /// If this label is not provided, a semantic label will be infered from the + /// [title] if it is not null. If there is no title, the label will be taken + /// from [MaterialLocalizations.dialogLabel]. + /// + /// See also: + /// + /// * [SemanticsConfiguration.isRouteName], for a description of how this + /// value is used. + final String semanticLabel; + + @override + Widget build(BuildContext context) { + final List body = []; + String label = semanticLabel; + + if (title != null) { + body.add(new Padding( + padding: titlePadding, + child: new DefaultTextStyle( + style: Theme.of(context).textTheme.title, + child: new Semantics(namesRoute: true, child: title), + ))); + } else { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + label = semanticLabel; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + label = + semanticLabel ?? MaterialLocalizations.of(context)?.dialogLabel; + } + } + + if (children != null) { + body.add(new Flexible( + child: new SingleChildScrollView( + padding: contentPadding, + child: new ListBody(children: children), + ))); + } + + Widget dialogChild = new IntrinsicWidth( + stepWidth: 56.0, + child: new ConstrainedBox( + constraints: const BoxConstraints(minWidth: 280.0), + child: new Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: body, + ), + ), + ); + + if (label != null) + dialogChild = new Semantics( + namesRoute: true, + label: label, + child: dialogChild, + ); + return new Dialog(child: dialogChild); + } +} + +class _DialogRoute extends PopupRoute { + _DialogRoute({ + @required this.theme, + bool barrierDismissible: true, + this.barrierLabel, + @required this.child, + RouteSettings settings, + }) : assert(barrierDismissible != null), + _barrierDismissible = barrierDismissible, + super(settings: settings); + + final Widget child; + final ThemeData theme; + + @override + Duration get transitionDuration => const Duration(milliseconds: 150); + + @override + bool get barrierDismissible => _barrierDismissible; + final bool _barrierDismissible; + + @override + Color get barrierColor => Colors.black54; + + @override + final String barrierLabel; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new SafeArea( + child: new Builder(builder: (BuildContext context) { + final Widget annotatedChild = new Semantics( + child: child, + scopesRoute: true, + explicitChildNodes: true, + ); + return theme != null + ? new Theme(data: theme, child: annotatedChild) + : annotatedChild; + }), + ); + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return new FadeTransition( + opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), + child: child); + } +} + +/// Displays a dialog above the current contents of the app. +/// +/// This function takes a `builder` which typically builds a [Dialog] widget. +/// Content below the dialog is dimmed with a [ModalBarrier]. This widget does +/// not share a context with the location that `showDialog` is originally +/// called from. Use a [StatefulBuilder] or a custom [StatefulWidget] if the +/// dialog needs to update dynamically. +/// +/// The `context` argument is used to look up the [Navigator] and [Theme] for +/// the dialog. It is only used when the method is called. Its corresponding +/// widget can be safely removed from the tree before the dialog is closed. +/// +/// The `child` argument is deprecated, and should be replaced with `builder`. +/// +/// Returns a [Future] that resolves to the value (if any) that was passed to +/// [Navigator.pop] when the dialog was closed. +/// +/// The dialog route created by this method is pushed to the root navigator. +/// If the application has multiple [Navigator] objects, it may be necessary to +/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the +/// dialog rather just 'Navigator.pop(context, result)`. +/// +/// See also: +/// * [AlertDialog], for dialogs that have a row of buttons below a body. +/// * [SimpleDialog], which handles the scrolling of the contents and does +/// not show buttons below its body. +/// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based. +/// * +Future customShowDialog({ + @required + BuildContext context, + bool barrierDismissible: true, + @Deprecated( + 'Instead of using the "child" argument, return the child from a closure ' + 'provided to the "builder" argument. This will ensure that the BuildContext ' + 'is appropriate for widgets built in the dialog.') + Widget child, + WidgetBuilder builder, +}) { + assert(child == null || builder == null); + return Navigator.of(context, rootNavigator: true).push(new _DialogRoute( + child: child ?? new Builder(builder: builder), + theme: Theme.of(context, shadowThemeOnly: true), + barrierDismissible: barrierDismissible, + barrierLabel: + MaterialLocalizations.of(context).modalBarrierDismissLabel, + )); +} \ No newline at end of file diff --git a/web_native/lib/main.dart b/web_native/lib/main.dart index 11655b6..1870498 100644 --- a/web_native/lib/main.dart +++ b/web_native/lib/main.dart @@ -1,117 +1,28 @@ import 'package:flutter/material.dart'; +import 'package:imagini/theme/style.dart'; +import 'package:imagini/routes.dart'; +import 'package:imagini/bloc/bloc-prov-tree.dart'; +import 'package:imagini/bloc/bloc-prov.dart'; +import 'package:imagini/blocs/blocs.dart'; +import 'blocs/blocs.dart'; void main() { - runApp(MyApp()); + runApp(ExampleApp()); } - -class MyApp extends StatelessWidget { - // This widget is the root of your application. +class ExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - // This makes the visual density adapt to the platform that you run - // the app on. For desktop platforms, the controls will be smaller and - // closer together (more dense) than on mobile platforms. - visualDensity: VisualDensity.adaptivePlatformDensity, + return BlocProviderTree( + blocProviders: [ + BlocProvider(bloc: AuthBloc()), + BlocProvider(bloc: PrefBloc()), + ], + child: MaterialApp( + title: 'ExampleApp', + theme: appTheme(), + initialRoute: '/', + routes: routes, ), - home: MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/web_native/lib/main.dart.bak b/web_native/lib/main.dart.bak new file mode 100644 index 0000000..d8a0526 --- /dev/null +++ b/web_native/lib/main.dart.bak @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/web_native/lib/models/contact.dart b/web_native/lib/models/contact.dart new file mode 100644 index 0000000..505b45c --- /dev/null +++ b/web_native/lib/models/contact.dart @@ -0,0 +1,8 @@ +import 'package:meta/meta.dart'; + +class Contact { + final String avatarUrl; + final String name; + + Contact({@required this.avatarUrl, @required this.name}); +} \ No newline at end of file diff --git a/web_native/lib/routes.dart b/web_native/lib/routes.dart new file mode 100644 index 0000000..acefa2c --- /dev/null +++ b/web_native/lib/routes.dart @@ -0,0 +1,8 @@ +import 'package:flutter/widgets.dart'; +import 'package:imagini/screens/example1/examplescreen1.dart'; +import 'package:imagini/screens/example2/examplescreen2.dart'; + +final Map routes = { + "/": (BuildContext context) => ExScreen1(), + "/ExScreen2": (BuildContext context) => ExScreen2(), +}; diff --git a/web_native/lib/screens/example1/components/body.dart b/web_native/lib/screens/example1/components/body.dart new file mode 100644 index 0000000..5f8ca91 --- /dev/null +++ b/web_native/lib/screens/example1/components/body.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class Body extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: RaisedButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text('Go back!'), + ), + ); + } +} \ No newline at end of file diff --git a/web_native/lib/screens/example1/example-bloc.dart b/web_native/lib/screens/example1/example-bloc.dart new file mode 100644 index 0000000..ec695c4 --- /dev/null +++ b/web_native/lib/screens/example1/example-bloc.dart @@ -0,0 +1,20 @@ +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/services.dart'; +import 'package:imagini/models/contact.dart'; +import 'package:flutter/material.dart'; +import 'package:imagini/bloc/bloc.dart'; + +class ExampleBloc extends Bloc { + StreamSubscription _audioPlayerStateSubscription; + + Stream get example => _exampleSubject.stream; + Sink get exampleSink => _exampleSubject.sink; + final StreamController _exampleSubject = StreamController(); + + ExampleBloc(); + + void dispose() { + _exampleSubject.close(); + } +} diff --git a/web_native/lib/screens/example1/examplescreen1.dart b/web_native/lib/screens/example1/examplescreen1.dart new file mode 100644 index 0000000..f95be1e --- /dev/null +++ b/web_native/lib/screens/example1/examplescreen1.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:imagini/screens/example1/components/body.dart'; +import 'package:imagini/screens/example1/example-bloc.dart'; +import 'package:imagini/bloc/bloc-prov.dart'; + +class ExScreen1 extends StatefulWidget { + @override + _ExScreen1State createState() => _ExScreen1State(); +} + +class _ExScreen1State extends State { + ExampleBloc exampleBloc; + + @override + void initState() { + super.initState(); + + exampleBloc = ExampleBloc(); + } + + @override + void dispose() { + exampleBloc.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + bloc: exampleBloc, + child: Scaffold( + appBar: AppBar( + title: Text("First Screen"), + ), + body: Body(), + ), + ); + } +} diff --git a/web_native/lib/screens/example2/components/body.dart b/web_native/lib/screens/example2/components/body.dart new file mode 100644 index 0000000..5f8ca91 --- /dev/null +++ b/web_native/lib/screens/example2/components/body.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class Body extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: RaisedButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text('Go back!'), + ), + ); + } +} \ No newline at end of file diff --git a/web_native/lib/screens/example2/example2-bloc.dart b/web_native/lib/screens/example2/example2-bloc.dart new file mode 100644 index 0000000..a20c51f --- /dev/null +++ b/web_native/lib/screens/example2/example2-bloc.dart @@ -0,0 +1,20 @@ +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/services.dart'; +import 'package:imagini/models/contact.dart'; +import 'package:flutter/material.dart'; +import 'package:imagini/bloc/bloc.dart'; + +class Example2Bloc extends Bloc { + StreamSubscription _audioPlayerStateSubscription; + + Stream get example => _exampleSubject.stream; + Sink get exampleSink => _exampleSubject.sink; + final StreamController _exampleSubject = StreamController(); + + Example2Bloc(); + + void dispose() { + _exampleSubject.close(); + } +} diff --git a/web_native/lib/screens/example2/examplescreen2.dart b/web_native/lib/screens/example2/examplescreen2.dart new file mode 100644 index 0000000..fafe7dd --- /dev/null +++ b/web_native/lib/screens/example2/examplescreen2.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:imagini/screens/example2/components/body.dart'; +import 'package:imagini/screens/example2/example2-bloc.dart'; +import 'package:imagini/bloc/bloc-prov.dart'; + +class ExScreen2 extends StatefulWidget { + @override + _ExScreen2State createState() => _ExScreen2State(); +} + +class _ExScreen2State extends State { + Example2Bloc example2Bloc; + + @override + void initState() { + super.initState(); + + example2Bloc = Example2Bloc(); + } + + @override + void dispose() { + example2Bloc.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + bloc: Example2Bloc(), + child: Scaffold( + appBar: AppBar( + title: Text("Second Screen"), + ), + body: Body(), + ), + ); + } +} diff --git a/web_native/lib/services/exampleapi.dart b/web_native/lib/services/exampleapi.dart new file mode 100644 index 0000000..209be12 --- /dev/null +++ b/web_native/lib/services/exampleapi.dart @@ -0,0 +1,11 @@ +import 'dart:async'; +import 'package:http/http.dart' as http; + +Future exampleApi(String orgid) async { + http.Response response = await http.get( + Uri.encodeFull("https://www.example.com/api"), + ); + print("Respone ${response.body.toString()}"); + //Returns 'true' or 'false' as a String + return response.body; +} \ No newline at end of file diff --git a/web_native/lib/theme/style.dart b/web_native/lib/theme/style.dart new file mode 100644 index 0000000..a4fe749 --- /dev/null +++ b/web_native/lib/theme/style.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +ThemeData appTheme() { + return ThemeData( + primaryColor: Colors.white, + accentColor: Colors.orange, + hintColor: Colors.white, + dividerColor: Colors.white, + buttonColor: Colors.white, + scaffoldBackgroundColor: Colors.black, + canvasColor: Colors.black, + ); +} \ No newline at end of file diff --git a/web_native/pubspec.lock b/web_native/pubspec.lock index f628010..62bef60 100644 --- a/web_native/pubspec.lock +++ b/web_native/pubspec.lock @@ -1,153 +1,385 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "12.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.40.6" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0-nullsafety.3" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.2" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0-nullsafety.4" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.9.2+2" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3-nullsafety.3" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10-nullsafety.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0-nullsafety.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0-nullsafety.3" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0-nullsafety.4" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0-nullsafety.3" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0-nullsafety.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0-nullsafety.4" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10-nullsafety.3" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0-nullsafety.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0-nullsafety.6" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19-nullsafety.6" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.12-nullsafety.9" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" sdks: - dart: ">=2.10.0-110 <2.11.0" + dart: ">=2.12.0-0.0 <3.0.0" diff --git a/web_native/pubspec.yaml b/web_native/pubspec.yaml index fe56857..469cbbb 100644 --- a/web_native/pubspec.yaml +++ b/web_native/pubspec.yaml @@ -1,4 +1,4 @@ -name: Imagini +name: imagini description: A new Flutter project. # The following line prevents the package from being accidentally published to @@ -27,11 +27,13 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 + cupertino_icons: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/web_native/test/widget_test.dart b/web_native/test/widget_test.dart index 687b4ab..18df222 100644 --- a/web_native/test/widget_test.dart +++ b/web_native/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:web_native/main.dart'; +import 'package:imagini/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { diff --git a/web_native/web/favicon.png b/web_native/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/web_native/web/icons/Icon-192.png b/web_native/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/web_native/web/icons/Icon-512.png b/web_native/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/web_native/web/index.html b/web_native/web/index.html new file mode 100644 index 0000000..544da45 --- /dev/null +++ b/web_native/web/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + imagini + + + + + + + + diff --git a/web_native/web/manifest.json b/web_native/web/manifest.json new file mode 100644 index 0000000..7d9e184 --- /dev/null +++ b/web_native/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "imagini", + "short_name": "imagini", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +}