diff --git a/.gitignore b/.gitignore
index 7b790f8..fe28037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,27 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
-# Created by https://www.gitignore.io/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
-# Edit at https://www.gitignore.io/?templates=dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Created by https://www.toptal.com/developers/gitignore/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
+# Edit at https://www.toptal.com/developers/gitignore?templates=dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
### Android ###
# Built application files
*.apk
+*.aar
*.ap_
*.aab
@@ -18,6 +35,8 @@
bin/
gen/
out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
# Gradle files
.gradle/
@@ -46,7 +65,11 @@ captures/
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
+# Android Studio 3 in .gitignore file.
.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
@@ -55,9 +78,10 @@ captures/
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
+.cxx/
# Google Services (e.g. APIs or Firebase)
-google-services.json
+# google-services.json
# Freeline
freeline.py
@@ -71,117 +95,22 @@ fastlane/screenshots
fastlane/test_output
fastlane/readme.md
-### Android Patch ###
-gen-external-apklibs
-
-### AndroidStudio ###
-# Covers files to be ignored for android development using Android Studio.
-
-# Built application files
-
-# Files for the ART/Dalvik VM
-
-# Java class files
-
-# Generated files
-
-# Gradle files
-.gradle
-
-# Signing files
-.signing/
-
-# Local configuration file (sdk path, etc)
-
-# Proguard folder generated by Eclipse
-
-# Log Files
-
-# Android Studio
-/*/build/
-/*/local.properties
-/*/out
-/*/*/build
-/*/*/production
-*.ipr
-*~
-*.swp
+# Version control
+vcs.xml
-# Android Patch
-
-# External native build folder generated in Android Studio 2.2 and later
-
-# NDK
-obj/
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
-# IntelliJ IDEA
-*.iws
-/out/
-
-# User-specific configurations
-.idea/caches/
-.idea/libraries/
-.idea/shelf/
-.idea/.name
-.idea/compiler.xml
-.idea/copyright/profiles_settings.xml
-.idea/encodings.xml
-.idea/misc.xml
-.idea/modules.xml
-.idea/scopes/scope_settings.xml
-.idea/vcs.xml
-.idea/jsLibraryMappings.xml
-.idea/datasources.xml
-.idea/dataSources.ids
-.idea/sqlDataSources.xml
-.idea/dynamic.xml
-.idea/uiDesigner.xml
-
-# OS-specific files
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-ehthumbs.db
-Thumbs.db
-
-# Legacy Eclipse project files
-.classpath
-.project
-.cproject
-.settings/
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.war
-*.ear
-
-# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
-hs_err_pid*
-
-## Plugin-specific files:
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Mongo Explorer plugin
-.idea/mongoSettings.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-### AndroidStudio Patch ###
+### Android Patch ###
+gen-external-apklibs
+output.json
-!/gradle/wrapper/gradle-wrapper.jar
+# Replacement of .externalNativeBuild directories introduced
+# with Android Studio 3.5.
### CocoaPods ###
## CocoaPods GitIgnore Template
@@ -214,9 +143,63 @@ doc/api/
*.js.map
### Flutter ###
+# Flutter/Dart/Pub related
+**/doc/api/
.flutter-plugins
+.flutter-plugins-dependencies
+.fvm/
+.pub-cache/
+.pub/
+lib/generated_plugin_registrant.dart
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/key.properties
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/.last_build_id
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
### Linux ###
+*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
@@ -232,18 +215,23 @@ doc/api/
### macOS ###
# General
+.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
+
# Thumbnails
+._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
+.Spotlight-V100
.TemporaryItems
+.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
@@ -256,6 +244,9 @@ Temporary Items
### Windows ###
# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
ehthumbs_vista.db
# Dump file
@@ -301,15 +292,126 @@ DerivedData/
*.perspectivev3
!default.perspectivev3
+## Gcc Patch
+/*.gcno
+
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
-/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
-# End of https://www.gitignore.io/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
+### AndroidStudio ###
+# Covers files to be ignored for android development using Android Studio.
+
+# Built application files
+
+# Files for the ART/Dalvik VM
+
+# Java class files
+
+# Generated files
+
+# Gradle files
+.gradle
+
+# Signing files
+.signing/
+
+# Local configuration file (sdk path, etc)
+
+# Proguard folder generated by Eclipse
+
+# Log Files
+
+# Android Studio
+/*/build/
+/*/local.properties
+/*/out
+/*/*/build
+/*/*/production
+*.ipr
+*.swp
+
+# Keystore files
+*.jks
+*.keystore
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Android Patch
+
+# External native build folder generated in Android Studio 2.2 and later
+
+# NDK
+obj/
+
+# IntelliJ IDEA
+*.iws
+/out/
+
+# User-specific configurations
+.idea/caches/
+.idea/libraries/
+.idea/shelf/
+.idea/.name
+.idea/compiler.xml
+.idea/copyright/profiles_settings.xml
+.idea/encodings.xml
+.idea/misc.xml
+.idea/scopes/scope_settings.xml
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
+.idea/datasources.xml
+.idea/dataSources.ids
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+.idea/jarRepositories.xml
+
+# OS-specific files
+.DS_Store?
+
+# Legacy Eclipse project files
+.classpath
+.project
+.cproject
+.settings/
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.war
+*.ear
+
+# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
+hs_err_pid*
+
+## Plugin-specific files:
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Mongo Explorer plugin
+.idea/mongoSettings.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### AndroidStudio Patch ###
+
+!/gradle/wrapper/gradle-wrapper.jar
+
+# End of https://www.toptal.com/developers/gitignore/api/dart,xcode,linux,macos,windows,android,flutter,cocoapods,androidstudio
### Custom ###
@@ -328,4 +430,7 @@ packages
ios/.symlinks/ # Plugins
-secrets.json
\ No newline at end of file
+config.json
+
+# Flutter Plugin
+.flutter-plugins-dependencies
\ No newline at end of file
diff --git a/README.md b/README.md
index b90422e..4a2b83d 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,21 @@ A new Flutter application about "resumes".
For help getting started with Flutter, view our online
[documentation](https://flutter.io/).
+## Directory structure
+
+- Date:
+Contains data layer shared classes that are not framework dependent (e.g. Flutter, AngularDart) or interfaces that must be used by client application
+- Domain:
+Contains domain layer company rules
+- Bloc:
+Contains shared business logic components
+- Presentation:
+Contains commons classes
+
## Installation
### Config
-Use secret template `./secrets.json.dist` to add secret file `./secrets.json`
+Use secret template `./config.json.dist` to add secret file `./config.json`
### Generate models
[documentation](https://flutter.io/json/)
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..ff48c07
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,93 @@
+analyzer:
+ strong-mode:
+ implicit-casts: false
+
+linter:
+ rules:
+ - always_declare_return_types
+ - always_require_non_null_named_parameters
+ - annotate_overrides
+ - avoid_double_and_int_checks
+ - avoid_empty_else
+ - avoid_field_initializers_in_const_classes
+ - avoid_init_to_null
+ - avoid_null_checks_in_equality_operators
+ - avoid_positional_boolean_parameters
+ - avoid_relative_lib_imports
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null
+ - avoid_single_cascade_in_expression_statements
+ - avoid_slow_async_io
+ - avoid_types_as_parameter_names
+ - avoid_unused_constructor_parameters
+ - await_only_futures
+ - camel_case_types
+ - cancel_subscriptions
+ - close_sinks
+ - constant_identifier_names
+ - control_flow_in_finally
+ - directives_ordering
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - hash_and_equals
+ - implementation_imports
+ - invariant_booleans
+ - iterable_contains_unrelated_type
+ - join_return_with_assignment
+ - library_names
+ - library_prefixes
+ - list_remove_unrelated_type
+ - literal_only_boolean_expressions
+ - no_duplicate_case_values
+ - non_constant_identifier_names
+ - null_closures
+ - package_api_docs
+ - package_names
+ - package_prefixed_library_names
+ - parameter_assignments
+ - prefer_adjacent_string_concatenation
+ - prefer_asserts_in_initializer_lists
+ - prefer_collection_literals
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_const_literals_to_create_immutables
+ - prefer_constructors_over_static_methods
+ - prefer_contains
+ - prefer_equal_for_default_values
+ - prefer_final_fields
+ - prefer_final_locals
+ - prefer_foreach
+ - prefer_generic_function_type_aliases
+ - prefer_interpolation_to_compose_strings
+ - prefer_is_not_empty
+ - prefer_iterable_whereType
+ - prefer_single_quotes
+ - prefer_typing_uninitialized_variables
+ - recursive_getters
+ - slash_for_doc_comments
+ - sort_unnamed_constructors_first
+ - test_types_in_equals
+ - throw_in_finally
+ - type_annotate_public_apis
+ - type_init_formals
+ - unawaited_futures
+ - unnecessary_const
+ - unnecessary_getters_setters
+ - unnecessary_lambdas
+ - unnecessary_new
+ - unnecessary_null_aware_assignments
+ - unnecessary_null_in_if_null_operators
+ - unnecessary_overrides
+ - unnecessary_parenthesis
+ - unnecessary_statements
+ - unnecessary_this
+ - unrelated_type_equality_checks
+ - use_rethrow_when_possible
+ - use_string_buffers
+ - use_to_and_as_if_applicable
+ - valid_regexps
+ - void_checks
\ No newline at end of file
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..aba602b
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c0758fb..cf73c6a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
-
+
+
+
+ android:name="io.flutter.embedding.android.SplashScreenDrawable"
+ android:resource="@drawable/launch_background"
+ />
+
+
diff --git a/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java b/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java
deleted file mode 100644
index 01f2582..0000000
--- a/android/app/src/main/java/me/lebot/axel/cv/MainActivity.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package me.lebot.axel.cv;
-
-import android.os.Bundle;
-
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
-}
diff --git a/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt b/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt
new file mode 100644
index 0000000..080b341
--- /dev/null
+++ b/android/app/src/main/kotlin/me/lebot/axel/social_cv_client_flutter/MainActivity.kt
@@ -0,0 +1,6 @@
+package me.lebot.axel.social_cv_client_flutter
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 00fa441..322503e 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -1,8 +1,18 @@
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..aba602b
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/assets/images/login_logo.png b/assets/images/login_logo.png
new file mode 100644
index 0000000..b5ccd30
Binary files /dev/null and b/assets/images/login_logo.png differ
diff --git a/config.json.dist b/config.json.dist
new file mode 100644
index 0000000..1c7ee7d
--- /dev/null
+++ b/config.json.dist
@@ -0,0 +1,5 @@
+{
+ "apiServerUrl": "",
+ "clientId": "",
+ "clientSecret": ""
+}
\ No newline at end of file
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..70693e4
--- /dev/null
+++ b/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/lib/bloc.dart b/lib/bloc.dart
new file mode 100644
index 0000000..a67a23f
--- /dev/null
+++ b/lib/bloc.dart
@@ -0,0 +1,24 @@
+/// Blocs + Events + States
+export 'package:social_cv_client_flutter/src/bloc/blocs/application/application.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/authentication/authentication.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/configuration/configuration.dart';
+
+/// Element Blocs + Event + States
+export 'package:social_cv_client_flutter/src/bloc/blocs/element/element.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/element_list/element_list.dart';
+
+/// Element List Blocs + Events + States
+export 'package:social_cv_client_flutter/src/bloc/blocs/entry/entry.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/entry_list/entry_list.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/group/group.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/group_list/group_list.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/identity/identity.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/login/login.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/part/part.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/part_list/part_list.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/profile/profile.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/profile_list/profile_list.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/register/register.dart';
+export 'package:social_cv_client_flutter/src/bloc/blocs/user/user.dart';
+
+//export 'package:social_cv_client_flutter/src/bloc/blocs/user_list/user_list.dart';
diff --git a/lib/data.dart b/lib/data.dart
new file mode 100644
index 0000000..ff3be7d
--- /dev/null
+++ b/lib/data.dart
@@ -0,0 +1,82 @@
+export 'package:social_cv_client_flutter/src/data/cache_model.dart';
+
+/// ----------------------------------------------------------------------------
+/// Exceptions
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/exceptions/api_exceptions.dart';
+
+/// ----------------------------------------------------------------------------
+/// Managers
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/managers/api_interceptor.dart';
+export 'package:social_cv_client_flutter/src/data/managers/cv_api_manager.dart';
+
+/// ----------------------------------------------------------------------------
+/// Models
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/models/element_model.dart';
+export 'package:social_cv_client_flutter/src/data/models/entry_model.dart';
+export 'package:social_cv_client_flutter/src/data/models/envelop_models.dart';
+export 'package:social_cv_client_flutter/src/data/models/group_model.dart';
+export 'package:social_cv_client_flutter/src/data/models/part_model.dart';
+export 'package:social_cv_client_flutter/src/data/models/profile_model.dart';
+export 'package:social_cv_client_flutter/src/data/models/user_model.dart';
+
+/// ----------------------------------------------------------------------------
+/// Repositories
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/repositories/app_prefs_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/auth_info_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/entry_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/group_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/identity_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/part_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/profile_repository.dart';
+export 'package:social_cv_client_flutter/src/data/repositories/user_repository.dart';
+export 'package:social_cv_client_flutter/src/data/stores/app_prefs_data_store/app_prefs_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/app_prefs_data_store/app_prefs_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/auth_info_data_store/auth_info_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/auth_info_data_store/auth_info_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/entry_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/entry_data_store/memory_entry_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/group_date_store/group_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/group_date_store/group_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/group_date_store/memory_group_data_store.dart';
+
+/// ----------------------------------------------------------------------------
+/// Data Stores
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/identity_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/identity_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/identity_data_store/memory_identity_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/part_date_store/memory_part_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/part_date_store/part_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/part_date_store/part_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/memory_profile_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/profile_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/profile_date_store/profile_data_store_factory.dart';
+export 'package:social_cv_client_flutter/src/data/stores/user_data_store/memory_user_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/user_data_store/user_data_store.dart';
+export 'package:social_cv_client_flutter/src/data/stores/user_data_store/user_data_store_factory.dart';
+
+/// ----------------------------------------------------------------------------
+/// Data Stores
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/utils/utils.dart';
+
+
+/// ----------------------------------------------------------------------------
+/// Managers
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/data/managers/app_shared_preferences_manager.dart';
+export 'package:social_cv_client_flutter/src/data/managers/auth_shared_preferences_manager.dart';
+export 'package:social_cv_client_flutter/src/data/managers/config_assets_manager.dart';
diff --git a/lib/domain.dart b/lib/domain.dart
new file mode 100644
index 0000000..5372519
--- /dev/null
+++ b/lib/domain.dart
@@ -0,0 +1,45 @@
+/// ----------------------------------------------------------------------------
+/// Entities
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/domain/entities/auth_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/base_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/element_model.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/entry_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/group_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/part_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/profile_entity.dart';
+export 'package:social_cv_client_flutter/src/domain/entities/user_entity.dart';
+
+/// ----------------------------------------------------------------------------
+/// Errors
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/domain/errors/commons_errors.dart';
+
+/// ----------------------------------------------------------------------------
+/// Exceptions
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/domain/exceptions/app_exceptions.dart';
+export 'package:social_cv_client_flutter/src/domain/exceptions/http_exceptions.dart';
+
+/// ----------------------------------------------------------------------------
+/// Repositories
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/domain/repositories/app_preferences_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/auth_info_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/entry_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/group_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/identity_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/part_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/profile_repository.dart';
+export 'package:social_cv_client_flutter/src/domain/repositories/user_repository.dart';
+
+/// ----------------------------------------------------------------------------
+/// Services
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/domain/services/cv_auth_service.dart';
+export 'package:social_cv_client_flutter/src/domain/services/foundation_config_service.dart';
diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart
new file mode 100644
index 0000000..9f6808e
--- /dev/null
+++ b/lib/generated_plugin_registrant.dart
@@ -0,0 +1,16 @@
+//
+// Generated file. Do not edit.
+//
+
+// ignore: unused_import
+import 'dart:ui';
+
+import 'package:shared_preferences_web/shared_preferences_web.dart';
+
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+
+// ignore: public_member_api_docs
+void registerPlugins(PluginRegistry registry) {
+ SharedPreferencesPlugin.registerWith(registry.registrarFor(SharedPreferencesPlugin));
+ registry.registerMessageHandler();
+}
diff --git a/lib/main.dart b/lib/main.dart
index 0a80b16..512b096 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,75 +1,53 @@
import 'dart:async';
import 'package:flutter/material.dart';
-import 'package:social_cv_client_dart_common/repositories.dart';
-import 'package:social_cv_client_flutter/src/app.dart';
-import 'package:social_cv_client_flutter/src/repositories/shared_preferences_repository.dart';
-import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart';
-import 'package:social_cv_client_flutter/src/repositories/local_secrets_repository.dart';
-import 'package:social_cv_client_flutter/src/utils/logger.dart';
-import 'package:social_cv_client_flutter/src/utils/logging_service.dart';
+import 'package:flutter/rendering.dart';
+import 'package:social_cv_client_flutter/src/presentation/app.dart';
+import 'package:social_cv_client_flutter/src/presentation/utils/logger.dart';
-// TODO automatically set this to false for release builds
-const DEBUG_MODE = true;
+/// TODO: automatically set this to false for release builds
+// ignore: constant_identifier_names
+const bool DEBUG_MODE = true;
+// ignore: constant_identifier_names
+const bool DEBUG_PAINT_SIZE = false;
+
+FutureOr main() async {
+ String _tag = '$main';
-Future main() async {
/// SystemChrome.setPreferredOrientations(
/// [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
void run() async {
- initLogger(package: "CV App");
-
- SecretsRepository secretsRepository = LocalSecretsRepository();
- PreferencesRepository preferencesRepository = SharedPreferencesRepository();
-
- CVClient cvClient = CVClientImpl(
- accessToken: await preferencesRepository.getAccessToken(),
- refreshToken: await preferencesRepository.getRefreshToken(),
- );
- CVCache cvCache = CVCacheImpl();
-
- CVRepository cvRepository = CVRepositoryImpl(
- client: cvClient,
- cache: cvCache,
- );
-
- runApp(
- RepositoriesProvider(
- cvRepository: cvRepository,
- preferencesRepository: preferencesRepository,
- secretsRepository: secretsRepository,
- child: CVApp(),
- ),
- );
+ runApp(ConfigWrapperApp());
}
+ FlutterError.onError = globalErrorHandler;
+
if (DEBUG_MODE) {
+ debugPaintSizeEnabled = DEBUG_PAINT_SIZE;
run();
} else {
- FlutterError.onError = globalErrorHandler;
runZoned(run, onError: globalErrorHandler);
}
}
-///
/// Global error handler. Show stack trace
-///
void globalErrorHandler(details) {
- String stackTrace;
+ StackTrace stackTrace;
if (details is FlutterErrorDetails) {
if (details.exception is Error) {
- stackTrace = details.stack.toString();
+ stackTrace = details.stack;
}
} else if (details is Error) {
- stackTrace = details.stackTrace.toString();
+ stackTrace = details.stackTrace;
} else {
+ Logger.fatal(
+ '${details.runtimeType}',
+ errorCode: ErrorCodes.UNHANDLED_EXCEPTION,
+ );
throw details;
}
- LoggingService.fatal(
- details.toString(),
- errorCode: ErrorCodes.UNHANDLED_EXCEPTION,
- stackTrace: stackTrace,
- );
+ Logger.error('${details.runtimeType}', stackTrace: stackTrace);
}
diff --git a/lib/presentation.dart b/lib/presentation.dart
new file mode 100644
index 0000000..15abd7a
--- /dev/null
+++ b/lib/presentation.dart
@@ -0,0 +1,102 @@
+/// ----------------------------------------------------------------------------
+/// Libs
+/// ----------------------------------------------------------------------------
+
+export 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+
+/// ----------------------------------------------------------------------------
+/// Mappers
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/mappers/model_mapper.dart';
+
+/// ----------------------------------------------------------------------------
+/// View Models
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/models/api_models.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/cursor_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/element_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/entry_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/group_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/part_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/profile_model.dart';
+export 'package:social_cv_client_flutter/src/presentation/models/user_model.dart';
+
+/// ----------------------------------------------------------------------------
+/// Commons
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/commons/api_values.dart';
+export 'package:social_cv_client_flutter/src/presentation/commons/assets.dart';
+export 'package:social_cv_client_flutter/src/presentation/commons/paths.dart';
+export 'package:social_cv_client_flutter/src/presentation/commons/styles.dart';
+export 'package:social_cv_client_flutter/src/presentation/commons/tags.dart';
+
+/// ----------------------------------------------------------------------------
+/// Localizations
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/localizations/cv_localization.dart';
+
+/// ----------------------------------------------------------------------------
+/// Pages
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/pages/account_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/auth_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/elements/entry_profile_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/elements/group_profile_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/elements/part_profile_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/elements/profile_profile_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/home_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/main_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/search_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/search_page.dart';
+export 'package:social_cv_client_flutter/src/presentation/pages/settings_page.dart';
+
+/// ----------------------------------------------------------------------------
+/// Utils
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/router.dart';
+export 'package:social_cv_client_flutter/src/presentation/utils/logger.dart';
+export 'package:social_cv_client_flutter/src/presentation/utils/navigation.dart';
+export 'package:social_cv_client_flutter/src/presentation/utils/translate_error.dart';
+export 'package:social_cv_client_flutter/src/presentation/utils/utils.dart';
+
+/// ----------------------------------------------------------------------------
+/// Widget
+/// ----------------------------------------------------------------------------
+
+export 'package:social_cv_client_flutter/src/presentation/widgets/account_tile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/arc_banner_image_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/bubble_indication_painter.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_list_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_list_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/entry_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_list_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_list_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/group_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_list_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/part_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_list_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_tile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/elements/profile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/error_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/initial_circle_avatar_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/loading_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/login_form_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/menu_bottom_sheet_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/menu_button_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/profile_image_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/register_form_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/repository_provider.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/sort_box_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/sort_dialog_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/sort_list_tile_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/splash_widget.dart';
+export 'package:social_cv_client_flutter/src/presentation/widgets/theme_switch_tile_widget.dart';
diff --git a/lib/src/app.dart b/lib/src/app.dart
deleted file mode 100644
index 84bd603..0000000
--- a/lib/src/app.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_localizations/flutter_localizations.dart';
-import 'package:social_cv_client_dart_common/blocs.dart';
-import 'package:social_cv_client_flutter/src/blocs/bloc_provider.dart';
-import 'package:social_cv_client_flutter/src/blocs/main_bloc.dart';
-import 'package:social_cv_client_flutter/src/commons/colors.dart';
-import 'package:social_cv_client_flutter/src/localizations/cv_localization.dart';
-import 'package:social_cv_client_flutter/src/pages/main_page.dart';
-import 'package:social_cv_client_flutter/src/repositories/repositories_provider.dart';
-import 'package:social_cv_client_flutter/src/routes.dart';
-import 'package:social_cv_client_flutter/src/utils/logger.dart';
-
-class CVApp extends StatelessWidget {
- const CVApp({
- Key key,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- logger.info('Building App');
-
- RepositoriesProvider repositories = RepositoriesProvider.of(context);
-
- return BlocProvider(
- bloc: ApplicationBloc(
- preferencesRepository: repositories.preferencesRepository,
- ),
- child: BlocProvider(
- bloc: AccountBloc(
- cvRepository: repositories.cvRepository,
- preferencesRepository: repositories.preferencesRepository,
- secretRepository: repositories.secretsRepository,
- ),
- child: _CVThemedApp(),
- ),
- );
- }
-}
-
-class _CVThemedApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- RepositoriesProvider repositories = RepositoriesProvider.of(context);
-
- BlocProvider _mainPageProvider = BlocProvider(
- bloc: MainBloc(),
- child: MainPage(),
- );
-
- ///Routes
- Routes routes = Routes(
- mainPageProvider: _mainPageProvider,
- cvRepository: repositories.cvRepository,
- preferencesRepository: repositories.preferencesRepository,
- secretsRepository: repositories.secretsRepository,
- );
-
- ApplicationBloc _appBloc = BlocProvider.of(context);
-
- return StreamBuilder(
- stream: _appBloc.themeStream,
- builder: (BuildContext context, AsyncSnapshot snapshot) {
- return MaterialApp(
- onGenerateTitle: (BuildContext context) =>
- CVLocalizations.of(context).appName,
- theme: _buildCVTheme(snapshot.data),
- home: _mainPageProvider,
- onGenerateRoute: routes.router.generator,
-
- ///Use Fluro routes
- localizationsDelegates: [
- const CVLocalizationsDelegate(),
- GlobalMaterialLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
- ],
- supportedLocales: [
- const Locale('en'),
- const Locale('fr'),
- ],
- debugShowCheckedModeBanner: false,
-// showSemanticsDebugger: true,
- );
- });
- }
-
- ThemeData _buildCVTheme(String theme) {
- ThemeData base;
- if (theme != ThemeType.DARK)
- base = ThemeData.light();
- else {
- base = ThemeData.dark();
- }
-
- return base.copyWith(
- primaryColor: AppColors.kCVPrimaryColor,
- primaryColorLight: AppColors.kCVPrimaryColorLight,
- primaryColorDark: AppColors.kCVPrimaryColorDark,
- accentColor: AppColors.kCVAccentColor,
- buttonColor: (theme != ThemeType.DARK)
- ? AppColors.kCVWhite
- : AppColors.kCVPrimaryColorDark,
- inputDecorationTheme: InputDecorationTheme(
- border: OutlineInputBorder(),
- ),
- textTheme: _buildCVTextTheme(base.textTheme),
- primaryTextTheme: _buildCVTextTheme(base.primaryTextTheme),
- accentTextTheme: _buildCVTextTheme(base.accentTextTheme),
- );
- }
-
- TextTheme _buildCVTextTheme(TextTheme base) {
- return base.apply(
- fontFamily: 'Google Sans',
- );
- }
-}
diff --git a/lib/src/bloc/blocs/application/application.dart b/lib/src/bloc/blocs/application/application.dart
new file mode 100644
index 0000000..57124f2
--- /dev/null
+++ b/lib/src/bloc/blocs/application/application.dart
@@ -0,0 +1,3 @@
+export './application_bloc.dart';
+export './application_event.dart';
+export './application_state.dart';
diff --git a/lib/src/bloc/blocs/application/application_bloc.dart b/lib/src/bloc/blocs/application/application_bloc.dart
new file mode 100644
index 0000000..e9174fb
--- /dev/null
+++ b/lib/src/bloc/blocs/application/application_bloc.dart
@@ -0,0 +1,69 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Application behaviors
+/// Can manage theme
+class AppBloc extends Bloc {
+ final String _tag = '$AppBloc';
+
+ final AppPrefsRepository appPreferencesRepository;
+
+ AppBloc({
+ @required this.appPreferencesRepository,
+ }) : assert(
+ appPreferencesRepository != null,
+ 'No $AppPrefsRepository given',
+ ),
+ super();
+
+ @override
+ AppState get initialState => AppUninitialized();
+
+ @override
+ Stream mapEventToState(AppEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is AppConfigured) {
+ yield* _mapAppConfiguredToState(event);
+ } else if (event is AppThemeChanged) {
+ yield* _mapAppThemeChangedToState(event);
+ }
+ }
+
+ /// -----------------------------------------------------------------------
+ /// All Event map to State
+ /// -----------------------------------------------------------------------
+
+ /// Map [AppThemeChanged] to [AppState]
+ ///
+ /// ```dart
+ /// yield* _mapAppThemeChangedToState(event);
+ /// ```
+ Stream _mapAppThemeChangedToState(AppThemeChanged event) async* {
+ try {
+ yield AppLoading();
+ await appPreferencesRepository.toggleDarkMode(event.darkMode);
+ yield AppInitialized(darkMode: event.darkMode);
+ } catch (error) {
+ yield AppFailure(error: error);
+ }
+ }
+
+ /// Map [AppConfigured] to [AppState]
+ ///
+ /// ```dart
+ /// yield* _mapAppConfiguredToState(event);
+ /// ```
+ Stream _mapAppConfiguredToState(AppConfigured event) async* {
+ try {
+ yield AppLoading();
+ final darkMode = await appPreferencesRepository.getDarkMode() ?? false;
+ yield AppInitialized(darkMode: darkMode);
+ } catch (error) {
+ yield AppFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/application/application_event.dart b/lib/src/bloc/blocs/application/application_event.dart
new file mode 100644
index 0000000..e20c3ff
--- /dev/null
+++ b/lib/src/bloc/blocs/application/application_event.dart
@@ -0,0 +1,23 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+/// [AppEvent] that must be dispatch to [AppBloc]
+abstract class AppEvent extends Equatable {
+ AppEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class AppConfigured extends AppEvent {}
+
+class AppThemeChanged extends AppEvent {
+ final bool darkMode;
+
+ AppThemeChanged({@required this.darkMode}) : super([darkMode]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'darkMode: $darkMode'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/application/application_state.dart b/lib/src/bloc/blocs/application/application_state.dart
new file mode 100644
index 0000000..8fa1ca6
--- /dev/null
+++ b/lib/src/bloc/blocs/application/application_state.dart
@@ -0,0 +1,37 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+abstract class AppState extends Equatable {
+ AppState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class AppUninitialized extends AppState {}
+
+class AppInitialized extends AppState {
+ final bool darkMode;
+
+ AppInitialized({this.darkMode = false}) : super([darkMode]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'darkMode: $darkMode'
+ ' }';
+}
+
+class AppFailure extends AppState {
+ final dynamic error;
+
+ AppFailure({@required this.error})
+ : assert(error != null, 'No error given'),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
+
+class AppLoading extends AppState {}
diff --git a/lib/src/bloc/blocs/authentication/authentication.dart b/lib/src/bloc/blocs/authentication/authentication.dart
new file mode 100644
index 0000000..0383408
--- /dev/null
+++ b/lib/src/bloc/blocs/authentication/authentication.dart
@@ -0,0 +1,3 @@
+export './authentication_bloc.dart';
+export './authentication_event.dart';
+export './authentication_state.dart';
diff --git a/lib/src/bloc/blocs/authentication/authentication_bloc.dart b/lib/src/bloc/blocs/authentication/authentication_bloc.dart
new file mode 100644
index 0000000..e38a1bb
--- /dev/null
+++ b/lib/src/bloc/blocs/authentication/authentication_bloc.dart
@@ -0,0 +1,129 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Authentication
+class AuthenticationBloc
+ extends Bloc {
+ final String _tag = '$AuthenticationBloc';
+
+ final CVAuthService cvAuthService;
+ final AuthInfoRepository authInfoRepository;
+ final LoginBloc loginBloc;
+ final RegisterBloc registerBloc;
+
+ StreamSubscription registerBlocSubscription;
+ StreamSubscription loginBlocSubscription;
+
+ AuthenticationBloc({
+ @required this.cvAuthService,
+ @required this.authInfoRepository,
+ @required this.loginBloc,
+ @required this.registerBloc,
+ }) : assert(cvAuthService != null, 'No $CVAuthService given'),
+ assert(authInfoRepository != null, 'No $AppPrefsRepository given'),
+ assert(loginBloc != null, 'No $LoginBloc given'),
+ assert(registerBloc != null, 'No $RegisterBloc given'),
+ super() {
+ loginBlocSubscription = loginBloc.state.listen((state) {
+ if (state is LoginSucceed) {
+ dispatch(LoggedIn(
+ accessToken: state.accessToken,
+ accessTokenExpiration: state.accessTokenExpiration,
+ refreshToken: state.refreshToken,
+ ));
+ }
+ });
+
+ registerBlocSubscription = registerBloc.state.listen((state) {
+ if (state is RegisterSucceed) {
+ dispatch(LoggedIn(
+ accessToken: state.accessToken,
+ accessTokenExpiration: state.accessTokenExpiration,
+ refreshToken: state.refreshToken,
+ ));
+ }
+ });
+ }
+
+ @override
+ void dispose() {
+ loginBlocSubscription.cancel();
+ registerBlocSubscription.cancel();
+ super.dispose();
+ }
+
+ @override
+ AuthenticationState get initialState => AuthenticationUninitialized();
+
+ @override
+ Stream mapEventToState(
+ AuthenticationEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is AppStarted) {
+ yield* _mapAppStartedToState(event);
+ } else if (event is LoggedIn) {
+ yield* _mapLoggedInToState(event);
+ } else if (event is LoggedOut) {
+ yield* _mapLoggedOutToState(event);
+ }
+ }
+
+ /// -----------------------------------------------------------------------
+ /// All Event map to State
+ /// -----------------------------------------------------------------------
+
+ /// Map [AppStarted] to [AuthenticationState]
+ ///
+ /// ```dart
+ /// yield* _mapAppStartedToState(event);
+ /// ```
+ Stream _mapAppStartedToState(AppStarted event) async* {
+ try {
+ final token = await authInfoRepository.getAccessToken();
+
+ /// TODO: Check access token expiration and fetch new access token with refresh token
+ /// TODO: Check refresh token expiration, if it's expired set state to Unauthenticated
+
+ if (token != null && token?.length > 0) {
+ yield AuthenticationAuthenticated();
+ } else {
+ yield AuthenticationUnauthenticated();
+ }
+ } catch (error) {
+ yield AuthenticationFailed(error: error);
+ }
+ }
+
+ /// Map [LoggedIn] to [AuthenticationState]
+ ///
+ /// ```dart
+ /// yield* _mapLoggedInToState(event);
+ /// ```
+ Stream _mapLoggedInToState(LoggedIn event) async* {
+ yield AuthenticationLoading();
+ await authInfoRepository.setAccessToken(event.accessToken);
+ await authInfoRepository
+ .setAccessTokenExpiration(event.accessTokenExpiration);
+ await authInfoRepository.setRefreshToken(event.refreshToken);
+ yield AuthenticationAuthenticated();
+ }
+
+ /// Map [LoggedIn] to [AuthenticationState]
+ ///
+ /// ```dart
+ /// yield* _mapLoggedInToState(event);
+ /// ```
+ Stream _mapLoggedOutToState(LoggedOut event) async* {
+ yield AuthenticationLoading();
+ await cvAuthService.logout();
+ await authInfoRepository.deleteAccessToken();
+ await authInfoRepository.deleteAccessTokenExpiration();
+ await authInfoRepository.deleteRefreshToken();
+ await authInfoRepository.deleteRefreshTokenExpiration();
+ yield AuthenticationUnauthenticated();
+ }
+}
diff --git a/lib/src/bloc/blocs/authentication/authentication_event.dart b/lib/src/bloc/blocs/authentication/authentication_event.dart
new file mode 100644
index 0000000..f908226
--- /dev/null
+++ b/lib/src/bloc/blocs/authentication/authentication_event.dart
@@ -0,0 +1,37 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+/// [AuthenticationEvent] that must be dispatch to [AuthenticationBloc]
+abstract class AuthenticationEvent extends Equatable {
+ AuthenticationEvent([List props = const []]) : super(props);
+}
+
+/// Use [AppStarted] to begin auth process on startup
+class AppStarted extends AuthenticationEvent {}
+
+/// Use [LoggedIn] to inform that user just logged in
+class LoggedIn extends AuthenticationEvent {
+ final String accessToken;
+ final DateTime accessTokenExpiration;
+ final String refreshToken;
+
+ LoggedIn({
+ @required this.accessToken,
+ @required this.accessTokenExpiration,
+ @required this.refreshToken,
+ }) : super([
+ accessToken,
+ accessTokenExpiration,
+ refreshToken,
+ ]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'accessToken: $accessToken, '
+ 'accessTokenExpiration: $accessTokenExpiration, '
+ 'refreshToken: $refreshToken, '
+ ' }';
+}
+
+/// Use [LoggedOut] to request logout
+class LoggedOut extends AuthenticationEvent {}
diff --git a/lib/src/bloc/blocs/authentication/authentication_state.dart b/lib/src/bloc/blocs/authentication/authentication_state.dart
new file mode 100644
index 0000000..d2b9a2e
--- /dev/null
+++ b/lib/src/bloc/blocs/authentication/authentication_state.dart
@@ -0,0 +1,30 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+abstract class AuthenticationState extends Equatable {
+ AuthenticationState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class AuthenticationUninitialized extends AuthenticationState {}
+
+class AuthenticationAuthenticated extends AuthenticationState {}
+
+class AuthenticationUnauthenticated extends AuthenticationState {}
+
+class AuthenticationLoading extends AuthenticationState {}
+
+class AuthenticationFailed extends AuthenticationState {
+ final dynamic error;
+
+ AuthenticationFailed({@required this.error})
+ : assert(error != null, 'No error given'),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/configuration/configuration.dart b/lib/src/bloc/blocs/configuration/configuration.dart
new file mode 100644
index 0000000..5a48431
--- /dev/null
+++ b/lib/src/bloc/blocs/configuration/configuration.dart
@@ -0,0 +1,3 @@
+export './configuration_bloc.dart';
+export './configuration_event.dart';
+export './configuration_state.dart';
diff --git a/lib/src/bloc/blocs/configuration/configuration_bloc.dart b/lib/src/bloc/blocs/configuration/configuration_bloc.dart
new file mode 100644
index 0000000..b8b8d2c
--- /dev/null
+++ b/lib/src/bloc/blocs/configuration/configuration_bloc.dart
@@ -0,0 +1,178 @@
+import 'package:bloc/bloc.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/data.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+class ConfigurationBloc extends Bloc {
+ final String _tag = '$ConfigurationBloc';
+
+ ConfigurationBloc() : super();
+
+ /// Services
+ FoundationConfigService _foundationConfigService;
+ CVAuthService _cvAuthService;
+
+ /// Repositories
+ AuthInfoRepository _authInfoRepository;
+ AppPrefsRepository _appPrefsRepository;
+
+ /// Entities Repositories
+ IdentityRepository _identityRepository;
+ UserRepository _userRepository;
+ ProfileRepository _profileRepository;
+ PartRepository _partRepository;
+ GroupRepository _groupRepository;
+ EntryRepository _entryRepository;
+
+ @override
+ ConfigurationState get initialState => ConfigLoading();
+
+ @override
+ Stream mapEventToState(ConfigurationEvent event) async* {
+ if (event is AppLaunched) {
+ yield* _mapAppLaunchedEventToState();
+ }
+ }
+
+ /// -----------------------------------------------------------------------
+ /// All Event map to State
+ /// -----------------------------------------------------------------------
+
+ Stream _mapAppLaunchedEventToState() async* {
+ try {
+ yield ConfigLoading();
+
+ _foundationConfigService = ConfigAssetsManager();
+
+ final AuthInfoDataStore diskAuthInfoDataStore =
+ AuthSharedPreferencesManager();
+
+ final apiInterceptor = ApiInterceptor(
+ accessToken: await diskAuthInfoDataStore.getAccessToken(),
+ refreshToken: await diskAuthInfoDataStore.getRefreshToken(),
+ );
+
+ final CVApiManager cvApiManager = CVApiManager(
+ apiBaseUrl: await _foundationConfigService.getApiServerUrl(),
+ tokenInterceptor: apiInterceptor,
+ );
+
+ _cvAuthService = cvApiManager;
+
+ // Data Stores
+
+ final AppPrefsDataStore diskAppPrefsDataStore = AppPrefsManager();
+
+ final IdentityDataStore memoryIdentityDataStore =
+ MemoryIdentityDataStore();
+ final IdentityDataStore cloudIdentityDataStore = cvApiManager;
+
+ final ProfileDataStore memoryProfileDataStore = MemoryProfileDataStore();
+ final ProfileDataStore cloudProfileDataStore = cvApiManager;
+
+ final UserDataStore memoryUserDataStore = MemoryUserDataStore();
+ final UserDataStore cloudUserDataStore = cvApiManager;
+
+ final PartDataStore memoryPartDataStore = MemoryPartDataStore();
+ final PartDataStore cloudPartDataStore = cvApiManager;
+
+ final GroupDataStore memoryGroupDataStore = MemoryGroupDataStore();
+ final GroupDataStore cloudGroupDataStore = cvApiManager;
+
+ final EntryDataStore memoryEntryDataStore = MemoryEntryDataStore();
+ final EntryDataStore cloudEntryDataStore = cvApiManager;
+
+ // Data Store Factories
+
+ final appPrefsDataStoreFactory = AppPrefsDataStoreFactory(
+ diskDataStore: diskAppPrefsDataStore,
+ );
+
+ final authInfoDataStoreFactory = AuthInfoDataStoreFactory(
+ diskDataStore: diskAuthInfoDataStore,
+ );
+
+ final identityDataStoreFactory = IdentityDataStoreFactory(
+ memoryDataStore: memoryIdentityDataStore,
+ cloudDataStore: cloudIdentityDataStore,
+ );
+
+ final userDataStoreFactory = UserDataStoreFactory(
+ memoryDataStore: memoryUserDataStore,
+ cloudDataStore: cloudUserDataStore,
+ );
+
+ final profileDataStoreFactory = ProfileDataStoreFactory(
+ memoryDataStore: memoryProfileDataStore,
+ cloudDataStore: cloudProfileDataStore,
+ );
+
+ final partDataStoreFactory = PartDataStoreFactory(
+ memoryDataStore: memoryPartDataStore,
+ cloudDataStore: cloudPartDataStore,
+ );
+
+ final groupDataStoreFactory = GroupDataStoreFactory(
+ memoryDataStore: memoryGroupDataStore,
+ cloudDataStore: cloudGroupDataStore,
+ );
+
+ final entryDataStoreFactory = EntryDataStoreFactory(
+ memoryDataStore: memoryEntryDataStore,
+ cloudDataStore: cloudEntryDataStore,
+ );
+
+ // Repositories
+
+ _appPrefsRepository = ImplAppPrefsRepository(
+ factory: appPrefsDataStoreFactory,
+ );
+
+ _authInfoRepository = ImplAuthInfoRepository(
+ factory: authInfoDataStoreFactory,
+ );
+
+ _identityRepository = ImplIdentityRepository(
+ factory: identityDataStoreFactory,
+ );
+
+ _userRepository = ImplUserRepository(
+ factory: userDataStoreFactory,
+ );
+
+ _profileRepository = ImplProfileRepository(
+ factory: profileDataStoreFactory,
+ );
+
+ _partRepository = ImplPartRepository(
+ factory: partDataStoreFactory,
+ );
+
+ _groupRepository = ImplGroupRepository(
+ factory: groupDataStoreFactory,
+ );
+
+ _entryRepository = ImplEntryRepository(
+ factory: entryDataStoreFactory,
+ );
+
+ // Return config
+
+ yield ConfigLoaded(
+ cvAuthService: _cvAuthService,
+ authInfoRepository: _authInfoRepository,
+ appPrefsRepository: _appPrefsRepository,
+ identityRepository: _identityRepository,
+ userRepository: _userRepository,
+ profileRepository: _profileRepository,
+ partRepository: _partRepository,
+ groupRepository: _groupRepository,
+ entryRepository: _entryRepository,
+ );
+ } catch (error, stacktrace) {
+ Logger.error('${error.runtimeType}', stackTrace: stacktrace);
+ yield ConfigFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/configuration/configuration_event.dart b/lib/src/bloc/blocs/configuration/configuration_event.dart
new file mode 100644
index 0000000..ae9b059
--- /dev/null
+++ b/lib/src/bloc/blocs/configuration/configuration_event.dart
@@ -0,0 +1,11 @@
+import 'package:equatable/equatable.dart';
+
+/// [ConfigurationEvent] that must be dispatch to [AppBloc]
+abstract class ConfigurationEvent extends Equatable {
+ ConfigurationEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class AppLaunched extends ConfigurationEvent {}
diff --git a/lib/src/bloc/blocs/configuration/configuration_state.dart b/lib/src/bloc/blocs/configuration/configuration_state.dart
new file mode 100644
index 0000000..cefdd55
--- /dev/null
+++ b/lib/src/bloc/blocs/configuration/configuration_state.dart
@@ -0,0 +1,70 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class ConfigurationState extends Equatable {
+ ConfigurationState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class ConfigLoading extends ConfigurationState {}
+
+class ConfigLoaded extends ConfigurationState {
+ final CVAuthService cvAuthService;
+
+ final AuthInfoRepository authInfoRepository;
+ final AppPrefsRepository appPrefsRepository;
+
+ final IdentityRepository identityRepository;
+ final UserRepository userRepository;
+ final ProfileRepository profileRepository;
+ final PartRepository partRepository;
+ final GroupRepository groupRepository;
+ final EntryRepository entryRepository;
+
+ ConfigLoaded({
+ @required this.cvAuthService,
+ @required this.authInfoRepository,
+ @required this.appPrefsRepository,
+ @required this.identityRepository,
+ @required this.userRepository,
+ @required this.profileRepository,
+ @required this.partRepository,
+ @required this.groupRepository,
+ @required this.entryRepository,
+ }) : assert(cvAuthService != null, 'No $CVAuthService given'),
+ assert(authInfoRepository != null, 'No $AuthInfoRepository given'),
+ assert(appPrefsRepository != null, 'No $AppPrefsRepository given'),
+ assert(identityRepository != null, 'No $IdentityRepository given'),
+ assert(userRepository != null, 'No $UserRepository given'),
+ assert(profileRepository != null, 'No $ProfileRepository given'),
+ assert(partRepository != null, 'No $PartRepository given'),
+ assert(groupRepository != null, 'No $GroupRepository given'),
+ assert(entryRepository != null, 'No $EntryRepository given'),
+ super([
+ cvAuthService,
+ authInfoRepository,
+ appPrefsRepository,
+ identityRepository,
+ userRepository,
+ profileRepository,
+ partRepository,
+ groupRepository,
+ entryRepository,
+ ]);
+}
+
+class ConfigFailure extends ConfigurationState {
+ final dynamic error;
+
+ ConfigFailure({@required this.error})
+ : assert(error != null),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/element/element.dart b/lib/src/bloc/blocs/element/element.dart
new file mode 100644
index 0000000..a927d20
--- /dev/null
+++ b/lib/src/bloc/blocs/element/element.dart
@@ -0,0 +1,3 @@
+export './element_bloc.dart';
+export './element_event.dart';
+export './element_state.dart';
diff --git a/lib/src/bloc/blocs/element/element_bloc.dart b/lib/src/bloc/blocs/element/element_bloc.dart
new file mode 100644
index 0000000..10bad00
--- /dev/null
+++ b/lib/src/bloc/blocs/element/element_bloc.dart
@@ -0,0 +1,17 @@
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for profile elements
+abstract class ElementBloc
+ extends Bloc {
+ final String _tag = '$ElementBloc<$T,$R,$E,$S>';
+
+ final R repository;
+
+ T element;
+
+ ElementBloc({@required this.repository})
+ : assert(repository != null, 'No $R given'),
+ super();
+}
diff --git a/lib/src/bloc/blocs/element/element_event.dart b/lib/src/bloc/blocs/element/element_event.dart
new file mode 100644
index 0000000..ba5ee14
--- /dev/null
+++ b/lib/src/bloc/blocs/element/element_event.dart
@@ -0,0 +1,17 @@
+import 'package:social_cv_client_flutter/domain.dart';
+
+mixin ElementInitialized {
+ String elementId;
+ T element;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'id: $elementId, '
+ 'element: $element'
+ ' }';
+}
+
+mixin ElementRefresh {
+ @override
+ String toString() => '$runtimeType{}';
+}
diff --git a/lib/src/bloc/blocs/element/element_state.dart b/lib/src/bloc/blocs/element/element_state.dart
new file mode 100644
index 0000000..1824ab9
--- /dev/null
+++ b/lib/src/bloc/blocs/element/element_state.dart
@@ -0,0 +1,29 @@
+import 'package:social_cv_client_flutter/domain.dart';
+
+mixin ElementUninitialized {
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+mixin ElementLoading {
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+mixin ElementLoaded {
+ T element;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'element: $element'
+ ' }';
+}
+
+mixin ElementFailure {
+ dynamic error;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/element_list/element_list.dart b/lib/src/bloc/blocs/element_list/element_list.dart
new file mode 100644
index 0000000..f84b7db
--- /dev/null
+++ b/lib/src/bloc/blocs/element_list/element_list.dart
@@ -0,0 +1,3 @@
+export './element_list_bloc.dart';
+export './element_list_event.dart';
+export './element_list_state.dart';
diff --git a/lib/src/bloc/blocs/element_list/element_list_bloc.dart b/lib/src/bloc/blocs/element_list/element_list_bloc.dart
new file mode 100644
index 0000000..21c9f1c
--- /dev/null
+++ b/lib/src/bloc/blocs/element_list/element_list_bloc.dart
@@ -0,0 +1,25 @@
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// Business Logic Component for profile element list
+abstract class ElementListBloc
+ extends Bloc {
+ final String _tag = '$ElementListBloc<$T>';
+
+ final R repository;
+
+ String parentId;
+ List elements;
+ String ownerId;
+
+ Cursor cursor;
+
+ /// TODO: Add filter
+ /// TODO: Add sort
+
+ ElementListBloc({@required this.repository})
+ : assert(repository != null, 'No $R given'),
+ super();
+}
diff --git a/lib/src/bloc/blocs/element_list/element_list_event.dart b/lib/src/bloc/blocs/element_list/element_list_event.dart
new file mode 100644
index 0000000..c5ead90
--- /dev/null
+++ b/lib/src/bloc/blocs/element_list/element_list_event.dart
@@ -0,0 +1,29 @@
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+mixin ElementListInitialized {
+ String parentId;
+ String ownerId;
+ Cursor cursor;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parentId: $parentId, '
+ 'ownerId: $ownerId, '
+ 'cursor: $cursor'
+ ' }';
+}
+
+mixin ElementListRefresh {
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+mixin ElementListLoadMore {
+ Cursor cursor;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'cursor: $cursor'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/element_list/element_list_state.dart b/lib/src/bloc/blocs/element_list/element_list_state.dart
new file mode 100644
index 0000000..2e54afa
--- /dev/null
+++ b/lib/src/bloc/blocs/element_list/element_list_state.dart
@@ -0,0 +1,33 @@
+import 'package:social_cv_client_flutter/domain.dart';
+
+mixin ElementListUninitialized {
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+mixin ElementListLoading {
+ int count;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'count: $count'
+ ' }';
+}
+
+mixin ElementListLoaded {
+ List elements;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'elements: $elements'
+ ' }';
+}
+
+mixin ElementListFailure {
+ dynamic error;
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/entry/entry.dart b/lib/src/bloc/blocs/entry/entry.dart
new file mode 100644
index 0000000..929412b
--- /dev/null
+++ b/lib/src/bloc/blocs/entry/entry.dart
@@ -0,0 +1,3 @@
+export 'entry_bloc.dart';
+export 'entry_event.dart';
+export 'entry_state.dart';
diff --git a/lib/src/bloc/blocs/entry/entry_bloc.dart b/lib/src/bloc/blocs/entry/entry_bloc.dart
new file mode 100644
index 0000000..7e29088
--- /dev/null
+++ b/lib/src/bloc/blocs/entry/entry_bloc.dart
@@ -0,0 +1,81 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Entry
+class EntryBloc
+ extends ElementBloc {
+ final String _tag = '$EntryBloc';
+
+ EntryBloc({@required EntryRepository repository})
+ : super(repository: repository);
+
+ /// [_fallBackId] is used if [element] is never assigned and
+ /// an [EntryRefresh] is dispatched
+ String _fallBackId;
+
+ @override
+ EntryState get initialState => EntryUninitialized();
+
+ @override
+ Stream mapEventToState(EntryEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is EntryInitialized) {
+ yield* _mapInitializedEventToState(event);
+ } else if (event is EntryRefresh) {
+ yield* _mapRefreshEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [EntryInitialized] to [EntryState]
+ ///
+ /// ```dart
+ /// yield* _mapInitializedEventToState(event);
+ /// ```
+ Stream _mapInitializedEventToState(
+ EntryInitialized event) async* {
+ print('$_tag:_mapInitializedEventToState($event)');
+ try {
+ yield EntryLoading();
+
+ if (event.elementId != null) {
+ _fallBackId = event.elementId;
+ element = await repository.getById(event.elementId);
+ } else if (event.element != null) {
+ _fallBackId = event.element.id;
+ element = event.element;
+ }
+
+ yield EntryLoaded(entry: element);
+ } catch (error) {
+ yield EntryFailure(error: error);
+ }
+ }
+
+ /// Map [EntryRefresh] to [EntryState]
+ ///
+ /// ```dart
+ /// yield* _mapRefreshEventToState(event);
+ /// ```
+ Stream _mapRefreshEventToState(EntryRefresh event) async* {
+ print('$_tag:_mapRefreshEventToState($event)');
+ try {
+ yield EntryLoading();
+
+ element = await repository.getById(
+ element?.id ?? _fallBackId,
+ force: true,
+ );
+
+ _fallBackId = element.id;
+
+ yield EntryLoaded(entry: element);
+ } catch (error) {
+ yield EntryFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/entry/entry_event.dart b/lib/src/bloc/blocs/entry/entry_event.dart
new file mode 100644
index 0000000..a5608e1
--- /dev/null
+++ b/lib/src/bloc/blocs/entry/entry_event.dart
@@ -0,0 +1,35 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// [EntryEvent] that must be dispatch to [EntryBloc]
+abstract class EntryEvent extends Equatable {
+ EntryEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class EntryInitialized extends EntryEvent with ElementInitialized {
+ EntryInitialized({String entryId, EntryEntity entry})
+ : assert(
+ entryId != null && entry == null,
+ '$EntryInitialized must be created with an $EntryEntity or its ID',
+ ),
+ assert(
+ entryId == null && entry != null,
+ '$EntryInitialized must be created with an $EntryEntity or its ID',
+ ),
+ super([entryId, entry]) {
+ elementId = entryId;
+ element = entry;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'entryId: $elementId, '
+ 'element: $element'
+ ' }';
+}
+
+class EntryRefresh extends EntryEvent with ElementRefresh {}
diff --git a/lib/src/bloc/blocs/entry/entry_state.dart b/lib/src/bloc/blocs/entry/entry_state.dart
new file mode 100644
index 0000000..c19a5c1
--- /dev/null
+++ b/lib/src/bloc/blocs/entry/entry_state.dart
@@ -0,0 +1,42 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class EntryState extends Equatable {
+ EntryState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class EntryUninitialized extends EntryState
+ with ElementUninitialized {}
+
+class EntryLoading extends EntryState with ElementLoading {}
+
+class EntryLoaded extends EntryState with ElementLoaded {
+ EntryLoaded({EntryEntity entry}) : super([entry]) {
+ element = entry;
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ '
+ 'entry: $element'
+ ' }';
+ }
+}
+
+class EntryFailure extends EntryState with ElementFailure {
+ EntryFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/entry_list/entry_list.dart b/lib/src/bloc/blocs/entry_list/entry_list.dart
new file mode 100644
index 0000000..f229303
--- /dev/null
+++ b/lib/src/bloc/blocs/entry_list/entry_list.dart
@@ -0,0 +1,3 @@
+export './entry_list_bloc.dart';
+export './entry_list_event.dart';
+export './entry_list_state.dart';
diff --git a/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart b/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart
new file mode 100644
index 0000000..33c8f70
--- /dev/null
+++ b/lib/src/bloc/blocs/entry_list/entry_list_bloc.dart
@@ -0,0 +1,120 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// Business Logic Component for Entry list
+class EntryListBloc extends ElementListBloc {
+ final String _tag = '$EntryListBloc';
+
+ EntryListBloc({@required EntryRepository repository})
+ : super(repository: repository);
+
+ @override
+ EntryListState get initialState => EntryListUninitialized();
+
+ @override
+ Stream mapEventToState(EntryListEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is EntryListInitialized) {
+ yield* _mapEntryListInitializedEventToState(event);
+ } else if (event is EntryListRefresh) {
+ yield* _mapEntryListRefreshEventToState(event);
+ } else if (event is EntryListLoadMore) {
+ yield* _mapEntryListLoadMoreEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [EntryListInitialized] to [EntryListState]
+ ///
+ /// ```dart
+ /// yield* _mapEntryListInitializedEventToState(event);
+ /// ```
+ Stream _mapEntryListInitializedEventToState(
+ EntryListInitialized event) async* {
+ print('$_tag:_mapEntryListInitializedEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+
+ parentId = event.parentId;
+ ownerId = event.ownerId;
+ cursor = event.cursor;
+
+ elements = await _getEntries(cursor: event.cursor);
+
+ yield EntryListLoaded(entries: elements);
+ } catch (error) {
+ yield EntryListFailure(error: error);
+ }
+ }
+
+ /// Map [EntryListRefresh] to [EntryListState]
+ ///
+ /// ```dart
+ /// yield* _mapEntryListRefreshEventToState(event);
+ /// ```
+ Stream _mapEntryListRefreshEventToState(
+ EntryListRefresh event) async* {
+ print('$_tag:_mapEntryListRefreshEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+ elements = await _getEntries(cursor: cursor);
+ yield EntryListLoaded(entries: elements);
+ } catch (error) {
+ yield EntryListFailure(error: error);
+ }
+ }
+
+ /// Map [EntryListLoadMore] to [EntryListState]
+ ///
+ /// ```dart
+ /// yield* _mapEntryListRefreshEventToState(event);
+ /// ```
+ Stream _mapEntryListLoadMoreEventToState(
+ EntryListLoadMore event) async* {
+ print('$_tag:_mapEntryListLoadMoreEventToState($event)');
+ try {
+ /// TODO: Add load more indicator stream
+
+ final List entries = await _getEntries(
+ cursor: event.cursor.copyWith(offset: elements.length),
+ );
+
+ /// Append to elements
+ elements.addAll(entries);
+
+ /// Save cursor limit if use list refreshed
+ cursor = cursor.copyWith(limit: elements.length);
+
+ yield EntryListLoaded(entries: elements);
+ } catch (error) {
+ yield EntryListFailure(error: error);
+ }
+ }
+
+ FutureOr> _getEntries({@required Cursor cursor}) async {
+ print('$_tag:_getEntries({cursor: $cursor})');
+ if (parentId != null) {
+ return await repository.getEntriesFromGroup(
+ parentId,
+ cursor: cursor,
+ );
+ } else if (ownerId != null) {
+ return await repository.getEntriesFromUser(
+ ownerId,
+ cursor: cursor,
+ );
+ } else {
+ return await repository.getList(
+ cursor: cursor,
+ );
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/entry_list/entry_list_event.dart b/lib/src/bloc/blocs/entry_list/entry_list_event.dart
new file mode 100644
index 0000000..22e5e7e
--- /dev/null
+++ b/lib/src/bloc/blocs/entry_list/entry_list_event.dart
@@ -0,0 +1,57 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// [EntryListEvent] that must be dispatch to [EntryListBloc]
+abstract class EntryListEvent extends Equatable {
+ EntryListEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class EntryListInitialized extends EntryListEvent
+ with ElementListInitialized {
+ EntryListInitialized({
+ String parentGroupId,
+ String ownerId,
+ Cursor cursor,
+ }) : assert(
+ parentGroupId != null && ownerId == null,
+ '$EntryListInitialized must be created with a parentId or an ownerId',
+ ),
+ assert(
+ parentGroupId == null && ownerId != null,
+ '$EntryListInitialized must be created with a parentId or an ownerId',
+ ),
+ super([parentGroupId, ownerId]) {
+ this.parentId = parentGroupId;
+ this.ownerId = ownerId;
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parentId: $parentId, '
+ 'ownerId: $ownerId, '
+ 'cursor: $cursor'
+ ' }';
+}
+
+class EntryListRefresh extends EntryListEvent
+ with ElementListRefresh {}
+
+class EntryListLoadMore extends EntryListEvent
+ with ElementListLoadMore {
+ EntryListLoadMore({Cursor cursor})
+ : assert(cursor != null),
+ super([cursor]) {
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'cursor: $cursor'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/entry_list/entry_list_state.dart b/lib/src/bloc/blocs/entry_list/entry_list_state.dart
new file mode 100644
index 0000000..ffef042
--- /dev/null
+++ b/lib/src/bloc/blocs/entry_list/entry_list_state.dart
@@ -0,0 +1,52 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class EntryListState extends Equatable {
+ EntryListState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class EntryListUninitialized extends EntryListState
+ with ElementListUninitialized {}
+
+class EntryListLoading extends EntryListState
+ with ElementListLoading {
+ EntryListLoading({int count = 0}) : super([count]) {
+ this.count = count;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'count: $count'
+ ' }';
+}
+
+class EntryListLoaded extends EntryListState
+ with ElementListLoaded {
+ EntryListLoaded({@required List entries}) : super([entries]) {
+ elements = entries;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'entries: $elements'
+ ' }';
+}
+
+class EntryListFailure extends EntryListState
+ with ElementListFailure {
+ EntryListFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/group/group.dart b/lib/src/bloc/blocs/group/group.dart
new file mode 100644
index 0000000..19c3ef1
--- /dev/null
+++ b/lib/src/bloc/blocs/group/group.dart
@@ -0,0 +1,3 @@
+export 'group_bloc.dart';
+export 'group_event.dart';
+export 'group_state.dart';
diff --git a/lib/src/bloc/blocs/group/group_bloc.dart b/lib/src/bloc/blocs/group/group_bloc.dart
new file mode 100644
index 0000000..c49e91a
--- /dev/null
+++ b/lib/src/bloc/blocs/group/group_bloc.dart
@@ -0,0 +1,83 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Group
+class GroupBloc
+ extends ElementBloc {
+ final String _tag = '$GroupBloc';
+
+ GroupBloc({@required GroupRepository repository})
+ : super(repository: repository);
+
+ /// [_fallBackId] is used if [element] is never assigned and
+ /// an [GroupRefresh] is dispatched
+ String _fallBackId;
+
+ @override
+ GroupState get initialState => GroupUninitialized();
+
+ @override
+ Stream mapEventToState(GroupEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is GroupInitialized) {
+ yield* _mapInitializedEventToState(event);
+ } else if (event is GroupRefresh) {
+ yield* _mapRefreshEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ Stream _mapInitializedEventToState(
+ GroupInitialized event) async* {
+ print('$_tag:_mapInitializedEventToState($event)');
+ try {
+ yield GroupLoading();
+
+ if (event.elementId != null) {
+ _fallBackId = event.elementId;
+ element = await repository.getById(event.elementId);
+ } else if (event.element != null) {
+ _fallBackId = event.element.id;
+ element = event.element;
+ }
+
+ yield GroupLoaded(group: element);
+ } catch (error) {
+ yield GroupFailure(error: error);
+ }
+ }
+
+ /// Map [GroupRefresh] to [GroupState]
+ ///
+ /// ```dart
+ /// yield* _mapRefreshEventToState(event);
+ /// ```
+ Stream _mapRefreshEventToState(GroupRefresh event) async* {
+ print('$_tag:_mapRefreshEventToState($event)');
+ try {
+ yield GroupLoading();
+
+ element = await repository.getById(
+ element?.id ?? _fallBackId,
+ force: true,
+ );
+
+ _fallBackId = element.id;
+
+ yield GroupLoaded(group: element);
+ } catch (error) {
+ yield GroupFailure(error: error);
+ }
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ '
+ 'repository: $repository'
+ ' }';
+ }
+}
diff --git a/lib/src/bloc/blocs/group/group_event.dart b/lib/src/bloc/blocs/group/group_event.dart
new file mode 100644
index 0000000..21fe121
--- /dev/null
+++ b/lib/src/bloc/blocs/group/group_event.dart
@@ -0,0 +1,33 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// [GroupEvent] that must be dispatch to [GroupBloc]
+
+abstract class GroupEvent extends Equatable {
+ GroupEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class GroupInitialized extends GroupEvent with ElementInitialized {
+ GroupInitialized({String groupId, GroupEntity group})
+ : assert(
+ groupId != null && group == null,
+ '$GroupInitialized must be created with a $GroupEntity or its ID',
+ ),
+ assert(
+ groupId == null && group != null,
+ '$GroupInitialized must be created with a $GroupEntity or its ID',
+ ),
+ super([groupId, group]) {
+ this.elementId = groupId;
+ this.element = group;
+ }
+
+ @override
+ String toString() => '$runtimeType{ id: $elementId, element: $element }';
+}
+
+class GroupRefresh extends GroupEvent with ElementRefresh {}
diff --git a/lib/src/bloc/blocs/group/group_state.dart b/lib/src/bloc/blocs/group/group_state.dart
new file mode 100644
index 0000000..ba68ce5
--- /dev/null
+++ b/lib/src/bloc/blocs/group/group_state.dart
@@ -0,0 +1,42 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class GroupState extends Equatable {
+ GroupState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class GroupUninitialized extends GroupState
+ with ElementUninitialized {}
+
+class GroupLoading extends GroupState with ElementLoading {}
+
+class GroupLoaded extends GroupState with ElementLoaded {
+ GroupLoaded({GroupEntity group}) : super([group]) {
+ element = group;
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ '
+ 'group: $element'
+ ' }';
+ }
+}
+
+class GroupFailure extends GroupState with ElementFailure {
+ GroupFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType { '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/group_list/group_list.dart b/lib/src/bloc/blocs/group_list/group_list.dart
new file mode 100644
index 0000000..33f2978
--- /dev/null
+++ b/lib/src/bloc/blocs/group_list/group_list.dart
@@ -0,0 +1,3 @@
+export './group_list_bloc.dart';
+export './group_list_event.dart';
+export './group_list_state.dart';
diff --git a/lib/src/bloc/blocs/group_list/group_list_bloc.dart b/lib/src/bloc/blocs/group_list/group_list_bloc.dart
new file mode 100644
index 0000000..fdf1574
--- /dev/null
+++ b/lib/src/bloc/blocs/group_list/group_list_bloc.dart
@@ -0,0 +1,120 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// Business Logic Component for Group list
+class GroupListBloc extends ElementListBloc {
+ final String _tag = '$GroupListBloc';
+
+ GroupListBloc({@required GroupRepository repository})
+ : super(repository: repository);
+
+ @override
+ GroupListState get initialState => GroupListUninitialized();
+
+ @override
+ Stream mapEventToState(GroupListEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is GroupListInitialized) {
+ yield* _mapGroupListInitializedEventToState(event);
+ } else if (event is GroupListRefresh) {
+ yield* _mapGroupListRefreshEventToState(event);
+ } else if (event is GroupListLoadMore) {
+ yield* _mapGroupListLoadMoreEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [GroupListInitialized] to [GroupListState]
+ ///
+ /// ```dart
+ /// yield* _mapGroupListInitializedEventToState(event);
+ /// ```
+ Stream _mapGroupListInitializedEventToState(
+ GroupListInitialized event) async* {
+ print('$_tag:_mapGroupListInitializedEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+
+ parentId = event.parentId;
+ ownerId = event.ownerId;
+ cursor = event.cursor;
+
+ elements = await _getGroups(cursor: cursor);
+
+ yield GroupListLoaded(groups: elements);
+ } catch (error) {
+ yield GroupListFailure(error: error);
+ }
+ }
+
+ /// Map [GroupListRefresh] to [GroupListState]
+ ///
+ /// ```dart
+ /// yield* _mapGroupListRefreshEventToState(event);
+ /// ```
+ Stream _mapGroupListRefreshEventToState(
+ GroupListRefresh event) async* {
+ print('$_tag:_mapGroupListRefreshEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+ elements = await _getGroups(cursor: cursor);
+ yield GroupListLoaded(groups: elements);
+ } catch (error) {
+ yield GroupListFailure(error: error);
+ }
+ }
+
+ /// Map [GroupListLoadMore] to [GroupListState]
+ ///
+ /// ```dart
+ /// yield* _mapGroupListLoadMoreEventToState(event);
+ /// ```
+ Stream _mapGroupListLoadMoreEventToState(
+ GroupListLoadMore event) async* {
+ print('$_tag:_mapGroupListLoadMoreEventToState($event)');
+ try {
+ /// TODO: Add load more indicator stream
+
+ final List groups = await _getGroups(
+ cursor: event.cursor.copyWith(offset: elements.length),
+ );
+
+ /// Append to elements
+ elements.addAll(groups);
+
+ /// Save cursor limit if use list refreshed
+ cursor = cursor.copyWith(limit: elements.length);
+
+ yield GroupListLoaded(groups: elements);
+ } catch (error) {
+ yield GroupListFailure(error: error);
+ }
+ }
+
+ FutureOr> _getGroups({@required Cursor cursor}) async {
+ print('$_tag:_getGroups({cursor: $cursor})');
+ if (parentId != null) {
+ return await repository.getGroupsFromPart(
+ parentId,
+ cursor: cursor,
+ );
+ } else if (ownerId != null) {
+ return await repository.getGroupsFromUser(
+ ownerId,
+ cursor: cursor,
+ );
+ } else {
+ return await repository.getList(
+ cursor: cursor,
+ );
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/group_list/group_list_event.dart b/lib/src/bloc/blocs/group_list/group_list_event.dart
new file mode 100644
index 0000000..bce5e24
--- /dev/null
+++ b/lib/src/bloc/blocs/group_list/group_list_event.dart
@@ -0,0 +1,57 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// [GroupListEvent] that must be dispatch to [GroupListBloc]
+abstract class GroupListEvent extends Equatable {
+ GroupListEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class GroupListInitialized extends GroupListEvent
+ with ElementListInitialized {
+ GroupListInitialized({
+ String parentPartId,
+ String ownerId,
+ Cursor cursor,
+ }) : assert(
+ parentPartId != null && ownerId == null,
+ '$GroupListInitialized must be created with a parentId or an ownerId',
+ ),
+ assert(
+ parentPartId == null && ownerId != null,
+ '$GroupListInitialized must be created with a parentId or an ownerId',
+ ),
+ super([parentPartId, ownerId]) {
+ this.parentId = parentPartId;
+ this.ownerId = ownerId;
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parentPartId: $parentId, '
+ 'ownerId: $ownerId, '
+ 'cursor: $cursor'
+ ' }';
+}
+
+class GroupListRefresh extends GroupListEvent
+ with ElementListRefresh {}
+
+class GroupListLoadMore extends GroupListEvent
+ with ElementListLoadMore {
+ GroupListLoadMore({Cursor cursor})
+ : assert(cursor != null),
+ super([cursor]) {
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'cursor: $cursor'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/group_list/group_list_state.dart b/lib/src/bloc/blocs/group_list/group_list_state.dart
new file mode 100644
index 0000000..a0b2875
--- /dev/null
+++ b/lib/src/bloc/blocs/group_list/group_list_state.dart
@@ -0,0 +1,52 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class GroupListState extends Equatable {
+ GroupListState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class GroupListUninitialized extends GroupListState
+ with ElementListUninitialized {}
+
+class GroupListLoading extends GroupListState
+ with ElementListLoading {
+ GroupListLoading({int count = 0}) : super([count]) {
+ this.count = count;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'count: $count'
+ ' }';
+}
+
+class GroupListLoaded extends GroupListState
+ with ElementListLoaded {
+ GroupListLoaded({@required List groups}) : super([groups]) {
+ elements = groups;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'groups: $elements'
+ ' }';
+}
+
+class GroupListFailure extends GroupListState
+ with ElementListFailure {
+ GroupListFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/identity/identity.dart b/lib/src/bloc/blocs/identity/identity.dart
new file mode 100644
index 0000000..a4fe72d
--- /dev/null
+++ b/lib/src/bloc/blocs/identity/identity.dart
@@ -0,0 +1,3 @@
+export './identity_bloc.dart';
+export './identity_event.dart';
+export './identity_state.dart';
diff --git a/lib/src/bloc/blocs/identity/identity_bloc.dart b/lib/src/bloc/blocs/identity/identity_bloc.dart
new file mode 100644
index 0000000..8245775
--- /dev/null
+++ b/lib/src/bloc/blocs/identity/identity_bloc.dart
@@ -0,0 +1,66 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Account
+class IdentityBloc extends Bloc {
+ final String _tag = '$IdentityBloc';
+
+ final IdentityRepository identityRepo;
+ final AuthenticationBloc authBloc;
+ StreamSubscription authBlocSubscription;
+
+ IdentityBloc({
+ @required this.identityRepo,
+ @required this.authBloc,
+ }) : assert(identityRepo != null, 'No $IdentityRepository given'),
+ super() {
+ authBlocSubscription = authBloc.state.listen((state) {
+ if (state is AuthenticationAuthenticated) {
+ dispatch(IdentityRefresh());
+ }
+ });
+ }
+
+ @override
+ void dispose() {
+ authBlocSubscription.cancel();
+ super.dispose();
+ }
+
+ @override
+ IdentityState get initialState => IdentityUninitialized();
+
+ @override
+ Stream mapEventToState(IdentityEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+
+ if (event is IdentityRefresh) {
+ yield* _mapAccountRefreshToState(event);
+ }
+ }
+
+ /// -----------------------------------------------------------------------
+ /// All Event map to State
+ /// -----------------------------------------------------------------------
+
+ /// Map [IdentityRefresh] to [IdentityState]
+ ///
+ /// ```dart
+ /// yield* _mapAccountRefreshToState(event);
+ /// ```
+ Stream _mapAccountRefreshToState(
+ IdentityRefresh event) async* {
+ try {
+ yield IdentityLoading();
+ final userModel = await identityRepo.getIdentity();
+ yield IdentityLoaded(user: userModel);
+ } catch (error) {
+ print('$_tag:_mapAccountRefreshToState -> ${error.runtimeType}');
+ yield IdentityFailed(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/identity/identity_event.dart b/lib/src/bloc/blocs/identity/identity_event.dart
new file mode 100644
index 0000000..c30d31f
--- /dev/null
+++ b/lib/src/bloc/blocs/identity/identity_event.dart
@@ -0,0 +1,11 @@
+import 'package:equatable/equatable.dart';
+
+/// [IdentityEvent] that must be dispatch to [AccountBloc]
+abstract class IdentityEvent extends Equatable {
+ IdentityEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class IdentityRefresh extends IdentityEvent {}
diff --git a/lib/src/bloc/blocs/identity/identity_state.dart b/lib/src/bloc/blocs/identity/identity_state.dart
new file mode 100644
index 0000000..55ee837
--- /dev/null
+++ b/lib/src/bloc/blocs/identity/identity_state.dart
@@ -0,0 +1,40 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class IdentityState extends Equatable {
+ IdentityState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class IdentityUninitialized extends IdentityState {}
+
+class IdentityLoading extends IdentityState {}
+
+class IdentityLoaded extends IdentityState {
+ final UserEntity user;
+
+ IdentityLoaded({
+ @required this.user,
+ }) : super([user]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'userModel: $user'
+ ' }';
+}
+
+class IdentityFailed extends IdentityState {
+ final dynamic error;
+
+ IdentityFailed({@required this.error})
+ : assert(error != null, 'No error given'),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/login/login.dart b/lib/src/bloc/blocs/login/login.dart
new file mode 100644
index 0000000..d3a5e51
--- /dev/null
+++ b/lib/src/bloc/blocs/login/login.dart
@@ -0,0 +1,3 @@
+export './login_bloc.dart';
+export './login_event.dart';
+export './login_state.dart';
diff --git a/lib/src/bloc/blocs/login/login_bloc.dart b/lib/src/bloc/blocs/login/login_bloc.dart
new file mode 100644
index 0000000..d71f230
--- /dev/null
+++ b/lib/src/bloc/blocs/login/login_bloc.dart
@@ -0,0 +1,60 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Login
+class LoginBloc extends Bloc {
+ final String _tag = '$LoginBloc';
+
+ final CVAuthService cvAuthService;
+
+ LoginBloc({
+ @required this.cvAuthService,
+ }) : assert(cvAuthService != null, 'No $CVAuthService given'),
+ super();
+
+ @override
+ LoginState get initialState => LoginInitial();
+
+ @override
+ Stream mapEventToState(LoginEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is LoginButtonPressed) {
+ yield* _mapLoginButtonPressedToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [LoginButtonPressed] to [LoginState]
+ ///
+ /// ```dart
+ /// yield* _mapLoginButtonPressedToState(event);
+ /// ```
+ Stream _mapLoginButtonPressedToState(
+ LoginButtonPressed event) async* {
+ try {
+ if (event is LoginButtonPressed) {
+ yield LoginLoading();
+
+ final auth = await cvAuthService.authenticate(
+ email: event.email,
+ password: event.password,
+ );
+
+ yield LoginSucceed(
+ accessToken: auth.accessToken,
+ accessTokenExpiration: auth.accessTokenExpiration,
+ refreshToken: auth.refreshToken,
+ );
+ }
+ } catch (error) {
+ yield LoginFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/login/login_event.dart b/lib/src/bloc/blocs/login/login_event.dart
new file mode 100644
index 0000000..636db38
--- /dev/null
+++ b/lib/src/bloc/blocs/login/login_event.dart
@@ -0,0 +1,26 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+/// [LoginEvent] that must be dispatch to [LoginBloc]
+abstract class LoginEvent extends Equatable {
+ LoginEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class LoginButtonPressed extends LoginEvent {
+ final String email;
+ final String password;
+
+ LoginButtonPressed({
+ @required this.email,
+ @required this.password,
+ }) : super([email, password]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'username: $email, '
+ 'password: HIDDEN'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/login/login_state.dart b/lib/src/bloc/blocs/login/login_state.dart
new file mode 100644
index 0000000..52377a2
--- /dev/null
+++ b/lib/src/bloc/blocs/login/login_state.dart
@@ -0,0 +1,45 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+abstract class LoginState extends Equatable {
+ LoginState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class LoginInitial extends LoginState {}
+
+class LoginLoading extends LoginState {}
+
+class LoginFailure extends LoginState {
+ final dynamic error;
+
+ LoginFailure({@required this.error})
+ : assert(error != null, 'No error given'),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
+
+class LoginSucceed extends LoginState {
+ final String accessToken;
+ final DateTime accessTokenExpiration;
+ final String refreshToken;
+
+ LoginSucceed({
+ @required this.accessToken,
+ @required this.accessTokenExpiration,
+ @required this.refreshToken,
+ }) : super([accessToken, accessTokenExpiration, refreshToken]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'accessToken: $accessToken, '
+ 'accessToken: $accessTokenExpiration, '
+ 'refreshToken: $refreshToken '
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/part/part.dart b/lib/src/bloc/blocs/part/part.dart
new file mode 100644
index 0000000..ebfbda0
--- /dev/null
+++ b/lib/src/bloc/blocs/part/part.dart
@@ -0,0 +1,3 @@
+export 'part_bloc.dart';
+export 'part_event.dart';
+export 'part_state.dart';
diff --git a/lib/src/bloc/blocs/part/part_bloc.dart b/lib/src/bloc/blocs/part/part_bloc.dart
new file mode 100644
index 0000000..0a3e08c
--- /dev/null
+++ b/lib/src/bloc/blocs/part/part_bloc.dart
@@ -0,0 +1,80 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Part
+class PartBloc
+ extends ElementBloc {
+ final String _tag = '$PartBloc';
+
+ PartBloc({@required PartRepository repository})
+ : super(repository: repository);
+
+ /// [_fallBackId] is used if [element] is never assigned and
+ /// an [PartRefresh] is dispatched
+ String _fallBackId;
+
+ @override
+ PartState get initialState => PartUninitialized();
+
+ @override
+ Stream mapEventToState(PartEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is PartInitialized) {
+ yield* _mapInitializedEventToState(event);
+ } else if (event is PartRefresh) {
+ yield* _mapRefreshEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [PartInitialized] to [PartState]
+ ///
+ /// ```dart
+ /// yield* _mapInitializedEventToState(event);
+ /// ```
+ Stream _mapInitializedEventToState(PartInitialized event) async* {
+ print('$_tag:_mapInitializedEventToState($event)');
+ try {
+ yield PartLoading();
+
+ if (event.elementId != null) {
+ _fallBackId = event.elementId;
+ element = await repository.getById(event.elementId);
+ } else if (event.element != null) {
+ _fallBackId = event.element.id;
+ element = event.element;
+ }
+
+ yield PartLoaded(part: element);
+ } catch (error) {
+ yield PartFailure(error: error);
+ }
+ }
+
+ /// Map [PartRefresh] to [PartState]
+ ///
+ /// ```dart
+ /// yield* _mapRefreshEventToState(event);
+ /// ```
+ Stream _mapRefreshEventToState(PartRefresh event) async* {
+ print('$_tag:_mapRefreshEventToState($event)');
+ try {
+ yield PartLoading();
+
+ element = await repository.getById(
+ element?.id ?? _fallBackId,
+ force: true,
+ );
+
+ _fallBackId = element.id;
+
+ yield PartLoaded(part: element);
+ } catch (error) {
+ yield PartFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/part/part_event.dart b/lib/src/bloc/blocs/part/part_event.dart
new file mode 100644
index 0000000..1173417
--- /dev/null
+++ b/lib/src/bloc/blocs/part/part_event.dart
@@ -0,0 +1,34 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class PartEvent extends Equatable {
+ PartEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class PartInitialized extends PartEvent with ElementInitialized {
+ PartInitialized({String partId, PartEntity part})
+ : assert(
+ partId != null && part == null,
+ '$PartInitialized must be created with a $PartEntity or its ID',
+ ),
+ assert(
+ partId == null && part != null,
+ '$PartInitialized must be created with a $PartEntity or its ID',
+ ),
+ super([partId, part]) {
+ elementId = partId;
+ element = part;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'id: $elementId, '
+ 'part: $element'
+ ' }';
+}
+
+class PartRefresh extends PartEvent with ElementRefresh {}
diff --git a/lib/src/bloc/blocs/part/part_state.dart b/lib/src/bloc/blocs/part/part_state.dart
new file mode 100644
index 0000000..d14f36c
--- /dev/null
+++ b/lib/src/bloc/blocs/part/part_state.dart
@@ -0,0 +1,40 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class PartState extends Equatable {
+ PartState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class PartUninitialized extends PartState
+ with ElementUninitialized {}
+
+class PartLoading extends PartState with ElementLoading {}
+
+class PartLoaded extends PartState with ElementLoaded {
+ PartLoaded({PartEntity part}) : super([part]) {
+ element = part;
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ part: $element }';
+ }
+}
+
+class PartFailure extends PartState with ElementFailure {
+ PartFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/part_list/part_list.dart b/lib/src/bloc/blocs/part_list/part_list.dart
new file mode 100644
index 0000000..85aa6b1
--- /dev/null
+++ b/lib/src/bloc/blocs/part_list/part_list.dart
@@ -0,0 +1,3 @@
+export './part_list_bloc.dart';
+export './part_list_event.dart';
+export './part_list_state.dart';
diff --git a/lib/src/bloc/blocs/part_list/part_list_bloc.dart b/lib/src/bloc/blocs/part_list/part_list_bloc.dart
new file mode 100644
index 0000000..7d2a5a8
--- /dev/null
+++ b/lib/src/bloc/blocs/part_list/part_list_bloc.dart
@@ -0,0 +1,110 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// Business Logic Component for Part list
+class PartListBloc extends ElementListBloc {
+ final String _tag = '$PartListBloc';
+
+ PartListBloc({@required PartRepository repository})
+ : super(repository: repository);
+
+ @override
+ PartListState get initialState => PartListUninitialized();
+
+ @override
+ Stream mapEventToState(PartListEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is PartListInitialized) {
+ yield* _mapPartListInitializedEventToState(event);
+ } else if (event is PartListRefresh) {
+ yield* _mapPartListRefreshEventToState(event);
+ } else if (event is PartListLoadMore) {
+ yield* _mapPartListLoadMoreEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ Stream _mapPartListInitializedEventToState(
+ PartListInitialized event) async* {
+ print('$_tag:_mapPartListInitializedEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+
+ parentId = event.parentId;
+ ownerId = event.ownerId;
+ cursor = event.cursor;
+
+ elements = await _getParts(cursor: cursor);
+
+ yield PartListLoaded(parts: elements);
+ } catch (error) {
+ yield PartListFailure(error: error);
+ }
+ }
+
+ Stream _mapPartListRefreshEventToState(
+ PartListRefresh event) async* {
+ print('$_tag:_mapPartListRefreshEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+ elements = await _getParts(cursor: cursor);
+ yield PartListLoaded(parts: elements);
+ } catch (error) {
+ yield PartListFailure(error: error);
+ }
+ }
+
+ /// Map [PartListLoadMore] to [PartListState]
+ ///
+ /// ```dart
+ /// yield* _mapPartListLoadMoreEventToState(event);
+ /// ```
+ Stream _mapPartListLoadMoreEventToState(
+ PartListLoadMore event) async* {
+ print('$_tag:_mapPartListLoadMoreEventToState($event)');
+ try {
+ /// TODO: Add load more indicator stream
+
+ List parts = await _getParts(
+ cursor: event.cursor.copyWith(offset: elements.length),
+ );
+
+ /// Append to elements
+ elements.addAll(parts);
+
+ /// Save cursor limit if use list refreshed
+ cursor = cursor.copyWith(limit: elements.length);
+
+ yield PartListLoaded(parts: elements);
+ } catch (error) {
+ yield PartListFailure(error: error);
+ }
+ }
+
+ FutureOr> _getParts({@required Cursor cursor}) async {
+ print('$_tag:_getParts({cursor: $cursor})');
+ if (parentId != null) {
+ return await repository.getPartsFromProfile(
+ parentId,
+ cursor: cursor,
+ );
+ } else if (ownerId != null) {
+ return await repository.getPartsFromUser(
+ ownerId,
+ cursor: cursor,
+ );
+ } else {
+ return await repository.getList(
+ cursor: cursor,
+ );
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/part_list/part_list_event.dart b/lib/src/bloc/blocs/part_list/part_list_event.dart
new file mode 100644
index 0000000..8f69057
--- /dev/null
+++ b/lib/src/bloc/blocs/part_list/part_list_event.dart
@@ -0,0 +1,57 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// [PartListEvent] that must be dispatch to [PartListBloc]
+abstract class PartListEvent extends Equatable {
+ PartListEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class PartListInitialized extends PartListEvent
+ with ElementListInitialized {
+ PartListInitialized({
+ String parentProfileId,
+ String ownerId,
+ Cursor cursor,
+ }) : assert(
+ parentProfileId != null && ownerId == null,
+ '$PartListInitialized must be created with a parentId or an ownerId',
+ ),
+ assert(
+ parentProfileId == null && ownerId != null,
+ '$PartListInitialized must be created with a parentId or an ownerId',
+ ),
+ super([parentProfileId, ownerId]) {
+ this.parentId = parentProfileId;
+ this.ownerId = ownerId;
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parentProfileId: $parentId, '
+ 'ownerId: $ownerId, '
+ 'cursor: $cursor'
+ ' }';
+}
+
+class PartListRefresh extends PartListEvent
+ with ElementListRefresh {}
+
+class PartListLoadMore extends PartListEvent
+ with ElementListLoadMore {
+ PartListLoadMore({Cursor cursor})
+ : assert(cursor != null),
+ super([cursor]) {
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'cursor: $cursor'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/part_list/part_list_state.dart b/lib/src/bloc/blocs/part_list/part_list_state.dart
new file mode 100644
index 0000000..3ffd7a1
--- /dev/null
+++ b/lib/src/bloc/blocs/part_list/part_list_state.dart
@@ -0,0 +1,51 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class PartListState extends Equatable {
+ PartListState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class PartListUninitialized extends PartListState
+ with ElementListUninitialized {}
+
+class PartListLoading extends PartListState
+ with ElementListLoading {
+ PartListLoading({int count = 0}) : super([count]) {
+ this.count = count;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'count: $count'
+ ' }';
+}
+
+class PartListLoaded extends PartListState with ElementListLoaded {
+ PartListLoaded({@required List parts}) : super([parts]) {
+ elements = parts;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parts: $elements'
+ ' }';
+}
+
+class PartListFailure extends PartListState
+ with ElementListFailure {
+ PartListFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/profile/profile.dart b/lib/src/bloc/blocs/profile/profile.dart
new file mode 100644
index 0000000..a642f12
--- /dev/null
+++ b/lib/src/bloc/blocs/profile/profile.dart
@@ -0,0 +1,3 @@
+export 'profile_bloc.dart';
+export 'profile_event.dart';
+export 'profile_state.dart';
diff --git a/lib/src/bloc/blocs/profile/profile_bloc.dart b/lib/src/bloc/blocs/profile/profile_bloc.dart
new file mode 100644
index 0000000..2306284
--- /dev/null
+++ b/lib/src/bloc/blocs/profile/profile_bloc.dart
@@ -0,0 +1,81 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for Profile
+class ProfileBloc extends ElementBloc {
+ final String _tag = '$ProfileBloc';
+
+ ProfileBloc({@required ProfileRepository repository})
+ : super(repository: repository);
+
+ /// [_fallBackId] is used if [element] is never assigned and
+ /// an [ProfileRefresh] is dispatched
+ String _fallBackId;
+
+ @override
+ ProfileState get initialState => ProfileUninitialized();
+
+ @override
+ Stream mapEventToState(ProfileEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is ProfileInitialized) {
+ yield* _mapInitializedEventToState(event);
+ } else if (event is ProfileRefresh) {
+ yield* _mapRefreshEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [ProfileInitialized] to [ProfileState]
+ ///
+ /// ```dart
+ /// yield* _mapInitializedEventToState(event);
+ /// ```
+ Stream _mapInitializedEventToState(
+ ProfileInitialized event) async* {
+ print('$_tag:_mapInitializedEventToState($event)');
+ try {
+ yield ProfileLoading();
+
+ if (event.elementId != null) {
+ _fallBackId = event.elementId;
+ element = await await repository.getById(event.elementId);
+ } else if (event.element != null) {
+ _fallBackId = event.element.id;
+ element = event.element;
+ }
+
+ yield ProfileLoaded(profile: element);
+ } catch (error) {
+ yield ProfileFailure(error: error);
+ }
+ }
+
+ /// Map [ProfileRefresh] to [ProfileState]
+ ///
+ /// ```dart
+ /// yield* _mapRefreshEventToState(event);
+ /// ```
+ Stream _mapRefreshEventToState(ProfileRefresh event) async* {
+ print('$_tag:_mapRefreshEventToState($event)');
+ try {
+ yield ProfileLoading();
+
+ element = await repository.getById(
+ element?.id ?? _fallBackId,
+ force: true,
+ );
+
+ _fallBackId = element.id;
+
+ yield ProfileLoaded(profile: element);
+ } catch (error) {
+ yield ProfileFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/profile/profile_event.dart b/lib/src/bloc/blocs/profile/profile_event.dart
new file mode 100644
index 0000000..0e58fb0
--- /dev/null
+++ b/lib/src/bloc/blocs/profile/profile_event.dart
@@ -0,0 +1,37 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// [ProfileEvent] that must be dispatch to [ProfileBloc]
+
+abstract class ProfileEvent extends Equatable {
+ ProfileEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class ProfileInitialized extends ProfileEvent
+ with ElementInitialized {
+ ProfileInitialized({String profileId, ProfileEntity profile})
+ : assert(
+ profileId != null && profile == null,
+ '$ProfileInitialized must be created with an $ProfileEntity or its ID',
+ ),
+ assert(
+ profileId == null && profile != null,
+ '$ProfileInitialized must be created with an $ProfileEntity or its ID',
+ ),
+ super([profileId, profile]) {
+ this.elementId = profileId;
+ this.element = profile;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'id: $elementId, '
+ 'element: $element'
+ ' }';
+}
+
+class ProfileRefresh extends ProfileEvent with ElementRefresh {}
diff --git a/lib/src/bloc/blocs/profile/profile_state.dart b/lib/src/bloc/blocs/profile/profile_state.dart
new file mode 100644
index 0000000..8c6cecc
--- /dev/null
+++ b/lib/src/bloc/blocs/profile/profile_state.dart
@@ -0,0 +1,42 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class ProfileState extends Equatable {
+ ProfileState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class ProfileUninitialized extends ProfileState
+ with ElementUninitialized {}
+
+class ProfileLoading extends ProfileState with ElementLoading {}
+
+class ProfileLoaded extends ProfileState with ElementLoaded {
+ ProfileLoaded({ProfileEntity profile}) : super([profile]) {
+ element = profile;
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ '
+ 'element: $element'
+ ' }';
+ }
+}
+
+class ProfileFailure extends ProfileState with ElementFailure {
+ ProfileFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/profile_list/profile_list.dart b/lib/src/bloc/blocs/profile_list/profile_list.dart
new file mode 100644
index 0000000..0d7c3b8
--- /dev/null
+++ b/lib/src/bloc/blocs/profile_list/profile_list.dart
@@ -0,0 +1,3 @@
+export './profile_list_bloc.dart';
+export './profile_list_event.dart';
+export './profile_list_state.dart';
diff --git a/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart b/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart
new file mode 100644
index 0000000..a9f0aff
--- /dev/null
+++ b/lib/src/bloc/blocs/profile_list/profile_list_bloc.dart
@@ -0,0 +1,120 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// Business Logic Component for Profile list
+class ProfileListBloc extends ElementListBloc {
+ final String _tag = '$ProfileListBloc';
+
+ ProfileListBloc({@required ProfileRepository repository})
+ : super(repository: repository);
+
+ @override
+ ProfileListState get initialState => ProfileListUninitialized();
+
+ @override
+ Stream mapEventToState(ProfileListEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is ProfileListInitialized) {
+ yield* _mapProfileListInitializedEventToState(event);
+ } else if (event is ProfileListRefresh) {
+ yield* _mapProfileListRefreshEventToState(event);
+ } else if (event is ProfileListLoadMore) {
+ yield* _mapProfileListLoadMoreEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [ProfileListInitialized] to [ProfileListState]
+ ///
+ /// ```dart
+ /// yield* _mapProfileListInitializedEventToState(event);
+ /// ```
+ Stream _mapProfileListInitializedEventToState(
+ ProfileListInitialized event) async* {
+ print('$_tag:_mapProfileListInitializedEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+
+ parentId = event.parentId;
+ ownerId = event.ownerId;
+ cursor = event.cursor;
+
+ elements = await _getProfiles(cursor: cursor);
+
+ yield ProfileListLoaded(profiles: elements);
+ } catch (error) {
+ yield ProfileListFailure(error: error);
+ }
+ }
+
+ /// Map [ProfileListRefresh] to [ProfileListState]
+ ///
+ /// ```dart
+ /// yield* _mapProfileListRefreshEventToState(event);
+ /// ```
+ Stream _mapProfileListRefreshEventToState(
+ ProfileListRefresh event) async* {
+ print('$_tag:_mapProfileListRefreshEventToState($event)');
+ try {
+ /// TODO: Add refresh indicator stream
+ elements = await _getProfiles(cursor: cursor);
+ yield ProfileListLoaded(profiles: elements);
+ } catch (error) {
+ yield ProfileListFailure(error: error);
+ }
+ }
+
+ /// Map [ProfileListLoadMore] to [ProfileListState]
+ ///
+ /// ```dart
+ /// yield* _mapProfileListLoadMoreEventToState(event);
+ /// ```
+ Stream _mapProfileListLoadMoreEventToState(
+ ProfileListLoadMore event) async* {
+ print('$_tag:_mapProfileListLoadMoreEventToState($event)');
+ try {
+ /// TODO: Add load more indicator stream
+
+ final List profiles = await _getProfiles(
+ cursor: event.cursor.copyWith(offset: elements.length),
+ );
+
+ /// Append to elements
+ elements.addAll(profiles);
+
+ /// Save cursor limit if use list refreshed
+ cursor = cursor.copyWith(limit: elements.length);
+
+ yield ProfileListLoaded(profiles: elements);
+ } catch (error) {
+ yield ProfileListFailure(error: error);
+ }
+ }
+
+ FutureOr> _getProfiles({@required Cursor cursor}) async {
+ print('$_tag:_getProfiles({cursor: $cursor})');
+ if (parentId != null) {
+ return await repository.getProfilesFromUser(
+ parentId,
+ cursor: cursor,
+ );
+ } else if (ownerId != null) {
+ return await repository.getProfilesFromUser(
+ ownerId,
+ cursor: cursor,
+ );
+ } else {
+ return await repository.getList(
+ cursor: cursor,
+ );
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/profile_list/profile_list_event.dart b/lib/src/bloc/blocs/profile_list/profile_list_event.dart
new file mode 100644
index 0000000..0ca2194
--- /dev/null
+++ b/lib/src/bloc/blocs/profile_list/profile_list_event.dart
@@ -0,0 +1,57 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/presentation.dart';
+
+/// [ProfileListEvent] that must be dispatch to [ProfileListBloc]
+abstract class ProfileListEvent extends Equatable {
+ ProfileListEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class ProfileListInitialized extends ProfileListEvent
+ with ElementListInitialized {
+ ProfileListInitialized({
+ String parentUserId,
+ String ownerId,
+ Cursor cursor,
+ }) : assert(
+ parentUserId != null && ownerId == null,
+ '$ProfileListInitialized must be created with a parentId or an ownerId',
+ ),
+ assert(
+ parentUserId == null && ownerId != null,
+ '$ProfileListInitialized must be created with a parentId or an ownerId',
+ ),
+ super([parentUserId, ownerId]) {
+ this.parentId = parentUserId;
+ this.ownerId = ownerId;
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'parentUserId: $parentId, '
+ 'ownerId: $ownerId, '
+ 'cursor: $cursor'
+ ' }';
+}
+
+class ProfileListRefresh extends ProfileListEvent
+ with ElementListRefresh {}
+
+class ProfileListLoadMore extends ProfileListEvent
+ with ElementListLoadMore {
+ ProfileListLoadMore({Cursor cursor})
+ : assert(cursor != null),
+ super([cursor]) {
+ this.cursor = cursor;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'cursor: $cursor'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/profile_list/profile_list_state.dart b/lib/src/bloc/blocs/profile_list/profile_list_state.dart
new file mode 100644
index 0000000..6e60d9a
--- /dev/null
+++ b/lib/src/bloc/blocs/profile_list/profile_list_state.dart
@@ -0,0 +1,53 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class ProfileListState extends Equatable {
+ ProfileListState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class ProfileListUninitialized extends ProfileListState
+ with ElementListUninitialized {}
+
+class ProfileListLoading extends ProfileListState
+ with ElementListLoading {
+ ProfileListLoading({int count = 0}) : super([count]) {
+ this.count = count;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'count: $count'
+ ' }';
+}
+
+class ProfileListLoaded extends ProfileListState
+ with ElementListLoaded {
+ ProfileListLoaded({@required List profiles})
+ : super([profiles]) {
+ elements = profiles;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'profiles: $elements'
+ ' }';
+}
+
+class ProfileListFailure extends ProfileListState
+ with ElementListFailure {
+ ProfileListFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/register/register.dart b/lib/src/bloc/blocs/register/register.dart
new file mode 100644
index 0000000..991f214
--- /dev/null
+++ b/lib/src/bloc/blocs/register/register.dart
@@ -0,0 +1,3 @@
+export './register_bloc.dart';
+export './register_event.dart';
+export './register_state.dart';
diff --git a/lib/src/bloc/blocs/register/register_bloc.dart b/lib/src/bloc/blocs/register/register_bloc.dart
new file mode 100644
index 0000000..9d4d56b
--- /dev/null
+++ b/lib/src/bloc/blocs/register/register_bloc.dart
@@ -0,0 +1,56 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+class RegisterBloc extends Bloc {
+ final String _tag = '$RegisterBloc';
+
+ final CVAuthService cvAuthService;
+
+ RegisterBloc({@required this.cvAuthService})
+ : assert(cvAuthService != null, 'No $CVAuthService given'),
+ super();
+
+ @override
+ RegisterState get initialState => RegisterInitial();
+
+ @override
+ Stream mapEventToState(RegisterEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+
+ if (event is RegistrationEvent) {
+ yield* _mapRegistrationEventToState(event);
+ }
+ }
+
+ /// -----------------------------------------------------------------------
+ /// All Event map to State
+ /// -----------------------------------------------------------------------
+
+ /// Map [RegistrationEvent] to [RegisterState]
+ ///
+ /// ```dart
+ /// yield* _mapRegistrationEventToState(event);
+ /// ```
+ Stream _mapRegistrationEventToState(
+ RegistrationEvent event) async* {
+ try {
+ if (event is RegistrationEvent) {
+ yield RegisterLoading();
+ cvAuthService.register(
+ fName: event.fName,
+ lName: event.lName,
+ email: event.email,
+ password: event.password,
+ );
+ await Future.delayed(Duration(seconds: 2));
+ yield RegisterInitial();
+ }
+ } catch (error) {
+ yield RegisterFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/register/register_event.dart b/lib/src/bloc/blocs/register/register_event.dart
new file mode 100644
index 0000000..8b20138
--- /dev/null
+++ b/lib/src/bloc/blocs/register/register_event.dart
@@ -0,0 +1,29 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+/// [RegisterEvent] that must be dispatch to [RegisterBloc]
+abstract class RegisterEvent extends Equatable {
+ RegisterEvent([List props = const []]) : super(props);
+}
+
+class RegistrationEvent extends RegisterEvent {
+ final String fName;
+ final String lName;
+ final String email;
+ final String password;
+
+ RegistrationEvent({
+ @required this.fName,
+ @required this.lName,
+ @required this.email,
+ @required this.password,
+ }) : super([fName, lName, email, password]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'fName: $fName, '
+ 'lName: $lName, '
+ 'email: $email, '
+ 'password: HIDDEN'
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/register/register_state.dart b/lib/src/bloc/blocs/register/register_state.dart
new file mode 100644
index 0000000..2ea9bc9
--- /dev/null
+++ b/lib/src/bloc/blocs/register/register_state.dart
@@ -0,0 +1,45 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+
+abstract class RegisterState extends Equatable {
+ RegisterState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class RegisterInitial extends RegisterState {}
+
+class RegisterLoading extends RegisterState {}
+
+class RegisterFailure extends RegisterState {
+ final dynamic error;
+
+ RegisterFailure({@required this.error})
+ : assert(error != null, 'No error given'),
+ super([error]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
+
+class RegisterSucceed extends RegisterState {
+ final String accessToken;
+ final DateTime accessTokenExpiration;
+ final String refreshToken;
+
+ RegisterSucceed({
+ @required this.accessToken,
+ @required this.accessTokenExpiration,
+ @required this.refreshToken,
+ }) : super([accessToken, accessTokenExpiration, refreshToken]);
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'accessToken: $accessToken, '
+ 'accessToken: $accessTokenExpiration, '
+ 'refreshToken: $refreshToken '
+ ' }';
+}
diff --git a/lib/src/bloc/blocs/user/user.dart b/lib/src/bloc/blocs/user/user.dart
new file mode 100644
index 0000000..c0ab383
--- /dev/null
+++ b/lib/src/bloc/blocs/user/user.dart
@@ -0,0 +1,3 @@
+export 'user_bloc.dart';
+export 'user_event.dart';
+export 'user_state.dart';
diff --git a/lib/src/bloc/blocs/user/user_bloc.dart b/lib/src/bloc/blocs/user/user_bloc.dart
new file mode 100644
index 0000000..0e01076
--- /dev/null
+++ b/lib/src/bloc/blocs/user/user_bloc.dart
@@ -0,0 +1,80 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// Business Logic Component for User
+class UserBloc
+ extends ElementBloc {
+ final String _tag = '$UserBloc';
+
+ UserBloc({@required UserRepository repository})
+ : super(repository: repository);
+
+ /// [_fallBackId] is used if [element] is never assigned and
+ /// an [UserRefresh] is dispatched
+ String _fallBackId;
+
+ @override
+ UserState get initialState => UserUninitialized();
+
+ @override
+ Stream mapEventToState(UserEvent event) async* {
+ print('$_tag:mapEventToState($event)');
+ if (event is UserInitialized) {
+ yield* _mapInitializedEventToState(event);
+ } else if (event is UserRefresh) {
+ yield* _mapRefreshEventToState(event);
+ }
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All Event map to State
+ /// --------------------------------------------------------------------------
+
+ /// Map [UserInitialized] to [UserState]
+ ///
+ /// ```dart
+ /// yield* _mapInitializedEventToState(event);
+ /// ```
+ Stream _mapInitializedEventToState(UserInitialized event) async* {
+ print('$_tag:_mapInitializedEventToState($event)');
+ try {
+ yield UserLoading();
+
+ if (event.elementId != null) {
+ _fallBackId = event.elementId;
+ element = await await repository.getById(event.elementId);
+ } else if (event.element != null) {
+ _fallBackId = event.element.id;
+ element = event.element;
+ }
+
+ yield UserLoaded(user: element);
+ } catch (error) {
+ yield UserFailure(error: error);
+ }
+ }
+
+ /// Map [UserRefresh] to [UserState]
+ ///
+ /// ```dart
+ /// yield* _mapRefreshEventToState(event);
+ /// ```
+ Stream _mapRefreshEventToState(UserRefresh event) async* {
+ print('$_tag:_mapRefreshEventToState($event)');
+ try {
+ yield UserLoading();
+
+ element = await repository.getById(
+ element?.id ?? _fallBackId,
+ force: true,
+ );
+
+ _fallBackId = element.id;
+
+ yield UserLoaded(user: element);
+ } catch (error) {
+ yield UserFailure(error: error);
+ }
+ }
+}
diff --git a/lib/src/bloc/blocs/user/user_event.dart b/lib/src/bloc/blocs/user/user_event.dart
new file mode 100644
index 0000000..c5b04f2
--- /dev/null
+++ b/lib/src/bloc/blocs/user/user_event.dart
@@ -0,0 +1,27 @@
+import 'package:equatable/equatable.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+/// [UserEvent] that must be dispatch to [UserBloc]
+
+abstract class UserEvent extends Equatable {
+ UserEvent([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class UserInitialized extends UserEvent with ElementInitialized {
+ UserInitialized({String userId, UserEntity user})
+ : assert(userId != null && user == null),
+ assert(userId == null && user != null),
+ super([userId, user]) {
+ this.elementId = userId;
+ this.element = user;
+ }
+
+ @override
+ String toString() => '$runtimeType{ userId: $elementId, user: $element }';
+}
+
+class UserRefresh extends UserEvent with ElementRefresh {}
diff --git a/lib/src/bloc/blocs/user/user_state.dart b/lib/src/bloc/blocs/user/user_state.dart
new file mode 100644
index 0000000..fdfd6f8
--- /dev/null
+++ b/lib/src/bloc/blocs/user/user_state.dart
@@ -0,0 +1,42 @@
+import 'package:equatable/equatable.dart';
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/bloc.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+abstract class UserState extends Equatable {
+ UserState([List props = const []]) : super(props);
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+class UserUninitialized extends UserState
+ with ElementUninitialized {}
+
+class UserLoading extends UserState with ElementLoading {}
+
+class UserLoaded extends UserState with ElementLoaded {
+ UserLoaded({UserEntity user}) : super([user]) {
+ element = user;
+ }
+
+ @override
+ String toString() {
+ return '$runtimeType{ '
+ 'element: $element'
+ ' }';
+ }
+}
+
+class UserFailure extends UserState with ElementFailure {
+ UserFailure({@required dynamic error})
+ : assert(error != null, 'No error given'),
+ super([error]) {
+ this.error = error;
+ }
+
+ @override
+ String toString() => '$runtimeType{ '
+ 'error: $error'
+ ' }';
+}
diff --git a/lib/src/blocs/bloc_provider.dart b/lib/src/blocs/bloc_provider.dart
deleted file mode 100644
index 13dfe0c..0000000
--- a/lib/src/blocs/bloc_provider.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-///Generic Interface for all BLoCs
-import 'package:flutter/material.dart';
-import 'package:social_cv_client_dart_common/blocs.dart';
-
-///Generic BLoC provider
-class BlocProvider extends StatefulWidget {
- BlocProvider({
- Key key,
- @required this.child,
- @required this.bloc,
- }) : super(key: key);
-
- final T bloc;
- final Widget child;
-
- @override
- _BlocProviderState createState() => _BlocProviderState();
-
- static T of(BuildContext context) {
- final type = _typeOf>();
- BlocProvider provider = context.ancestorWidgetOfExactType(type);
-
- return provider.bloc;
- }
-
- static Type _typeOf() => T;
-}
-
-class _BlocProviderState extends State> {
- @override
- void dispose() {
- widget.bloc.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return widget.child;
- }
-}
diff --git a/lib/src/blocs/main_bloc.dart b/lib/src/blocs/main_bloc.dart
deleted file mode 100644
index d279c4d..0000000
--- a/lib/src/blocs/main_bloc.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:rxdart/rxdart.dart';
-import 'package:social_cv_client_dart_common/blocs.dart';
-
-enum TabType {
- HOME_TAB,
- ACCOUNT_TAB,
-}
-
-class MainBloc extends BlocBase {
- MainBloc() : super() {
- _tabController.add(TabType.HOME_TAB);
- }
-
- ///Reactive variables
- final _tabController = BehaviorSubject();
-
- ///Streams
- Observable get tabStream => _tabController.stream;
-
- ///Sinks
- Sink get tab => _tabController.sink;
-
- /* Functions */
-
- ///Human function
- Function(TabType) get changeTab => tab.add;
-
- @override
- void dispose() {
- _tabController.close();
- }
-}
diff --git a/lib/src/commons/colors.dart b/lib/src/commons/colors.dart
deleted file mode 100644
index e8f87eb..0000000
--- a/lib/src/commons/colors.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-import 'dart:ui';
-
-import 'package:flutter/material.dart';
-
-class AppColors {
- ///Colors
- static const Color kCVBlue = Colors.blue;
- static const Color kCVOrange = Colors.deepOrange;
- static const Color kCVPink = Colors.pink;
- static const Color kCVWhite = Colors.white;
- static const Color kCVBlack = Colors.black;
-
- ///Basics
- static const Color kCVPrimaryColor = const Color(0xFF2196f3);
- static const Color kCVPrimaryColorLight = const Color(0xFF6ec6ff);
- static const Color kCVPrimaryColorDark = const Color(0xFF0069c0);
- static const Color kCVTextOnPrimary = const Color(0xFFFFFFFF);
- static const Color kCVAccentColor = const Color(0xFFFF5722);
- static const Color kCVAccentColorLight = const Color(0xFFff8a50);
- static const Color kCVAccentColorDark = const Color(0xFFc41c00);
- static const Color kCVTextOnAccent = const Color(0xFFFFFFFF);
-
- static const Color kCVBackgroundColor = const Color(0xFFFFFFFF);
- static const Color kCVBackgroundColorLight = kCVBackgroundColor;
- static const Color kCVBackgroundColorDark = const Color(0xFF353A3A);
-
- ///Cards
- static const Color kCVCardBackgroundColor = const Color(0xFFFFFFFF);
- static const Color kCVCardBackgroundColorLight = kCVBackgroundColor;
- static const Color kCVCardBackgroundColorDark = const Color(0xFF353A3A);
-
- /// Misc
- static const Color kCVErrorRed = Colors.red;
-
- /// Auth Stuff
- static const Color loginGradientEnd = kCVPrimaryColorLight;
- static const Color loginGradientStart = kCVPrimaryColorDark;
-}
diff --git a/lib/src/commons/dimensions.dart b/lib/src/commons/dimensions.dart
deleted file mode 100644
index c5a1786..0000000
--- a/lib/src/commons/dimensions.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-class AppDimensions {
- ///Group
- static const double kCVGroupPadding = 5.0;
-
- ///Entry
- static const double kCVEntryPadding = 10.0;
- static const double kCVEntryTagSpacing = 4.0;
- static const double kCVEntryCardElevation = 2.0;
- static const double kCVEntryEventHeight = 200.0;
- static const double kCVEntryEventHWidth = 300.0;
-
- static const double kCVHorizontalEntryListHeight = kCVEntryEventHeight;
- static const double kCVHorizontalGroupListHeight = 300.0;
-
- ///Sort Dialog
- static const double kCVSortDialogWidth = 200.0;
- static const double kCVSortDialogHeight = 300.0;
-
- ///List
- static const double kCVListHeaderDefaultHeightMax = 40.0;
- static const double kCVListHeaderDefaultHeightMin = 40.0;
-
- ///Profile
- static const double kCVProfileAvatarMin = 5.0;
- static const double kCVProfileAvatarMax = 50.0;
- static const double kCVProfileAvatarElevation = 2.0;
-}
diff --git a/lib/src/commons/tags.dart b/lib/src/commons/tags.dart
deleted file mode 100644
index 4beb430..0000000
--- a/lib/src/commons/tags.dart
+++ /dev/null
@@ -1 +0,0 @@
-const String kHeroSearchFAB = 'TAG_HERO_SEARCH_FAB';
diff --git a/lib/src/data/cache_model.dart b/lib/src/data/cache_model.dart
new file mode 100644
index 0000000..9762a8d
--- /dev/null
+++ b/lib/src/data/cache_model.dart
@@ -0,0 +1,16 @@
+import 'package:meta/meta.dart';
+
+class CacheModel {
+ CacheModel({
+ @required this.model,
+ @required this.expiration,
+ }) : assert(model != null),
+ assert(model != null);
+
+ T model;
+ DateTime expiration;
+
+ bool isExpired() {
+ return DateTime.now().compareTo(expiration) >= 0 ? true : false;
+ }
+}
diff --git a/lib/src/data/exceptions/api_exceptions.dart b/lib/src/data/exceptions/api_exceptions.dart
new file mode 100644
index 0000000..621a815
--- /dev/null
+++ b/lib/src/data/exceptions/api_exceptions.dart
@@ -0,0 +1,59 @@
+import 'package:meta/meta.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+
+class ApiException extends AppException {
+ ApiException._internal({
+ @required AppExceptionType type,
+ String message,
+ StackTrace stackTrace,
+ }) : assert(type != null, ' No $AppExceptionType given'),
+ super(type: type, message: message, stackTrace: stackTrace);
+
+ factory ApiException.fromDioRequest({
+ @required String errorCode,
+ String message,
+ StackTrace stackTrace,
+ }) {
+ assert(errorCode != null);
+
+ final AppExceptionType errorType = _apiErrorCodes[errorCode];
+
+ return ApiException._internal(
+ type: errorType ?? AppExceptionType.somethingWentWrong,
+ message: message,
+ stackTrace: stackTrace,
+ );
+ }
+}
+
+/// Error map between api errors and domain exception types
+Map _apiErrorCodes = {
+ /// --------------------------------------------------------------------------
+ /// Server
+ /// --------------------------------------------------------------------------
+
+ 'SERVER_ERROR': AppExceptionType.serverSideProblem,
+
+ /// --------------------------------------------------------------------------
+ /// Authentication
+ /// --------------------------------------------------------------------------
+
+ 'AUTH_LOGIN_FAILED': AppExceptionType.authLoginFailed,
+ 'AUTH_REGISTRATION_FAILED': AppExceptionType.authRegistrationFailed,
+ 'AUTH_ACCOUNT_ALREADY_EXISTS': AppExceptionType.authAccountAlreadyExists,
+ 'AUTH_ACCOUNT_DISABLED': AppExceptionType.authAccountDisabled,
+ 'AUTH_NOT_AUTHORIZED': AppExceptionType.authUnauthorized,
+ 'AUTH_FORBIDDEN': AppExceptionType.authForbidden,
+
+ /// --------------------------------------------------------------------------
+ /// User
+ /// --------------------------------------------------------------------------
+
+ 'USER_NOT_FOUND': AppExceptionType.userNotFound,
+
+ /// --------------------------------------------------------------------------
+ /// Form
+ /// --------------------------------------------------------------------------
+
+ 'FORM_PASSWORD_WRONG_POLICY': AppExceptionType.formPasswordWrongPolicy,
+};
diff --git a/lib/src/data/managers/api_interceptor.dart b/lib/src/data/managers/api_interceptor.dart
new file mode 100644
index 0000000..2bfe157
--- /dev/null
+++ b/lib/src/data/managers/api_interceptor.dart
@@ -0,0 +1,117 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:social_cv_client_flutter/data.dart';
+import 'package:social_cv_client_flutter/domain.dart' as domain;
+
+class ApiInterceptor extends Interceptor {
+ String _clientId;
+ String _clientSecret;
+ String _accessToken;
+ String _refreshToken;
+
+ InterceptorsWrapper _interceptorWrapper;
+
+ ApiInterceptor({
+ String clientId,
+ String clientSecret,
+ String accessToken,
+ String refreshToken,
+ }) {
+ _clientId = clientId;
+ _clientSecret = clientSecret;
+ _accessToken = accessToken;
+ _refreshToken = refreshToken;
+
+ _interceptorWrapper =
+ InterceptorsWrapper(onRequest: _onRequest, onResponse: _onResponse);
+ }
+
+ InterceptorsWrapper get interceptorsWrapper => _interceptorWrapper;
+
+ RequestOptions _onRequest(RequestOptions options) {
+ // Adding client credentials
+ if (_clientId != null && _clientSecret != null) {
+ options.headers.addAll({
+ 'client_id': '$_clientId',
+ 'client_secret': '$_clientSecret',
+ 'grant_type': 'password',
+ });
+ }
+
+ // Adding access token
+ if (_accessToken != null) {
+ options.headers
+ .addAll({HttpHeaders.authorizationHeader: 'Bearer $_accessToken'});
+ }
+
+ // Adding refresh token
+ if (_refreshToken != null) {
+ options.headers.addAll({'refresh_token': '$_refreshToken'});
+ }
+
+ return options;
+ }
+
+ Response _onResponse(Response response) {
+ /// Save access token
+ final data = response.data;
+ if (data is Map) {
+ if (data.containsKey('access_token')) {
+ _accessToken = data['access_token'] as String;
+ }
+
+ /// Save refresh token
+ if (data.containsKey('refresh_token')) {
+ _refreshToken = response.data['refresh_token'] as String;
+ }
+ }
+ return response;
+ }
+
+ FutureOr deleteAuthData() async {
+ _accessToken = null;
+ }
+
+ @override
+ String toString() => '$runtimeType{}';
+}
+
+/// Parse response to check if there is any error
+dynamic checkApiResponse(Response response, {StackTrace stackTrace}) {
+ // TODO: use Envelop type for Response body
+ final Map body = response.data as Map;
+ final String apiErrorCode = body['error'] as String;
+ final String apiMessage = body['message'] as String;
+
+ if (apiErrorCode != null) {
+ throw ApiException.fromDioRequest(
+ errorCode: apiErrorCode,
+ message: apiMessage,
+ stackTrace: stackTrace ?? StackTrace.current,
+ );
+ }
+ return response;
+}
+
+dynamic apiErrorCatcher(dynamic err) {
+ if (err is DioError && err.response != null) {
+ final Response response = err.response;
+ final StackTrace stackTrace = (err?.error as dynamic).stackTrace as StackTrace;
+
+ checkApiResponse(response, stackTrace: stackTrace);
+
+ final statusCode = err?.response?.statusCode;
+ final statusMessage = err?.response?.statusMessage;
+
+ if (statusCode != null) {
+ throw domain.HttpException(
+ statusCode: statusCode,
+ statusMessage: statusMessage,
+ stackTrace: stackTrace ?? StackTrace.current,
+ );
+ }
+ }
+ return err;
+}
diff --git a/lib/src/data/managers/app_shared_preferences_manager.dart b/lib/src/data/managers/app_shared_preferences_manager.dart
new file mode 100644
index 0000000..f36ff7e
--- /dev/null
+++ b/lib/src/data/managers/app_shared_preferences_manager.dart
@@ -0,0 +1,50 @@
+import 'dart:async';
+
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:social_cv_client_flutter/data.dart';
+
+/// Application preferences manager implementation
+/// providing [AppPrefsDataStore]
+class AppPrefsManager implements AppPrefsDataStore {
+ final String _keyAppDarkMode = 'APP_DARK_MODE';
+
+ FutureOr get _prefs => SharedPreferences.getInstance();
+
+ AppPrefsManager();
+
+ /// --------------------------------------------------------------------------
+ /// Dark Mode
+ /// --------------------------------------------------------------------------
+
+ @override
+ FutureOr getDarkMode() async {
+ final storage = await _prefs;
+ return storage.getBool(_keyAppDarkMode);
+ }
+
+ @override
+ FutureOr toggleDarkMode(bool darkMode) async {
+ final storage = await _prefs;
+ return await storage.setBool(
+ _keyAppDarkMode,
+ darkMode,
+ );
+ }
+
+ @override
+ FutureOr deleteDarkMode() async {
+ final storage = await _prefs;
+ await storage.remove(_keyAppDarkMode);
+ return null;
+ }
+
+ /// --------------------------------------------------------------------------
+ /// All
+ /// --------------------------------------------------------------------------
+
+ @override
+ Future deleteAll() async {
+ final storage = await _prefs;
+ await storage.remove(_keyAppDarkMode);
+ }
+}
diff --git a/lib/src/data/managers/auth_shared_preferences_manager.dart b/lib/src/data/managers/auth_shared_preferences_manager.dart
new file mode 100644
index 0000000..b0d134f
--- /dev/null
+++ b/lib/src/data/managers/auth_shared_preferences_manager.dart
@@ -0,0 +1,116 @@
+import 'dart:async';
+
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:social_cv_client_flutter/data.dart';
+
+/// One of the possible Implementation of PreferencesService Interface
+class AuthSharedPreferencesManager implements AuthInfoDataStore {
+ final String _keyOAuthAccessToken = 'OAUTH_ACCESS_TOKEN';
+ final String _keyOAuthAccessTokenExpiration = 'OAUTH_ACCESS_TOKEN_EXPIRATION';
+ final String _keyOAuthRefreshToken = 'OAUTH_REFRESH_TOKEN';
+ final String _keyOAuthRefreshTokenExpiration =
+ 'OAUTH_REFRESH_TOKEN_EXPIRATION';
+
+ FutureOr get _prefs => SharedPreferences.getInstance();
+
+ AuthSharedPreferencesManager();
+
+ /// ----------------------------------------------------------
+ /// ------------------------- Tokens -------------------------
+ /// ----------------------------------------------------------
+
+ @override
+ FutureOr getAccessToken() async {
+ final prefs = await _prefs;
+ return prefs.getString(_keyOAuthAccessToken);
+ }
+
+ @override
+ FutureOr setAccessToken(String token) async {
+ final prefs = await _prefs;
+ return (await prefs.setString(_keyOAuthAccessToken, token)) ? token : null;
+ }
+
+ @override
+ FutureOr deleteAccessToken() async {
+ final prefs = await _prefs;
+ await prefs.remove(_keyOAuthAccessToken);
+ return null;
+ }
+
+ @override
+ FutureOr getAccessTokenExpiration() async {
+ final prefs = await _prefs;
+ return DateTime.parse(prefs.getString(_keyOAuthAccessTokenExpiration));
+ }
+
+ @override
+ FutureOr setAccessTokenExpiration(DateTime expiration) async {
+ final prefs = await _prefs;
+ return (await prefs.setString(
+ _keyOAuthAccessTokenExpiration, expiration.toIso8601String()))
+ ? expiration
+ : null;
+ }
+
+ @override
+ FutureOr deleteAccessTokenExpiration() async {
+ final prefs = await _prefs;
+ await prefs.remove(_keyOAuthAccessTokenExpiration);
+ return null;
+ }
+
+ @override
+ FutureOr getRefreshToken() async {
+ final prefs = await _prefs;
+ return prefs.getString(_keyOAuthRefreshToken);
+ }
+
+ @override
+ FutureOr setRefreshToken(String refreshToken) async {
+ final prefs = await _prefs;
+ return (await prefs.setString(_keyOAuthRefreshToken, refreshToken))
+ ? refreshToken
+ : null;
+ }
+
+ @override
+ FutureOr deleteRefreshToken() async {
+ final prefs = await _prefs;
+ await prefs.remove(_keyOAuthRefreshToken);
+ return null;
+ }
+
+ @override
+ FutureOr getRefreshTokenExpiration() async {
+ final prefs = await _prefs;
+ return DateTime.parse(prefs.getString(_keyOAuthRefreshTokenExpiration));
+ }
+
+ @override
+ FutureOr setRefreshTokenExpiration(DateTime expiration) async {
+ final prefs = await _prefs;
+ return (await prefs.setString(
+ _keyOAuthRefreshTokenExpiration, expiration.toIso8601String()))
+ ? expiration
+ : null;
+ }
+
+ @override
+ FutureOr deleteRefreshTokenExpiration() async {
+ final prefs = await _prefs;
+ await prefs.remove(_keyOAuthRefreshTokenExpiration);
+ return null;
+ }
+
+ /// ----------------------------------------------------------
+ /// -------------------------- All ---------------------------
+ /// ----------------------------------------------------------
+
+ Future deleteAll() async {
+ await deleteAccessToken();
+ await deleteAccessTokenExpiration();
+ await deleteRefreshToken();
+ await deleteRefreshTokenExpiration();
+ }
+}
diff --git a/lib/src/data/managers/config_assets_manager.dart b/lib/src/data/managers/config_assets_manager.dart
new file mode 100644
index 0000000..e7f384f
--- /dev/null
+++ b/lib/src/data/managers/config_assets_manager.dart
@@ -0,0 +1,42 @@
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:flutter/services.dart';
+import 'package:social_cv_client_flutter/domain.dart';
+import 'package:social_cv_client_flutter/src/data/models/config_model.dart';
+
+/// From https://medium.com/@sokrato/storing-your-secret-keys-in-flutter-c0b9af1c0f69
+class ConfigAssetsManager implements FoundationConfigService {
+ static const _configPath = 'config.json';
+ ConfigDataModel _config;
+
+ ConfigAssetsManager();
+
+ FutureOr _load() async {
+ final jsonMap = await rootBundle.loadStructuredData