├── .fvm
├── flutter_sdk
└── fvm_config.json
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── ouahiddev
│ │ │ │ └── Blog
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
└── images
│ ├── identity.svg
│ ├── shield.svg
│ ├── space-man.svg
│ └── woman-laugh.svg
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── main.dart
└── src
│ ├── application
│ ├── article_create
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── article_details
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── articles
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── favorites
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── profile
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── search
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── sign_in
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ └── sign_up
│ │ ├── bloc.dart
│ │ ├── bloc.freezed.dart
│ │ └── state.dart
│ ├── domain
│ ├── article_create
│ │ └── i_article_create_facade.dart
│ ├── article_details
│ │ └── i_article_details_facade.dart
│ ├── articles
│ │ └── i_articles_facade.dart
│ ├── entities
│ │ ├── api_error
│ │ │ ├── api_error.dart
│ │ │ └── api_error.g.dart
│ │ ├── api_response
│ │ │ ├── api_response.dart
│ │ │ └── api_response.g.dart
│ │ ├── article
│ │ │ ├── article.dart
│ │ │ └── article.g.dart
│ │ ├── category
│ │ │ ├── category.dart
│ │ │ └── category.g.dart
│ │ ├── favorite
│ │ │ ├── favorite.dart
│ │ │ └── favorite.g.dart
│ │ └── user
│ │ │ ├── user.dart
│ │ │ └── user.g.dart
│ ├── favorites
│ │ └── i_favorites_facade.dart
│ ├── profile
│ │ └── i_profile_facade.dart
│ ├── search
│ │ └── i_search_facade.dart
│ ├── sign_in
│ │ └── i_sign_in_facade.dart
│ └── sign_up
│ │ └── i_sign_up_facade.dart
│ ├── infrastructure
│ ├── article_create
│ │ └── article_create_facade.dart
│ ├── article_details
│ │ └── article_details_facade.dart
│ ├── articles
│ │ └── articles_facade.dart
│ ├── core
│ │ ├── network
│ │ │ ├── interceptor.dart
│ │ │ ├── network.dart
│ │ │ └── network.g.dart
│ │ └── preferences.dart
│ ├── favorites
│ │ └── favorites_facade.dart
│ ├── profile
│ │ └── profile_facade.dart
│ ├── search
│ │ └── search_facade.dart
│ ├── sign_in
│ │ └── sign_in_facade.dart
│ └── sign_up
│ │ └── sign_up_facade.dart
│ ├── injection.config.dart
│ ├── injection.dart
│ └── presentation
│ ├── routes
│ ├── routes.dart
│ └── routes_generator.dart
│ ├── ui
│ ├── components
│ │ ├── article_item.dart
│ │ ├── buttons
│ │ │ ├── clear_button.dart
│ │ │ ├── rounded_button.dart
│ │ │ └── rounded_outline_button.dart
│ │ ├── dialogs
│ │ │ ├── question.dart
│ │ │ └── waiting.dart
│ │ ├── loading.dart
│ │ ├── login.dart
│ │ ├── my_article_item.dart
│ │ └── text_fields
│ │ │ ├── rounded_outline_text_field.dart
│ │ │ └── search_field.dart
│ └── pages
│ │ ├── article_create
│ │ ├── article_create.dart
│ │ └── widgets
│ │ │ ├── categories.dart
│ │ │ ├── image.dart
│ │ │ ├── images.dart
│ │ │ └── widgets.dart
│ │ ├── article_details
│ │ └── article_details.dart
│ │ ├── articles
│ │ ├── articles.dart
│ │ └── widgets
│ │ │ ├── articles.dart
│ │ │ ├── categories.dart
│ │ │ └── widgets.dart
│ │ ├── favorites
│ │ └── favorites.dart
│ │ ├── home
│ │ ├── home.dart
│ │ └── widgets
│ │ │ ├── bottom_tab_bar.dart
│ │ │ └── widgets.dart
│ │ ├── on_boarding
│ │ ├── on_boarding.dart
│ │ └── widgets
│ │ │ ├── dote.dart
│ │ │ ├── indicator.dart
│ │ │ ├── page.dart
│ │ │ └── widgets.dart
│ │ ├── profile
│ │ ├── profile.dart
│ │ └── widgets
│ │ │ ├── header.dart
│ │ │ └── widgets.dart
│ │ ├── search
│ │ └── search.dart
│ │ ├── sign_in
│ │ └── sign_in.dart
│ │ └── sign_up
│ │ └── sign_up.dart
│ └── utils
│ ├── extensions.dart
│ └── validators.dart
├── pubspec.lock
├── pubspec.yaml
├── screenshot
├── flutter_01.png
├── flutter_02.png
├── flutter_03.png
├── flutter_04.png
├── flutter_05.png
├── flutter_06.png
├── flutter_07.png
└── flutter_08.png
├── test
├── extensions_test.dart
├── test.dart
└── widget_test.dart
└── web
├── favicon.png
├── icons
├── Icon-192.png
└── Icon-512.png
├── index.html
└── manifest.json
/.fvm/flutter_sdk:
--------------------------------------------------------------------------------
1 | C:/Users/Abdelouahed/fvm/versions/stable
--------------------------------------------------------------------------------
/.fvm/fvm_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "flutterSdkVersion": "stable",
3 | "flavors": {}
4 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: d408d302e22179d598f467e11da5dd968dbdc9ec
8 | channel: beta
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Blog
2 | a flutter project based on Backend built by Dart + Aqueduct + MongoDB
3 | ### [Backend code](https://github.com/GeekAbdelouahed/Aqueduct-MongoDB)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | #### Developer [Abdelouahed Medjoudja](https://www.facebook.com/AbdelouahedMedjoudja)
16 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 29
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.ouahiddev.Blog"
42 | minSdkVersion 16
43 | targetSdkVersion 29
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
9 |
10 |
15 |
22 |
26 |
30 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
46 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/ouahiddev/Blog/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ouahiddev.Blog
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPhotoLibraryUsageDescription
6 | Allow Select images for Article
7 | NSCameraUsageDescription
8 | Allow Select images for Article
9 | CFBundleDevelopmentRegion
10 | $(DEVELOPMENT_LANGUAGE)
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | Blog
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIMainStoryboardFile
32 | Main
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:get_it/get_it.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import 'src/infrastructure/core/preferences.dart';
7 | import 'src/injection.dart';
8 | import 'src/presentation/routes/routes.dart';
9 | import 'src/presentation/routes/routes_generator.dart';
10 |
11 | void main() async {
12 | WidgetsFlutterBinding.ensureInitialized();
13 |
14 | configureInjection(Environment.prod);
15 |
16 | await GetIt.I.isReady();
17 |
18 | SystemChrome.setSystemUIOverlayStyle(
19 | SystemUiOverlayStyle(statusBarColor: Colors.indigo),
20 | );
21 |
22 | runApp(MyApp());
23 | }
24 |
25 | class MyApp extends StatelessWidget {
26 | @override
27 | Widget build(BuildContext context) {
28 | return MaterialApp(
29 | debugShowCheckedModeBanner: false,
30 | title: 'Flutter Blog',
31 | theme: ThemeData(
32 | primarySwatch: Colors.indigo,
33 | visualDensity: VisualDensity.adaptivePlatformDensity,
34 | ),
35 | onGenerateRoute: AppRoutesGenerator.generateRoute,
36 | initialRoute: getIt().isLoggedIn
37 | ? AppRoutes.home
38 | : AppRoutes.onBoarding,
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/src/application/article_create/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:dio/dio.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:injectable/injectable.dart';
6 |
7 | import '../../domain/article_create/i_article_create_facade.dart';
8 | import '../../domain/entities/api_error/api_error.dart';
9 | import '../../domain/entities/api_response/api_response.dart';
10 |
11 | part 'bloc.freezed.dart';
12 | part 'state.dart';
13 |
14 | @injectable
15 | class ArticleCreateBloc extends Cubit {
16 | final IArticleCreateFacade _articleFacade;
17 |
18 | ArticleCreateBloc(this._articleFacade) : super(ArticleCreateState.initial());
19 |
20 | void createArticle(FormData data) async {
21 | emit(state.copyWith(articleCreateState: none()));
22 | final result = await _articleFacade.createArticle(data);
23 | emit(state.copyWith(articleCreateState: optionOf(result)));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/application/article_create/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$ArticleCreateStateTearOff {
14 | const _$ArticleCreateStateTearOff();
15 |
16 | // ignore: unused_element
17 | _ArticleCreateState call(
18 | {@required Option> articleCreateState}) {
19 | return _ArticleCreateState(
20 | articleCreateState: articleCreateState,
21 | );
22 | }
23 | }
24 |
25 | /// @nodoc
26 | // ignore: unused_element
27 | const $ArticleCreateState = _$ArticleCreateStateTearOff();
28 |
29 | /// @nodoc
30 | mixin _$ArticleCreateState {
31 | Option> get articleCreateState;
32 |
33 | @JsonKey(ignore: true)
34 | $ArticleCreateStateCopyWith get copyWith;
35 | }
36 |
37 | /// @nodoc
38 | abstract class $ArticleCreateStateCopyWith<$Res> {
39 | factory $ArticleCreateStateCopyWith(
40 | ArticleCreateState value, $Res Function(ArticleCreateState) then) =
41 | _$ArticleCreateStateCopyWithImpl<$Res>;
42 | $Res call({Option> articleCreateState});
43 | }
44 |
45 | /// @nodoc
46 | class _$ArticleCreateStateCopyWithImpl<$Res>
47 | implements $ArticleCreateStateCopyWith<$Res> {
48 | _$ArticleCreateStateCopyWithImpl(this._value, this._then);
49 |
50 | final ArticleCreateState _value;
51 | // ignore: unused_field
52 | final $Res Function(ArticleCreateState) _then;
53 |
54 | @override
55 | $Res call({
56 | Object articleCreateState = freezed,
57 | }) {
58 | return _then(_value.copyWith(
59 | articleCreateState: articleCreateState == freezed
60 | ? _value.articleCreateState
61 | : articleCreateState as Option>,
62 | ));
63 | }
64 | }
65 |
66 | /// @nodoc
67 | abstract class _$ArticleCreateStateCopyWith<$Res>
68 | implements $ArticleCreateStateCopyWith<$Res> {
69 | factory _$ArticleCreateStateCopyWith(
70 | _ArticleCreateState value, $Res Function(_ArticleCreateState) then) =
71 | __$ArticleCreateStateCopyWithImpl<$Res>;
72 | @override
73 | $Res call({Option> articleCreateState});
74 | }
75 |
76 | /// @nodoc
77 | class __$ArticleCreateStateCopyWithImpl<$Res>
78 | extends _$ArticleCreateStateCopyWithImpl<$Res>
79 | implements _$ArticleCreateStateCopyWith<$Res> {
80 | __$ArticleCreateStateCopyWithImpl(
81 | _ArticleCreateState _value, $Res Function(_ArticleCreateState) _then)
82 | : super(_value, (v) => _then(v as _ArticleCreateState));
83 |
84 | @override
85 | _ArticleCreateState get _value => super._value as _ArticleCreateState;
86 |
87 | @override
88 | $Res call({
89 | Object articleCreateState = freezed,
90 | }) {
91 | return _then(_ArticleCreateState(
92 | articleCreateState: articleCreateState == freezed
93 | ? _value.articleCreateState
94 | : articleCreateState as Option>,
95 | ));
96 | }
97 | }
98 |
99 | /// @nodoc
100 | class _$_ArticleCreateState implements _ArticleCreateState {
101 | const _$_ArticleCreateState({@required this.articleCreateState})
102 | : assert(articleCreateState != null);
103 |
104 | @override
105 | final Option> articleCreateState;
106 |
107 | @override
108 | String toString() {
109 | return 'ArticleCreateState(articleCreateState: $articleCreateState)';
110 | }
111 |
112 | @override
113 | bool operator ==(dynamic other) {
114 | return identical(this, other) ||
115 | (other is _ArticleCreateState &&
116 | (identical(other.articleCreateState, articleCreateState) ||
117 | const DeepCollectionEquality()
118 | .equals(other.articleCreateState, articleCreateState)));
119 | }
120 |
121 | @override
122 | int get hashCode =>
123 | runtimeType.hashCode ^
124 | const DeepCollectionEquality().hash(articleCreateState);
125 |
126 | @JsonKey(ignore: true)
127 | @override
128 | _$ArticleCreateStateCopyWith<_ArticleCreateState> get copyWith =>
129 | __$ArticleCreateStateCopyWithImpl<_ArticleCreateState>(this, _$identity);
130 | }
131 |
132 | abstract class _ArticleCreateState implements ArticleCreateState {
133 | const factory _ArticleCreateState(
134 | {@required
135 | Option> articleCreateState}) =
136 | _$_ArticleCreateState;
137 |
138 | @override
139 | Option> get articleCreateState;
140 | @override
141 | @JsonKey(ignore: true)
142 | _$ArticleCreateStateCopyWith<_ArticleCreateState> get copyWith;
143 | }
144 |
--------------------------------------------------------------------------------
/lib/src/application/article_create/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class ArticleCreateState with _$ArticleCreateState {
5 | const factory ArticleCreateState({
6 | @required Option> articleCreateState,
7 | }) = _ArticleCreateState;
8 |
9 | factory ArticleCreateState.initial() => ArticleCreateState(
10 | articleCreateState: none(),
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/application/article_details/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/article_details/i_article_details_facade.dart';
7 | import '../../domain/entities/api_error/api_error.dart';
8 | import '../../domain/entities/api_response/api_response.dart';
9 | import '../../domain/entities/article/article.dart';
10 |
11 | part 'bloc.freezed.dart';
12 |
13 | part 'state.dart';
14 |
15 | @injectable
16 | class ArticleDetailsBloc extends Cubit {
17 | final IArticleDetailsFacade _articleFacade;
18 |
19 | ArticleDetailsBloc(this._articleFacade)
20 | : super(ArticleDetailsState.initial());
21 |
22 | void getArticleDetails(String id, {String userId}) async {
23 | emit(state.copyWith(articleDetailsState: none()));
24 | final result = await _articleFacade.getArticleDetails(id, userId: userId);
25 | emit(state.copyWith(articleDetailsState: optionOf(result)));
26 | }
27 |
28 | void addFavorite(String articleId, String userId) async {
29 | await _articleFacade.addFavorite(articleId, userId);
30 | }
31 |
32 | void removeFavorite(String articleId, String userId) async {
33 | await _articleFacade.removeFavorite(articleId, userId);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/application/article_details/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$ArticleDetailsStateTearOff {
14 | const _$ArticleDetailsStateTearOff();
15 |
16 | // ignore: unused_element
17 | _ArticleDetailsState call(
18 | {@required
19 | Option>> articleDetailsState}) {
20 | return _ArticleDetailsState(
21 | articleDetailsState: articleDetailsState,
22 | );
23 | }
24 | }
25 |
26 | /// @nodoc
27 | // ignore: unused_element
28 | const $ArticleDetailsState = _$ArticleDetailsStateTearOff();
29 |
30 | /// @nodoc
31 | mixin _$ArticleDetailsState {
32 | Option>> get articleDetailsState;
33 |
34 | @JsonKey(ignore: true)
35 | $ArticleDetailsStateCopyWith get copyWith;
36 | }
37 |
38 | /// @nodoc
39 | abstract class $ArticleDetailsStateCopyWith<$Res> {
40 | factory $ArticleDetailsStateCopyWith(
41 | ArticleDetailsState value, $Res Function(ArticleDetailsState) then) =
42 | _$ArticleDetailsStateCopyWithImpl<$Res>;
43 | $Res call(
44 | {Option>> articleDetailsState});
45 | }
46 |
47 | /// @nodoc
48 | class _$ArticleDetailsStateCopyWithImpl<$Res>
49 | implements $ArticleDetailsStateCopyWith<$Res> {
50 | _$ArticleDetailsStateCopyWithImpl(this._value, this._then);
51 |
52 | final ArticleDetailsState _value;
53 | // ignore: unused_field
54 | final $Res Function(ArticleDetailsState) _then;
55 |
56 | @override
57 | $Res call({
58 | Object articleDetailsState = freezed,
59 | }) {
60 | return _then(_value.copyWith(
61 | articleDetailsState: articleDetailsState == freezed
62 | ? _value.articleDetailsState
63 | : articleDetailsState
64 | as Option>>,
65 | ));
66 | }
67 | }
68 |
69 | /// @nodoc
70 | abstract class _$ArticleDetailsStateCopyWith<$Res>
71 | implements $ArticleDetailsStateCopyWith<$Res> {
72 | factory _$ArticleDetailsStateCopyWith(_ArticleDetailsState value,
73 | $Res Function(_ArticleDetailsState) then) =
74 | __$ArticleDetailsStateCopyWithImpl<$Res>;
75 | @override
76 | $Res call(
77 | {Option>> articleDetailsState});
78 | }
79 |
80 | /// @nodoc
81 | class __$ArticleDetailsStateCopyWithImpl<$Res>
82 | extends _$ArticleDetailsStateCopyWithImpl<$Res>
83 | implements _$ArticleDetailsStateCopyWith<$Res> {
84 | __$ArticleDetailsStateCopyWithImpl(
85 | _ArticleDetailsState _value, $Res Function(_ArticleDetailsState) _then)
86 | : super(_value, (v) => _then(v as _ArticleDetailsState));
87 |
88 | @override
89 | _ArticleDetailsState get _value => super._value as _ArticleDetailsState;
90 |
91 | @override
92 | $Res call({
93 | Object articleDetailsState = freezed,
94 | }) {
95 | return _then(_ArticleDetailsState(
96 | articleDetailsState: articleDetailsState == freezed
97 | ? _value.articleDetailsState
98 | : articleDetailsState
99 | as Option>>,
100 | ));
101 | }
102 | }
103 |
104 | /// @nodoc
105 | class _$_ArticleDetailsState implements _ArticleDetailsState {
106 | const _$_ArticleDetailsState({@required this.articleDetailsState})
107 | : assert(articleDetailsState != null);
108 |
109 | @override
110 | final Option>> articleDetailsState;
111 |
112 | @override
113 | String toString() {
114 | return 'ArticleDetailsState(articleDetailsState: $articleDetailsState)';
115 | }
116 |
117 | @override
118 | bool operator ==(dynamic other) {
119 | return identical(this, other) ||
120 | (other is _ArticleDetailsState &&
121 | (identical(other.articleDetailsState, articleDetailsState) ||
122 | const DeepCollectionEquality()
123 | .equals(other.articleDetailsState, articleDetailsState)));
124 | }
125 |
126 | @override
127 | int get hashCode =>
128 | runtimeType.hashCode ^
129 | const DeepCollectionEquality().hash(articleDetailsState);
130 |
131 | @JsonKey(ignore: true)
132 | @override
133 | _$ArticleDetailsStateCopyWith<_ArticleDetailsState> get copyWith =>
134 | __$ArticleDetailsStateCopyWithImpl<_ArticleDetailsState>(
135 | this, _$identity);
136 | }
137 |
138 | abstract class _ArticleDetailsState implements ArticleDetailsState {
139 | const factory _ArticleDetailsState(
140 | {@required
141 | Option>>
142 | articleDetailsState}) = _$_ArticleDetailsState;
143 |
144 | @override
145 | Option>> get articleDetailsState;
146 | @override
147 | @JsonKey(ignore: true)
148 | _$ArticleDetailsStateCopyWith<_ArticleDetailsState> get copyWith;
149 | }
150 |
--------------------------------------------------------------------------------
/lib/src/application/article_details/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class ArticleDetailsState with _$ArticleDetailsState {
5 | const factory ArticleDetailsState({
6 | @required
7 | Option>> articleDetailsState,
8 | }) = _ArticleDetailsState;
9 |
10 | factory ArticleDetailsState.initial() => ArticleDetailsState(
11 | articleDetailsState: none(),
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/lib/src/application/articles/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/articles/i_articles_facade.dart';
7 | import '../../domain/entities/api_error/api_error.dart';
8 | import '../../domain/entities/api_response/api_response.dart';
9 | import '../../domain/entities/article/article.dart';
10 | import '../../domain/entities/category/category.dart';
11 |
12 | part 'bloc.freezed.dart';
13 |
14 | part 'state.dart';
15 |
16 | @injectable
17 | class ArticlesBloc extends Cubit {
18 | final IArticlesFacade _articlesFacade;
19 |
20 | ArticlesBloc(this._articlesFacade) : super(ArticlesState.initial());
21 |
22 | void getCategories() async {
23 | emit(state.copyWith(categoriesState: none()));
24 | final result = await _articlesFacade.getCategories();
25 | emit(state.copyWith(categoriesState: optionOf(result)));
26 | }
27 |
28 | void getArticles() async {
29 | emit(state.copyWith(articlesState: none()));
30 | final result = await _articlesFacade.getArticles();
31 | emit(state.copyWith(articlesState: optionOf(result)));
32 | }
33 |
34 | void getArticlesByCategory(String categoryId) async {
35 | emit(state.copyWith(articlesState: none()));
36 | final result = await _articlesFacade.getArticlesByCategory(categoryId);
37 | emit(state.copyWith(articlesState: optionOf(result)));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/application/articles/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class ArticlesState with _$ArticlesState {
5 | const factory ArticlesState({
6 | @required
7 | Option>>> categoriesState,
8 | @required
9 | Option>>> articlesState,
10 | }) = _ArticlesState;
11 |
12 | factory ArticlesState.initial() => ArticlesState(
13 | categoriesState: none(),
14 | articlesState: none(),
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/application/favorites/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../domain/entities/favorite/favorite.dart';
9 | import '../../domain/favorites/i_favorites_facade.dart';
10 |
11 | part 'bloc.freezed.dart';
12 |
13 | part 'state.dart';
14 |
15 | @injectable
16 | class FavoritesBloc extends Cubit {
17 | final IFavoritesFacade _favoritesFacade;
18 |
19 | FavoritesBloc(this._favoritesFacade) : super(FavoritesState.initial());
20 |
21 | void getFavorites(String userId) async {
22 | emit(state.copyWith(favoritesState: none()));
23 | final result = await _favoritesFacade.getFavorites(userId);
24 | emit(state.copyWith(favoritesState: optionOf(result)));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/application/favorites/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$FavoritesStateTearOff {
14 | const _$FavoritesStateTearOff();
15 |
16 | // ignore: unused_element
17 | _FavoritesState call(
18 | {@required
19 | Option>>>
20 | favoritesState}) {
21 | return _FavoritesState(
22 | favoritesState: favoritesState,
23 | );
24 | }
25 | }
26 |
27 | /// @nodoc
28 | // ignore: unused_element
29 | const $FavoritesState = _$FavoritesStateTearOff();
30 |
31 | /// @nodoc
32 | mixin _$FavoritesState {
33 | Option>>> get favoritesState;
34 |
35 | @JsonKey(ignore: true)
36 | $FavoritesStateCopyWith get copyWith;
37 | }
38 |
39 | /// @nodoc
40 | abstract class $FavoritesStateCopyWith<$Res> {
41 | factory $FavoritesStateCopyWith(
42 | FavoritesState value, $Res Function(FavoritesState) then) =
43 | _$FavoritesStateCopyWithImpl<$Res>;
44 | $Res call(
45 | {Option>>> favoritesState});
46 | }
47 |
48 | /// @nodoc
49 | class _$FavoritesStateCopyWithImpl<$Res>
50 | implements $FavoritesStateCopyWith<$Res> {
51 | _$FavoritesStateCopyWithImpl(this._value, this._then);
52 |
53 | final FavoritesState _value;
54 | // ignore: unused_field
55 | final $Res Function(FavoritesState) _then;
56 |
57 | @override
58 | $Res call({
59 | Object favoritesState = freezed,
60 | }) {
61 | return _then(_value.copyWith(
62 | favoritesState: favoritesState == freezed
63 | ? _value.favoritesState
64 | : favoritesState
65 | as Option>>>,
66 | ));
67 | }
68 | }
69 |
70 | /// @nodoc
71 | abstract class _$FavoritesStateCopyWith<$Res>
72 | implements $FavoritesStateCopyWith<$Res> {
73 | factory _$FavoritesStateCopyWith(
74 | _FavoritesState value, $Res Function(_FavoritesState) then) =
75 | __$FavoritesStateCopyWithImpl<$Res>;
76 | @override
77 | $Res call(
78 | {Option>>> favoritesState});
79 | }
80 |
81 | /// @nodoc
82 | class __$FavoritesStateCopyWithImpl<$Res>
83 | extends _$FavoritesStateCopyWithImpl<$Res>
84 | implements _$FavoritesStateCopyWith<$Res> {
85 | __$FavoritesStateCopyWithImpl(
86 | _FavoritesState _value, $Res Function(_FavoritesState) _then)
87 | : super(_value, (v) => _then(v as _FavoritesState));
88 |
89 | @override
90 | _FavoritesState get _value => super._value as _FavoritesState;
91 |
92 | @override
93 | $Res call({
94 | Object favoritesState = freezed,
95 | }) {
96 | return _then(_FavoritesState(
97 | favoritesState: favoritesState == freezed
98 | ? _value.favoritesState
99 | : favoritesState
100 | as Option>>>,
101 | ));
102 | }
103 | }
104 |
105 | /// @nodoc
106 | class _$_FavoritesState implements _FavoritesState {
107 | const _$_FavoritesState({@required this.favoritesState})
108 | : assert(favoritesState != null);
109 |
110 | @override
111 | final Option>>> favoritesState;
112 |
113 | @override
114 | String toString() {
115 | return 'FavoritesState(favoritesState: $favoritesState)';
116 | }
117 |
118 | @override
119 | bool operator ==(dynamic other) {
120 | return identical(this, other) ||
121 | (other is _FavoritesState &&
122 | (identical(other.favoritesState, favoritesState) ||
123 | const DeepCollectionEquality()
124 | .equals(other.favoritesState, favoritesState)));
125 | }
126 |
127 | @override
128 | int get hashCode =>
129 | runtimeType.hashCode ^
130 | const DeepCollectionEquality().hash(favoritesState);
131 |
132 | @JsonKey(ignore: true)
133 | @override
134 | _$FavoritesStateCopyWith<_FavoritesState> get copyWith =>
135 | __$FavoritesStateCopyWithImpl<_FavoritesState>(this, _$identity);
136 | }
137 |
138 | abstract class _FavoritesState implements FavoritesState {
139 | const factory _FavoritesState(
140 | {@required
141 | Option>>>
142 | favoritesState}) = _$_FavoritesState;
143 |
144 | @override
145 | Option>>> get favoritesState;
146 | @override
147 | @JsonKey(ignore: true)
148 | _$FavoritesStateCopyWith<_FavoritesState> get copyWith;
149 | }
150 |
--------------------------------------------------------------------------------
/lib/src/application/favorites/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class FavoritesState with _$FavoritesState {
5 | const factory FavoritesState({
6 | @required
7 | Option>>> favoritesState,
8 | }) = _FavoritesState;
9 |
10 | factory FavoritesState.initial() => FavoritesState(
11 | favoritesState: none(),
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/lib/src/application/profile/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../domain/entities/article/article.dart';
9 | import '../../domain/entities/user/user.dart';
10 | import '../../domain/profile/i_profile_facade.dart';
11 |
12 | part 'bloc.freezed.dart';
13 |
14 | part 'state.dart';
15 |
16 | @injectable
17 | class ProfileBloc extends Cubit {
18 | final IProfileFacade _profileFacade;
19 |
20 | ProfileBloc(this._profileFacade) : super(ProfileState.initial());
21 |
22 | void getUserInformation(String userId) async {
23 | emit(state.copyWith(userInformationState: none()));
24 | final result = await _profileFacade.getUserInformation(userId);
25 | emit(state.copyWith(userInformationState: optionOf(result)));
26 | }
27 |
28 | void getArticlesByUser(String userId) async {
29 | emit(state.copyWith(articlesState: none()));
30 | final result = await _profileFacade.getArticlesByUser(userId);
31 | emit(state.copyWith(articlesState: optionOf(result)));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/src/application/profile/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$ProfileStateTearOff {
14 | const _$ProfileStateTearOff();
15 |
16 | // ignore: unused_element
17 | _ProfileState call(
18 | {@required
19 | Option>> userInformationState,
20 | @required
21 | Option>>> articlesState}) {
22 | return _ProfileState(
23 | userInformationState: userInformationState,
24 | articlesState: articlesState,
25 | );
26 | }
27 | }
28 |
29 | /// @nodoc
30 | // ignore: unused_element
31 | const $ProfileState = _$ProfileStateTearOff();
32 |
33 | /// @nodoc
34 | mixin _$ProfileState {
35 | Option>> get userInformationState;
36 | Option>>> get articlesState;
37 |
38 | @JsonKey(ignore: true)
39 | $ProfileStateCopyWith get copyWith;
40 | }
41 |
42 | /// @nodoc
43 | abstract class $ProfileStateCopyWith<$Res> {
44 | factory $ProfileStateCopyWith(
45 | ProfileState value, $Res Function(ProfileState) then) =
46 | _$ProfileStateCopyWithImpl<$Res>;
47 | $Res call(
48 | {Option>> userInformationState,
49 | Option>>> articlesState});
50 | }
51 |
52 | /// @nodoc
53 | class _$ProfileStateCopyWithImpl<$Res> implements $ProfileStateCopyWith<$Res> {
54 | _$ProfileStateCopyWithImpl(this._value, this._then);
55 |
56 | final ProfileState _value;
57 | // ignore: unused_field
58 | final $Res Function(ProfileState) _then;
59 |
60 | @override
61 | $Res call({
62 | Object userInformationState = freezed,
63 | Object articlesState = freezed,
64 | }) {
65 | return _then(_value.copyWith(
66 | userInformationState: userInformationState == freezed
67 | ? _value.userInformationState
68 | : userInformationState as Option>>,
69 | articlesState: articlesState == freezed
70 | ? _value.articlesState
71 | : articlesState
72 | as Option>>>,
73 | ));
74 | }
75 | }
76 |
77 | /// @nodoc
78 | abstract class _$ProfileStateCopyWith<$Res>
79 | implements $ProfileStateCopyWith<$Res> {
80 | factory _$ProfileStateCopyWith(
81 | _ProfileState value, $Res Function(_ProfileState) then) =
82 | __$ProfileStateCopyWithImpl<$Res>;
83 | @override
84 | $Res call(
85 | {Option>> userInformationState,
86 | Option>>> articlesState});
87 | }
88 |
89 | /// @nodoc
90 | class __$ProfileStateCopyWithImpl<$Res> extends _$ProfileStateCopyWithImpl<$Res>
91 | implements _$ProfileStateCopyWith<$Res> {
92 | __$ProfileStateCopyWithImpl(
93 | _ProfileState _value, $Res Function(_ProfileState) _then)
94 | : super(_value, (v) => _then(v as _ProfileState));
95 |
96 | @override
97 | _ProfileState get _value => super._value as _ProfileState;
98 |
99 | @override
100 | $Res call({
101 | Object userInformationState = freezed,
102 | Object articlesState = freezed,
103 | }) {
104 | return _then(_ProfileState(
105 | userInformationState: userInformationState == freezed
106 | ? _value.userInformationState
107 | : userInformationState as Option>>,
108 | articlesState: articlesState == freezed
109 | ? _value.articlesState
110 | : articlesState
111 | as Option>>>,
112 | ));
113 | }
114 | }
115 |
116 | /// @nodoc
117 | class _$_ProfileState implements _ProfileState {
118 | const _$_ProfileState(
119 | {@required this.userInformationState, @required this.articlesState})
120 | : assert(userInformationState != null),
121 | assert(articlesState != null);
122 |
123 | @override
124 | final Option>> userInformationState;
125 | @override
126 | final Option>>> articlesState;
127 |
128 | @override
129 | String toString() {
130 | return 'ProfileState(userInformationState: $userInformationState, articlesState: $articlesState)';
131 | }
132 |
133 | @override
134 | bool operator ==(dynamic other) {
135 | return identical(this, other) ||
136 | (other is _ProfileState &&
137 | (identical(other.userInformationState, userInformationState) ||
138 | const DeepCollectionEquality().equals(
139 | other.userInformationState, userInformationState)) &&
140 | (identical(other.articlesState, articlesState) ||
141 | const DeepCollectionEquality()
142 | .equals(other.articlesState, articlesState)));
143 | }
144 |
145 | @override
146 | int get hashCode =>
147 | runtimeType.hashCode ^
148 | const DeepCollectionEquality().hash(userInformationState) ^
149 | const DeepCollectionEquality().hash(articlesState);
150 |
151 | @JsonKey(ignore: true)
152 | @override
153 | _$ProfileStateCopyWith<_ProfileState> get copyWith =>
154 | __$ProfileStateCopyWithImpl<_ProfileState>(this, _$identity);
155 | }
156 |
157 | abstract class _ProfileState implements ProfileState {
158 | const factory _ProfileState(
159 | {@required
160 | Option>> userInformationState,
161 | @required
162 | Option>>>
163 | articlesState}) = _$_ProfileState;
164 |
165 | @override
166 | Option>> get userInformationState;
167 | @override
168 | Option>>> get articlesState;
169 | @override
170 | @JsonKey(ignore: true)
171 | _$ProfileStateCopyWith<_ProfileState> get copyWith;
172 | }
173 |
--------------------------------------------------------------------------------
/lib/src/application/profile/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class ProfileState with _$ProfileState {
5 | const factory ProfileState({
6 | @required Option>> userInformationState,
7 | @required
8 | Option>>> articlesState,
9 | }) = _ProfileState;
10 |
11 | factory ProfileState.initial() => ProfileState(
12 | userInformationState: none(),
13 | articlesState: none(),
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/application/search/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../domain/entities/article/article.dart';
9 | import '../../domain/search/i_search_facade.dart';
10 |
11 | part 'bloc.freezed.dart';
12 |
13 | part 'state.dart';
14 |
15 | @injectable
16 | class SearchBloc extends Cubit {
17 | final ISearchFacade _searchFacade;
18 |
19 | SearchBloc(this._searchFacade) : super(SearchState.initial());
20 |
21 | void searchArticles(String query) async {
22 | emit(state.copyWith(
23 | searchState: none(),
24 | isSearching: true,
25 | ));
26 | final result = await _searchFacade.searchArticles(query);
27 | emit(state.copyWith(
28 | searchState: optionOf(result),
29 | isSearching: false,
30 | ));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/src/application/search/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$SearchStateTearOff {
14 | const _$SearchStateTearOff();
15 |
16 | // ignore: unused_element
17 | _SearchState call(
18 | {@required
19 | Option>>> searchState,
20 | @required
21 | bool isSearching}) {
22 | return _SearchState(
23 | searchState: searchState,
24 | isSearching: isSearching,
25 | );
26 | }
27 | }
28 |
29 | /// @nodoc
30 | // ignore: unused_element
31 | const $SearchState = _$SearchStateTearOff();
32 |
33 | /// @nodoc
34 | mixin _$SearchState {
35 | Option>>> get searchState;
36 | bool get isSearching;
37 |
38 | @JsonKey(ignore: true)
39 | $SearchStateCopyWith get copyWith;
40 | }
41 |
42 | /// @nodoc
43 | abstract class $SearchStateCopyWith<$Res> {
44 | factory $SearchStateCopyWith(
45 | SearchState value, $Res Function(SearchState) then) =
46 | _$SearchStateCopyWithImpl<$Res>;
47 | $Res call(
48 | {Option>>> searchState,
49 | bool isSearching});
50 | }
51 |
52 | /// @nodoc
53 | class _$SearchStateCopyWithImpl<$Res> implements $SearchStateCopyWith<$Res> {
54 | _$SearchStateCopyWithImpl(this._value, this._then);
55 |
56 | final SearchState _value;
57 | // ignore: unused_field
58 | final $Res Function(SearchState) _then;
59 |
60 | @override
61 | $Res call({
62 | Object searchState = freezed,
63 | Object isSearching = freezed,
64 | }) {
65 | return _then(_value.copyWith(
66 | searchState: searchState == freezed
67 | ? _value.searchState
68 | : searchState as Option>>>,
69 | isSearching:
70 | isSearching == freezed ? _value.isSearching : isSearching as bool,
71 | ));
72 | }
73 | }
74 |
75 | /// @nodoc
76 | abstract class _$SearchStateCopyWith<$Res>
77 | implements $SearchStateCopyWith<$Res> {
78 | factory _$SearchStateCopyWith(
79 | _SearchState value, $Res Function(_SearchState) then) =
80 | __$SearchStateCopyWithImpl<$Res>;
81 | @override
82 | $Res call(
83 | {Option>>> searchState,
84 | bool isSearching});
85 | }
86 |
87 | /// @nodoc
88 | class __$SearchStateCopyWithImpl<$Res> extends _$SearchStateCopyWithImpl<$Res>
89 | implements _$SearchStateCopyWith<$Res> {
90 | __$SearchStateCopyWithImpl(
91 | _SearchState _value, $Res Function(_SearchState) _then)
92 | : super(_value, (v) => _then(v as _SearchState));
93 |
94 | @override
95 | _SearchState get _value => super._value as _SearchState;
96 |
97 | @override
98 | $Res call({
99 | Object searchState = freezed,
100 | Object isSearching = freezed,
101 | }) {
102 | return _then(_SearchState(
103 | searchState: searchState == freezed
104 | ? _value.searchState
105 | : searchState as Option>>>,
106 | isSearching:
107 | isSearching == freezed ? _value.isSearching : isSearching as bool,
108 | ));
109 | }
110 | }
111 |
112 | /// @nodoc
113 | class _$_SearchState implements _SearchState {
114 | const _$_SearchState({@required this.searchState, @required this.isSearching})
115 | : assert(searchState != null),
116 | assert(isSearching != null);
117 |
118 | @override
119 | final Option>>> searchState;
120 | @override
121 | final bool isSearching;
122 |
123 | @override
124 | String toString() {
125 | return 'SearchState(searchState: $searchState, isSearching: $isSearching)';
126 | }
127 |
128 | @override
129 | bool operator ==(dynamic other) {
130 | return identical(this, other) ||
131 | (other is _SearchState &&
132 | (identical(other.searchState, searchState) ||
133 | const DeepCollectionEquality()
134 | .equals(other.searchState, searchState)) &&
135 | (identical(other.isSearching, isSearching) ||
136 | const DeepCollectionEquality()
137 | .equals(other.isSearching, isSearching)));
138 | }
139 |
140 | @override
141 | int get hashCode =>
142 | runtimeType.hashCode ^
143 | const DeepCollectionEquality().hash(searchState) ^
144 | const DeepCollectionEquality().hash(isSearching);
145 |
146 | @JsonKey(ignore: true)
147 | @override
148 | _$SearchStateCopyWith<_SearchState> get copyWith =>
149 | __$SearchStateCopyWithImpl<_SearchState>(this, _$identity);
150 | }
151 |
152 | abstract class _SearchState implements SearchState {
153 | const factory _SearchState(
154 | {@required
155 | Option>>> searchState,
156 | @required
157 | bool isSearching}) = _$_SearchState;
158 |
159 | @override
160 | Option>>> get searchState;
161 | @override
162 | bool get isSearching;
163 | @override
164 | @JsonKey(ignore: true)
165 | _$SearchStateCopyWith<_SearchState> get copyWith;
166 | }
167 |
--------------------------------------------------------------------------------
/lib/src/application/search/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class SearchState with _$SearchState {
5 | const factory SearchState({
6 | @required Option>>> searchState,
7 | @required bool isSearching,
8 | }) = _SearchState;
9 |
10 | factory SearchState.initial() => SearchState(
11 | searchState: none(),
12 | isSearching: false,
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/application/sign_in/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../domain/entities/user/user.dart';
9 | import '../../domain/sign_in/i_sign_in_facade.dart';
10 |
11 | part 'bloc.freezed.dart';
12 |
13 | part 'state.dart';
14 |
15 | @injectable
16 | class SignInBloc extends Cubit {
17 | final ISignInFacade _signInFacade;
18 |
19 | SignInBloc(this._signInFacade) : super(SignInState.initial());
20 |
21 | void signIn(User user) async {
22 | emit(SignInState(signInState: none()));
23 | final result = await _signInFacade.signIn(user: user);
24 | emit(SignInState(signInState: optionOf(result)));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/application/sign_in/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$SignInStateTearOff {
14 | const _$SignInStateTearOff();
15 |
16 | // ignore: unused_element
17 | _SignInState call(
18 | {@required Option>> signInState}) {
19 | return _SignInState(
20 | signInState: signInState,
21 | );
22 | }
23 | }
24 |
25 | /// @nodoc
26 | // ignore: unused_element
27 | const $SignInState = _$SignInStateTearOff();
28 |
29 | /// @nodoc
30 | mixin _$SignInState {
31 | Option>> get signInState;
32 |
33 | @JsonKey(ignore: true)
34 | $SignInStateCopyWith get copyWith;
35 | }
36 |
37 | /// @nodoc
38 | abstract class $SignInStateCopyWith<$Res> {
39 | factory $SignInStateCopyWith(
40 | SignInState value, $Res Function(SignInState) then) =
41 | _$SignInStateCopyWithImpl<$Res>;
42 | $Res call({Option>> signInState});
43 | }
44 |
45 | /// @nodoc
46 | class _$SignInStateCopyWithImpl<$Res> implements $SignInStateCopyWith<$Res> {
47 | _$SignInStateCopyWithImpl(this._value, this._then);
48 |
49 | final SignInState _value;
50 | // ignore: unused_field
51 | final $Res Function(SignInState) _then;
52 |
53 | @override
54 | $Res call({
55 | Object signInState = freezed,
56 | }) {
57 | return _then(_value.copyWith(
58 | signInState: signInState == freezed
59 | ? _value.signInState
60 | : signInState as Option>>,
61 | ));
62 | }
63 | }
64 |
65 | /// @nodoc
66 | abstract class _$SignInStateCopyWith<$Res>
67 | implements $SignInStateCopyWith<$Res> {
68 | factory _$SignInStateCopyWith(
69 | _SignInState value, $Res Function(_SignInState) then) =
70 | __$SignInStateCopyWithImpl<$Res>;
71 | @override
72 | $Res call({Option>> signInState});
73 | }
74 |
75 | /// @nodoc
76 | class __$SignInStateCopyWithImpl<$Res> extends _$SignInStateCopyWithImpl<$Res>
77 | implements _$SignInStateCopyWith<$Res> {
78 | __$SignInStateCopyWithImpl(
79 | _SignInState _value, $Res Function(_SignInState) _then)
80 | : super(_value, (v) => _then(v as _SignInState));
81 |
82 | @override
83 | _SignInState get _value => super._value as _SignInState;
84 |
85 | @override
86 | $Res call({
87 | Object signInState = freezed,
88 | }) {
89 | return _then(_SignInState(
90 | signInState: signInState == freezed
91 | ? _value.signInState
92 | : signInState as Option>>,
93 | ));
94 | }
95 | }
96 |
97 | /// @nodoc
98 | class _$_SignInState implements _SignInState {
99 | const _$_SignInState({@required this.signInState})
100 | : assert(signInState != null);
101 |
102 | @override
103 | final Option>> signInState;
104 |
105 | @override
106 | String toString() {
107 | return 'SignInState(signInState: $signInState)';
108 | }
109 |
110 | @override
111 | bool operator ==(dynamic other) {
112 | return identical(this, other) ||
113 | (other is _SignInState &&
114 | (identical(other.signInState, signInState) ||
115 | const DeepCollectionEquality()
116 | .equals(other.signInState, signInState)));
117 | }
118 |
119 | @override
120 | int get hashCode =>
121 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(signInState);
122 |
123 | @JsonKey(ignore: true)
124 | @override
125 | _$SignInStateCopyWith<_SignInState> get copyWith =>
126 | __$SignInStateCopyWithImpl<_SignInState>(this, _$identity);
127 | }
128 |
129 | abstract class _SignInState implements SignInState {
130 | const factory _SignInState(
131 | {@required Option>> signInState}) =
132 | _$_SignInState;
133 |
134 | @override
135 | Option>> get signInState;
136 | @override
137 | @JsonKey(ignore: true)
138 | _$SignInStateCopyWith<_SignInState> get copyWith;
139 | }
140 |
--------------------------------------------------------------------------------
/lib/src/application/sign_in/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class SignInState with _$SignInState {
5 | const factory SignInState({
6 | @required Option>> signInState,
7 | }) = _SignInState;
8 |
9 | factory SignInState.initial() => SignInState(
10 | signInState: none(),
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/application/sign_up/bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:injectable/injectable.dart';
5 |
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../domain/entities/user/user.dart';
9 | import '../../domain/sign_up/i_sign_up_facade.dart';
10 |
11 | part 'bloc.freezed.dart';
12 |
13 | part 'state.dart';
14 |
15 | @injectable
16 | class SignUpBloc extends Cubit {
17 | final ISignUpFacade _signUpFacade;
18 |
19 | SignUpBloc(this._signUpFacade) : super(SignUpState.initial());
20 |
21 | void signUp(User user) async {
22 | emit(SignUpState(signUpState: none()));
23 | final result = await _signUpFacade.signUp(user: user);
24 | emit(SignUpState(signUpState: optionOf(result)));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/application/sign_up/bloc.freezed.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies
3 |
4 | part of 'bloc.dart';
5 |
6 | // **************************************************************************
7 | // FreezedGenerator
8 | // **************************************************************************
9 |
10 | T _$identity(T value) => value;
11 |
12 | /// @nodoc
13 | class _$SignUpStateTearOff {
14 | const _$SignUpStateTearOff();
15 |
16 | // ignore: unused_element
17 | _SignUpState call(
18 | {@required Option>> signUpState}) {
19 | return _SignUpState(
20 | signUpState: signUpState,
21 | );
22 | }
23 | }
24 |
25 | /// @nodoc
26 | // ignore: unused_element
27 | const $SignUpState = _$SignUpStateTearOff();
28 |
29 | /// @nodoc
30 | mixin _$SignUpState {
31 | Option>> get signUpState;
32 |
33 | @JsonKey(ignore: true)
34 | $SignUpStateCopyWith get copyWith;
35 | }
36 |
37 | /// @nodoc
38 | abstract class $SignUpStateCopyWith<$Res> {
39 | factory $SignUpStateCopyWith(
40 | SignUpState value, $Res Function(SignUpState) then) =
41 | _$SignUpStateCopyWithImpl<$Res>;
42 | $Res call({Option>> signUpState});
43 | }
44 |
45 | /// @nodoc
46 | class _$SignUpStateCopyWithImpl<$Res> implements $SignUpStateCopyWith<$Res> {
47 | _$SignUpStateCopyWithImpl(this._value, this._then);
48 |
49 | final SignUpState _value;
50 | // ignore: unused_field
51 | final $Res Function(SignUpState) _then;
52 |
53 | @override
54 | $Res call({
55 | Object signUpState = freezed,
56 | }) {
57 | return _then(_value.copyWith(
58 | signUpState: signUpState == freezed
59 | ? _value.signUpState
60 | : signUpState as Option>>,
61 | ));
62 | }
63 | }
64 |
65 | /// @nodoc
66 | abstract class _$SignUpStateCopyWith<$Res>
67 | implements $SignUpStateCopyWith<$Res> {
68 | factory _$SignUpStateCopyWith(
69 | _SignUpState value, $Res Function(_SignUpState) then) =
70 | __$SignUpStateCopyWithImpl<$Res>;
71 | @override
72 | $Res call({Option>> signUpState});
73 | }
74 |
75 | /// @nodoc
76 | class __$SignUpStateCopyWithImpl<$Res> extends _$SignUpStateCopyWithImpl<$Res>
77 | implements _$SignUpStateCopyWith<$Res> {
78 | __$SignUpStateCopyWithImpl(
79 | _SignUpState _value, $Res Function(_SignUpState) _then)
80 | : super(_value, (v) => _then(v as _SignUpState));
81 |
82 | @override
83 | _SignUpState get _value => super._value as _SignUpState;
84 |
85 | @override
86 | $Res call({
87 | Object signUpState = freezed,
88 | }) {
89 | return _then(_SignUpState(
90 | signUpState: signUpState == freezed
91 | ? _value.signUpState
92 | : signUpState as Option>>,
93 | ));
94 | }
95 | }
96 |
97 | /// @nodoc
98 | class _$_SignUpState implements _SignUpState {
99 | const _$_SignUpState({@required this.signUpState})
100 | : assert(signUpState != null);
101 |
102 | @override
103 | final Option>> signUpState;
104 |
105 | @override
106 | String toString() {
107 | return 'SignUpState(signUpState: $signUpState)';
108 | }
109 |
110 | @override
111 | bool operator ==(dynamic other) {
112 | return identical(this, other) ||
113 | (other is _SignUpState &&
114 | (identical(other.signUpState, signUpState) ||
115 | const DeepCollectionEquality()
116 | .equals(other.signUpState, signUpState)));
117 | }
118 |
119 | @override
120 | int get hashCode =>
121 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(signUpState);
122 |
123 | @JsonKey(ignore: true)
124 | @override
125 | _$SignUpStateCopyWith<_SignUpState> get copyWith =>
126 | __$SignUpStateCopyWithImpl<_SignUpState>(this, _$identity);
127 | }
128 |
129 | abstract class _SignUpState implements SignUpState {
130 | const factory _SignUpState(
131 | {@required Option>> signUpState}) =
132 | _$_SignUpState;
133 |
134 | @override
135 | Option>> get signUpState;
136 | @override
137 | @JsonKey(ignore: true)
138 | _$SignUpStateCopyWith<_SignUpState> get copyWith;
139 | }
140 |
--------------------------------------------------------------------------------
/lib/src/application/sign_up/state.dart:
--------------------------------------------------------------------------------
1 | part of 'bloc.dart';
2 |
3 | @freezed
4 | abstract class SignUpState with _$SignUpState {
5 | const factory SignUpState({
6 | @required Option>> signUpState,
7 | }) = _SignUpState;
8 |
9 | factory SignUpState.initial() => SignUpState(
10 | signUpState: none(),
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/domain/article_create/i_article_create_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:dio/dio.dart';
3 |
4 | import '../entities/api_error/api_error.dart';
5 | import '../entities/api_response/api_response.dart';
6 |
7 | abstract class IArticleCreateFacade {
8 | Future> createArticle(FormData data);
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/domain/article_details/i_article_details_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 |
3 | import '../entities/api_error/api_error.dart';
4 | import '../entities/api_response/api_response.dart';
5 | import '../entities/article/article.dart';
6 |
7 | abstract class IArticleDetailsFacade {
8 | Future>> getArticleDetails(
9 | String articleId, {
10 | String userId,
11 | });
12 |
13 | Future> addFavorite(
14 | String articleId,
15 | String userId,
16 | );
17 |
18 | Future> removeFavorite(
19 | String articleId,
20 | String userId,
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/domain/articles/i_articles_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 |
3 | import '../entities/api_error/api_error.dart';
4 | import '../entities/api_response/api_response.dart';
5 | import '../entities/article/article.dart';
6 | import '../entities/category/category.dart';
7 |
8 | abstract class IArticlesFacade {
9 | Future>>> getCategories();
10 |
11 | Future>>> getArticles();
12 |
13 | Future>>> getArticlesByCategory(String categoryId);
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/api_error/api_error.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'api_error.g.dart';
4 |
5 | @JsonSerializable()
6 | class ApiError {
7 | final String message;
8 |
9 | ApiError({
10 | this.message,
11 | });
12 |
13 | factory ApiError.fromJson(Map json) =>
14 | _$ApiErrorFromJson(json);
15 |
16 | Map toJson() => _$ApiErrorToJson(this);
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/api_error/api_error.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'api_error.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | ApiError _$ApiErrorFromJson(Map json) {
10 | return ApiError(
11 | message: json['message'] as String,
12 | );
13 | }
14 |
15 | Map _$ApiErrorToJson(ApiError instance) => {
16 | 'message': instance.message,
17 | };
18 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/api_response/api_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'api_response.g.dart';
4 |
5 | @JsonSerializable(genericArgumentFactories: true)
6 | class ApiResponse {
7 | final bool status;
8 | final String message;
9 | final T data;
10 |
11 | ApiResponse({
12 | this.status,
13 | this.message,
14 | this.data,
15 | });
16 |
17 | factory ApiResponse.fromJson(
18 | Map json,
19 | T Function(Object json) fromJsonT,
20 | ) =>
21 | _$ApiResponseFromJson(json, fromJsonT);
22 |
23 | Map toJson(Object Function(T value) toJsonT) =>
24 | _$ApiResponseToJson(this, toJsonT);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/api_response/api_response.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'api_response.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | ApiResponse _$ApiResponseFromJson(
10 | Map json,
11 | T Function(Object json) fromJsonT,
12 | ) {
13 | return ApiResponse(
14 | status: json['status'] as bool,
15 | message: json['message'] as String,
16 | data: fromJsonT(json['data']),
17 | );
18 | }
19 |
20 | Map _$ApiResponseToJson(
21 | ApiResponse instance,
22 | Object Function(T value) toJsonT,
23 | ) =>
24 | {
25 | 'status': instance.status,
26 | 'message': instance.message,
27 | 'data': toJsonT(instance.data),
28 | };
29 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/article/article.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'article.g.dart';
4 |
5 | @JsonSerializable()
6 | class Article {
7 | @JsonKey(name: '_id')
8 | final String id;
9 | final String title;
10 | final String content;
11 | @JsonKey(name: 'user_id')
12 | final String userId;
13 | @JsonKey(name: 'category_id')
14 | final String categoryId;
15 | @JsonKey(name: 'created_at')
16 | final String createdAt;
17 | final List images;
18 | final bool isFavorite;
19 |
20 | Article({
21 | this.id,
22 | this.title,
23 | this.content,
24 | this.userId,
25 | this.categoryId,
26 | this.createdAt,
27 | this.images,
28 | this.isFavorite,
29 | });
30 |
31 | factory Article.fromJson(Map json) =>
32 | _$ArticleFromJson(json);
33 |
34 | Map toJson() => _$ArticleToJson(this);
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/article/article.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'article.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Article _$ArticleFromJson(Map json) {
10 | return Article(
11 | id: json['_id'] as String,
12 | title: json['title'] as String,
13 | content: json['content'] as String,
14 | userId: json['user_id'] as String,
15 | categoryId: json['category_id'] as String,
16 | createdAt: json['created_at'] as String,
17 | images: (json['images'] as List)?.map((e) => e as String)?.toList(),
18 | isFavorite: json['isFavorite'] as bool,
19 | );
20 | }
21 |
22 | Map _$ArticleToJson(Article instance) => {
23 | '_id': instance.id,
24 | 'title': instance.title,
25 | 'content': instance.content,
26 | 'user_id': instance.userId,
27 | 'category_id': instance.categoryId,
28 | 'created_at': instance.createdAt,
29 | 'images': instance.images,
30 | 'isFavorite': instance.isFavorite,
31 | };
32 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/category/category.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'category.g.dart';
4 |
5 | @JsonSerializable()
6 | class Category {
7 | @JsonKey(name: '_id')
8 | final String id;
9 | final String name;
10 |
11 | Category({
12 | this.id,
13 | this.name,
14 | });
15 |
16 | factory Category.fromJson(Map json) =>
17 | _$CategoryFromJson(json);
18 |
19 | Map toJson() => _$CategoryToJson(this);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/category/category.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'category.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Category _$CategoryFromJson(Map json) {
10 | return Category(
11 | id: json['_id'] as String,
12 | name: json['name'] as String,
13 | );
14 | }
15 |
16 | Map _$CategoryToJson(Category instance) => {
17 | '_id': instance.id,
18 | 'name': instance.name,
19 | };
20 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/favorite/favorite.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | import '../article/article.dart';
4 |
5 | part 'favorite.g.dart';
6 |
7 | @JsonSerializable()
8 | class Favorite {
9 | @JsonKey(name: '_id')
10 | final String id;
11 | @JsonKey(name: 'article_id')
12 | final String articleId;
13 | @JsonKey(name: 'user_id')
14 | final String userId;
15 | final Article article;
16 |
17 | Favorite({
18 | this.id,
19 | this.articleId,
20 | this.userId,
21 | this.article,
22 | });
23 |
24 | factory Favorite.fromJson(Map json) =>
25 | _$FavoriteFromJson(json);
26 |
27 | Map toJson() => _$FavoriteToJson(this);
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/favorite/favorite.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'favorite.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Favorite _$FavoriteFromJson(Map json) {
10 | return Favorite(
11 | id: json['_id'] as String,
12 | articleId: json['article_id'] as String,
13 | userId: json['user_id'] as String,
14 | article: json['article'] == null
15 | ? null
16 | : Article.fromJson(json['article'] as Map),
17 | );
18 | }
19 |
20 | Map _$FavoriteToJson(Favorite instance) => {
21 | '_id': instance.id,
22 | 'article_id': instance.articleId,
23 | 'user_id': instance.userId,
24 | 'article': instance.article,
25 | };
26 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/user/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'user.g.dart';
4 |
5 | @JsonSerializable()
6 | class User {
7 | @JsonKey(name: '_id')
8 | final String id;
9 | @JsonKey(name: 'first_name')
10 | final String firstName;
11 | @JsonKey(name: 'last_name')
12 | final String lastName;
13 | final String email;
14 | final String password;
15 | final String token;
16 |
17 | User({
18 | this.id,
19 | this.firstName,
20 | this.lastName,
21 | this.email,
22 | this.password,
23 | this.token,
24 | });
25 |
26 | factory User.fromJson(Map json) => _$UserFromJson(json);
27 |
28 | Map toJson() => _$UserToJson(this);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/src/domain/entities/user/user.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'user.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | User _$UserFromJson(Map json) {
10 | return User(
11 | id: json['_id'] as String,
12 | firstName: json['first_name'] as String,
13 | lastName: json['last_name'] as String,
14 | email: json['email'] as String,
15 | password: json['password'] as String,
16 | token: json['token'] as String,
17 | );
18 | }
19 |
20 | Map _$UserToJson(User instance) => {
21 | '_id': instance.id,
22 | 'first_name': instance.firstName,
23 | 'last_name': instance.lastName,
24 | 'email': instance.email,
25 | 'password': instance.password,
26 | 'token': instance.token,
27 | };
28 |
--------------------------------------------------------------------------------
/lib/src/domain/favorites/i_favorites_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 |
3 | import '../entities/api_error/api_error.dart';
4 | import '../entities/api_response/api_response.dart';
5 | import '../entities/favorite/favorite.dart';
6 |
7 | abstract class IFavoritesFacade {
8 | Future>>> getFavorites(
9 | String userId,
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/domain/profile/i_profile_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 |
3 | import '../../domain/entities/user/user.dart';
4 | import '../entities/api_error/api_error.dart';
5 | import '../entities/api_response/api_response.dart';
6 | import '../entities/article/article.dart';
7 |
8 | abstract class IProfileFacade {
9 | Future>> getUserInformation(
10 | String userId,
11 | );
12 |
13 | Future>>> getArticlesByUser(
14 | String userId,
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/domain/search/i_search_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 |
3 | import '../entities/api_error/api_error.dart';
4 | import '../entities/api_response/api_response.dart';
5 | import '../entities/article/article.dart';
6 |
7 | abstract class ISearchFacade {
8 | Future>>> searchArticles(
9 | String query);
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/domain/sign_in/i_sign_in_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | import '../entities/api_error/api_error.dart';
5 | import '../entities/api_response/api_response.dart';
6 | import '../entities/user/user.dart';
7 |
8 | abstract class ISignInFacade {
9 | Future>> signIn({@required User user});
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/domain/sign_up/i_sign_up_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | import '../entities/api_error/api_error.dart';
5 | import '../entities/api_response/api_response.dart';
6 | import '../entities/user/user.dart';
7 |
8 | abstract class ISignUpFacade {
9 | Future>> signUp({@required User user});
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/article_create/article_create_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:dio/dio.dart';
3 | import 'package:injectable/injectable.dart';
4 |
5 | import '../../domain/article_create/i_article_create_facade.dart';
6 | import '../../domain/entities/api_error/api_error.dart';
7 | import '../../domain/entities/api_response/api_response.dart';
8 | import '../../injection.dart';
9 | import '../core/network/network.dart';
10 |
11 | @LazySingleton(as: IArticleCreateFacade)
12 | class ArticlesDetailsFacade implements IArticleCreateFacade {
13 | @override
14 | Future> createArticle(FormData data) async {
15 | try {
16 | final apiResponse = await getIt().createArticle(data);
17 | if (apiResponse.status) {
18 | return right(apiResponse);
19 | } else {
20 | return left(
21 | ApiError(message: apiResponse.message),
22 | );
23 | }
24 | } catch (e) {
25 | return left(
26 | ApiError(message: '$e'),
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/article_details/article_details_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/article_details/i_article_details_facade.dart';
5 | import '../../domain/entities/api_error/api_error.dart';
6 | import '../../domain/entities/api_response/api_response.dart';
7 | import '../../domain/entities/article/article.dart';
8 | import '../../domain/entities/favorite/favorite.dart';
9 | import '../../injection.dart';
10 | import '../core/network/network.dart';
11 |
12 | @LazySingleton(as: IArticleDetailsFacade)
13 | class ArticlesDetailsFacade implements IArticleDetailsFacade {
14 | @override
15 | Future>> getArticleDetails(
16 | String id, {
17 | String userId,
18 | }) async {
19 | try {
20 | final apiResponse =
21 | await getIt().getArticlesById(id, userId: userId);
22 | if (apiResponse.status) {
23 | return right(apiResponse);
24 | } else {
25 | return left(
26 | ApiError(message: apiResponse.message),
27 | );
28 | }
29 | } catch (e) {
30 | return left(
31 | ApiError(message: '$e'),
32 | );
33 | }
34 | }
35 |
36 | @override
37 | Future> addFavorite(
38 | String articleId,
39 | String userId,
40 | ) async {
41 | try {
42 | final favorite = Favorite(articleId: articleId, userId: userId);
43 | final apiResponse = await getIt().addFavorite(favorite);
44 | if (apiResponse.status) {
45 | return right(apiResponse);
46 | } else {
47 | return left(
48 | ApiError(message: apiResponse.message),
49 | );
50 | }
51 | } catch (e) {
52 | return left(
53 | ApiError(message: '$e'),
54 | );
55 | }
56 | }
57 |
58 | @override
59 | Future> removeFavorite(
60 | String articleId,
61 | String userId,
62 | ) async {
63 | try {
64 | final favorite = Favorite(articleId: articleId, userId: userId);
65 | final apiResponse =
66 | await getIt().removeFavorite(favorite);
67 | if (apiResponse.status) {
68 | return right(apiResponse);
69 | } else {
70 | return left(
71 | ApiError(message: apiResponse.message),
72 | );
73 | }
74 | } catch (e) {
75 | return left(
76 | ApiError(message: '$e'),
77 | );
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/articles/articles_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/articles/i_articles_facade.dart';
5 | import '../../domain/entities/api_error/api_error.dart';
6 | import '../../domain/entities/api_response/api_response.dart';
7 | import '../../domain/entities/article/article.dart';
8 | import '../../domain/entities/category/category.dart';
9 | import '../../injection.dart';
10 | import '../core/network/network.dart';
11 |
12 | @LazySingleton(as: IArticlesFacade)
13 | class ArticlesFacade implements IArticlesFacade {
14 | @override
15 | Future>>> getCategories() async {
16 | try {
17 | final apiResponse = await getIt().getCategories();
18 | if (apiResponse.status) {
19 | return right(apiResponse);
20 | } else {
21 | return left(
22 | ApiError(message: apiResponse.message),
23 | );
24 | }
25 | } catch (e) {
26 | return left(
27 | ApiError(message: '$e'),
28 | );
29 | }
30 | }
31 |
32 | @override
33 | Future>>> getArticles() async {
34 | try {
35 | final apiResponse = await getIt().getArticles();
36 | if (apiResponse.status) {
37 | return right(apiResponse);
38 | } else {
39 | return left(
40 | ApiError(message: apiResponse.message),
41 | );
42 | }
43 | } catch (e) {
44 | return left(
45 | ApiError(message: '$e'),
46 | );
47 | }
48 | }
49 |
50 | @override
51 | Future>>> getArticlesByCategory(
52 | categoryId) async {
53 | try {
54 | final apiResponse =
55 | await getIt().getArticlesByCategory(categoryId);
56 | if (apiResponse.status) {
57 | return right(apiResponse);
58 | } else {
59 | return left(
60 | ApiError(message: apiResponse.message),
61 | );
62 | }
63 | } catch (e) {
64 | return left(
65 | ApiError(message: '$e'),
66 | );
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/core/network/interceptor.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | import '../../../injection.dart';
4 | import '../preferences.dart';
5 |
6 | class AppInterceptor extends Interceptor {
7 | final _preferences = getIt();
8 |
9 | @override
10 | Future onRequest(RequestOptions options) async {
11 | final token = _preferences.token;
12 | options.headers.addAll({'Authorization': token});
13 | return options;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/core/network/network.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:retrofit/retrofit.dart';
3 |
4 | import '../../../domain/entities/api_response/api_response.dart';
5 | import '../../../domain/entities/article/article.dart';
6 | import '../../../domain/entities/category/category.dart';
7 | import '../../../domain/entities/favorite/favorite.dart';
8 | import '../../../domain/entities/user/user.dart';
9 | import '../../../presentation/routes/routes.dart';
10 | import 'interceptor.dart';
11 |
12 | part 'network.g.dart';
13 |
14 | @RestApi(baseUrl: AppRoutes.host)
15 | abstract class AppNetworkClient {
16 | factory AppNetworkClient(Dio dio, {String baseUrl}) = _AppNetworkClient;
17 |
18 | @POST('/auth/login')
19 | Future> signIn(
20 | @Body() User user,
21 | );
22 |
23 | @POST('/auth/register')
24 | Future> signUp(
25 | @Body() User user,
26 | );
27 |
28 | @GET('/users/{userId}')
29 | Future> getUserInformation(
30 | @Path('userId') String userId,
31 | );
32 |
33 | @GET('/categories')
34 | Future>> getCategories();
35 |
36 | @POST('/articles')
37 | Future createArticle(
38 | @Body() FormData data,
39 | );
40 |
41 | @GET('/articles')
42 | Future>> getArticles();
43 |
44 | @GET('/articles/{id}')
45 | Future> getArticlesById(
46 | @Path('id') String id, {
47 | @Query('userId') String userId,
48 | });
49 |
50 | @GET('/articles')
51 | Future>> searchArticles(
52 | @Query('query') String query,
53 | );
54 |
55 | @GET('/articles/byCategory/{categoryId}')
56 | Future>> getArticlesByCategory(
57 | @Path('categoryId') String categoryId,
58 | );
59 |
60 | @GET('/articles/byUser/{userId}')
61 | Future>> getArticlesByUser(
62 | @Path('userId') String userId,
63 | );
64 |
65 | @GET('/favorites/{userId}')
66 | Future>> getFavorites(
67 | @Path('userId') String userId,
68 | );
69 |
70 | @POST('/favorites')
71 | Future addFavorite(
72 | @Body() Favorite favorite,
73 | );
74 |
75 | @DELETE('/favorites')
76 | Future removeFavorite(
77 | @Body() Favorite favorite,
78 | );
79 | }
80 |
81 | class AppNetwork {
82 | AppNetwork._();
83 |
84 | static AppNetworkClient _appNetworkClient;
85 |
86 | static AppNetworkClient get instance {
87 | if (_appNetworkClient == null) {
88 | final dio = Dio();
89 | dio.interceptors.add(AppInterceptor());
90 | _appNetworkClient = AppNetworkClient(dio);
91 | }
92 | return _appNetworkClient;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/core/preferences.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | abstract class AppPreferencesKeys {
4 | static const String TOKEN = 'token';
5 | static const String USER_ID = 'user_id';
6 | }
7 |
8 | class AppPreferences {
9 | static Future get instance async {
10 | final sharedPreferences = await SharedPreferences.getInstance();
11 | return AppPreferences(sharedPreferences);
12 | }
13 |
14 | AppPreferences(this.sharedPreferences);
15 |
16 | final SharedPreferences sharedPreferences;
17 |
18 | Future setString(String key, String value) =>
19 | sharedPreferences.setString(key, value);
20 |
21 | String getString(String key, {String def}) =>
22 | sharedPreferences.getString(key) ?? def;
23 |
24 | String get userId => getString(AppPreferencesKeys.USER_ID);
25 |
26 | String get token => getString(AppPreferencesKeys.TOKEN);
27 |
28 | bool get isLoggedIn =>
29 | (getString(AppPreferencesKeys.TOKEN)?.isNotEmpty ?? false) &&
30 | (getString(AppPreferencesKeys.USER_ID)?.isNotEmpty ?? false);
31 |
32 | Future clear() => sharedPreferences.clear();
33 | }
34 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/favorites/favorites_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/entities/api_error/api_error.dart';
5 | import '../../domain/entities/api_response/api_response.dart';
6 | import '../../domain/entities/favorite/favorite.dart';
7 | import '../../domain/favorites/i_favorites_facade.dart';
8 | import '../../injection.dart';
9 | import '../core/network/network.dart';
10 |
11 | @LazySingleton(as: IFavoritesFacade)
12 | class FavoritesFacade implements IFavoritesFacade {
13 | @override
14 | Future>>> getFavorites(
15 | String userId) async {
16 | try {
17 | final apiResponse = await getIt().getFavorites(userId);
18 | if (apiResponse.status) {
19 | return right(apiResponse);
20 | } else {
21 | return left(
22 | ApiError(message: apiResponse.message),
23 | );
24 | }
25 | } catch (e) {
26 | return left(
27 | ApiError(message: '$e'),
28 | );
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/profile/profile_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/entities/api_error/api_error.dart';
5 | import '../../domain/entities/api_response/api_response.dart';
6 | import '../../domain/entities/article/article.dart';
7 | import '../../domain/entities/user/user.dart';
8 | import '../../domain/profile/i_profile_facade.dart';
9 | import '../../injection.dart';
10 | import '../core/network/network.dart';
11 |
12 | @LazySingleton(as: IProfileFacade)
13 | class ProfileFacade implements IProfileFacade {
14 | @override
15 | Future>> getUserInformation(
16 | String userId,
17 | ) async {
18 | try {
19 | final apiResponse =
20 | await getIt().getUserInformation(userId);
21 | if (apiResponse.status) {
22 | return right(apiResponse);
23 | } else {
24 | return left(
25 | ApiError(message: apiResponse.message),
26 | );
27 | }
28 | } catch (e) {
29 | return left(
30 | ApiError(message: '$e'),
31 | );
32 | }
33 | }
34 |
35 | @override
36 | Future>>> getArticlesByUser(
37 | String userId,
38 | ) async {
39 | try {
40 | final apiResponse =
41 | await getIt().getArticlesByUser(userId);
42 | if (apiResponse.status) {
43 | return right(apiResponse);
44 | } else {
45 | return left(
46 | ApiError(message: apiResponse.message),
47 | );
48 | }
49 | } catch (e) {
50 | return left(
51 | ApiError(message: '$e'),
52 | );
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/search/search_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/entities/api_error/api_error.dart';
5 | import '../../domain/entities/api_response/api_response.dart';
6 | import '../../domain/entities/article/article.dart';
7 | import '../../domain/search/i_search_facade.dart';
8 | import '../../injection.dart';
9 | import '../core/network/network.dart';
10 |
11 | @LazySingleton(as: ISearchFacade)
12 | class SearchFacade implements ISearchFacade {
13 | @override
14 | Future>>> searchArticles(
15 | String query) async {
16 | try {
17 | final apiResponse = await getIt().searchArticles(query);
18 | if (apiResponse.status) {
19 | return right(apiResponse);
20 | } else {
21 | return left(
22 | ApiError(message: apiResponse.message),
23 | );
24 | }
25 | } catch (e) {
26 | return left(
27 | ApiError(message: '$e'),
28 | );
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/sign_in/sign_in_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/entities/api_error/api_error.dart';
5 | import '../../domain/entities/api_response/api_response.dart';
6 | import '../../domain/entities/user/user.dart';
7 | import '../../domain/sign_in/i_sign_in_facade.dart';
8 | import '../../injection.dart';
9 | import '../core/network/network.dart';
10 |
11 | @LazySingleton(as: ISignInFacade)
12 | class SignInFacade implements ISignInFacade {
13 | @override
14 | Future>> signIn({User user}) async {
15 | try {
16 | final apiResponse = await getIt().signIn(user);
17 | if (apiResponse.status) {
18 | return right(apiResponse);
19 | } else {
20 | return left(
21 | ApiError(message: apiResponse.message),
22 | );
23 | }
24 | } catch (e) {
25 | return left(
26 | ApiError(message: '$e'),
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/infrastructure/sign_up/sign_up_facade.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import '../../domain/entities/api_error/api_error.dart';
5 | import '../../domain/entities/api_response/api_response.dart';
6 | import '../../domain/entities/user/user.dart';
7 | import '../../domain/sign_up/i_sign_up_facade.dart';
8 | import '../../injection.dart';
9 | import '../core/network/network.dart';
10 |
11 | @LazySingleton(as: ISignUpFacade)
12 | class SignUpFacade implements ISignUpFacade {
13 | @override
14 | Future>> signUp({User user}) async {
15 | try {
16 | final apiResponse = await getIt().signUp(user);
17 | if (apiResponse.status) {
18 | return right(apiResponse);
19 | } else {
20 | return left(
21 | ApiError(message: apiResponse.message),
22 | );
23 | }
24 | } catch (e) {
25 | return left(
26 | ApiError(message: '$e'),
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/injection.config.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // **************************************************************************
4 | // InjectableConfigGenerator
5 | // **************************************************************************
6 |
7 | import 'package:get_it/get_it.dart' as _i1;
8 | import 'package:injectable/injectable.dart' as _i2;
9 |
10 | import 'application/article_create/bloc.dart' as _i23;
11 | import 'application/article_details/bloc.dart' as _i24;
12 | import 'application/articles/bloc.dart' as _i25;
13 | import 'application/favorites/bloc.dart' as _i26;
14 | import 'application/profile/bloc.dart' as _i19;
15 | import 'application/search/bloc.dart' as _i20;
16 | import 'application/sign_in/bloc.dart' as _i21;
17 | import 'application/sign_up/bloc.dart' as _i22;
18 | import 'domain/article_create/i_article_create_facade.dart' as _i3;
19 | import 'domain/article_details/i_article_details_facade.dart' as _i5;
20 | import 'domain/articles/i_articles_facade.dart' as _i7;
21 | import 'domain/favorites/i_favorites_facade.dart' as _i9;
22 | import 'domain/profile/i_profile_facade.dart' as _i11;
23 | import 'domain/search/i_search_facade.dart' as _i13;
24 | import 'domain/sign_in/i_sign_in_facade.dart' as _i15;
25 | import 'domain/sign_up/i_sign_up_facade.dart' as _i17;
26 | import 'infrastructure/article_create/article_create_facade.dart' as _i4;
27 | import 'infrastructure/article_details/article_details_facade.dart' as _i6;
28 | import 'infrastructure/articles/articles_facade.dart' as _i8;
29 | import 'infrastructure/favorites/favorites_facade.dart' as _i10;
30 | import 'infrastructure/profile/profile_facade.dart' as _i12;
31 | import 'infrastructure/search/search_facade.dart' as _i14;
32 | import 'infrastructure/sign_in/sign_in_facade.dart' as _i16;
33 | import 'infrastructure/sign_up/sign_up_facade.dart'
34 | as _i18; // ignore_for_file: unnecessary_lambdas
35 |
36 | // ignore_for_file: lines_longer_than_80_chars
37 | /// initializes the registration of provided dependencies inside of [GetIt]
38 | _i1.GetIt $initGetIt(_i1.GetIt get,
39 | {String environment, _i2.EnvironmentFilter environmentFilter}) {
40 | final gh = _i2.GetItHelper(get, environment, environmentFilter);
41 | gh.lazySingleton<_i3.IArticleCreateFacade>(() => _i4.ArticlesDetailsFacade());
42 | gh.lazySingleton<_i5.IArticleDetailsFacade>(
43 | () => _i6.ArticlesDetailsFacade());
44 | gh.lazySingleton<_i7.IArticlesFacade>(() => _i8.ArticlesFacade());
45 | gh.lazySingleton<_i9.IFavoritesFacade>(() => _i10.FavoritesFacade());
46 | gh.lazySingleton<_i11.IProfileFacade>(() => _i12.ProfileFacade());
47 | gh.lazySingleton<_i13.ISearchFacade>(() => _i14.SearchFacade());
48 | gh.lazySingleton<_i15.ISignInFacade>(() => _i16.SignInFacade());
49 | gh.lazySingleton<_i17.ISignUpFacade>(() => _i18.SignUpFacade());
50 | gh.factory<_i19.ProfileBloc>(
51 | () => _i19.ProfileBloc(get<_i11.IProfileFacade>()));
52 | gh.factory<_i20.SearchBloc>(() => _i20.SearchBloc(get<_i13.ISearchFacade>()));
53 | gh.factory<_i21.SignInBloc>(() => _i21.SignInBloc(get<_i15.ISignInFacade>()));
54 | gh.factory<_i22.SignUpBloc>(() => _i22.SignUpBloc(get<_i17.ISignUpFacade>()));
55 | gh.factory<_i23.ArticleCreateBloc>(
56 | () => _i23.ArticleCreateBloc(get<_i3.IArticleCreateFacade>()));
57 | gh.factory<_i24.ArticleDetailsBloc>(
58 | () => _i24.ArticleDetailsBloc(get<_i5.IArticleDetailsFacade>()));
59 | gh.factory<_i25.ArticlesBloc>(
60 | () => _i25.ArticlesBloc(get<_i7.IArticlesFacade>()));
61 | gh.factory<_i26.FavoritesBloc>(
62 | () => _i26.FavoritesBloc(get<_i9.IFavoritesFacade>()));
63 | return get;
64 | }
65 |
--------------------------------------------------------------------------------
/lib/src/injection.dart:
--------------------------------------------------------------------------------
1 | import 'package:get_it/get_it.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import 'infrastructure/core/network/network.dart';
5 | import 'infrastructure/core/preferences.dart';
6 | import 'injection.config.dart';
7 |
8 | final getIt = GetIt.instance;
9 |
10 | @injectableInit
11 | void configureInjection(String env) {
12 | $initGetIt(getIt, environment: env);
13 | getIt.registerSingletonAsync(
14 | () async => await AppPreferences.instance,
15 | );
16 | getIt.registerFactory(() => AppNetwork.instance);
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/presentation/routes/routes.dart:
--------------------------------------------------------------------------------
1 | abstract class AppRoutes {
2 | static const String host = 'http://192.168.1.8:8888/';
3 |
4 | static const String home = '/home';
5 | static const String onBoarding = '/onBoarding';
6 | static const String signIn = '/signIn';
7 | static const String signUp = '/signUp';
8 | static const String articleDetails = '/articleDetails';
9 | static const String articleCreate = '/articleCreate';
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/presentation/routes/routes_generator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import '../../domain/entities/article/article.dart';
5 | import '../ui/pages/article_create/article_create.dart';
6 | import '../ui/pages/article_details/article_details.dart';
7 | import '../ui/pages/home/home.dart';
8 | import '../ui/pages/on_boarding/on_boarding.dart';
9 | import '../ui/pages/sign_in/sign_in.dart';
10 | import '../ui/pages/sign_up/sign_up.dart';
11 | import 'routes.dart';
12 |
13 | abstract class AppRoutesGenerator {
14 | static Route generateRoute(RouteSettings params) {
15 | var screen;
16 | var args = params.arguments;
17 |
18 | switch (params.name) {
19 | case AppRoutes.onBoarding:
20 | screen = OnBoardingPage();
21 | break;
22 | case AppRoutes.signUp:
23 | screen = SignUpPage();
24 | break;
25 |
26 | case AppRoutes.signIn:
27 | screen = SignInPage();
28 | break;
29 |
30 | case AppRoutes.home:
31 | screen = HomePage();
32 | break;
33 |
34 | case AppRoutes.articleDetails:
35 | if (args is Article)
36 | screen = ArticleDetails(article: args);
37 | else
38 | screen = _errorScreen(params.name);
39 | break;
40 |
41 | case AppRoutes.articleCreate:
42 | screen = ArticleCreatePage();
43 | break;
44 |
45 | default:
46 | screen = _errorScreen(params.name);
47 | }
48 |
49 | return CupertinoPageRoute(
50 | builder: (_) => screen,
51 | );
52 | }
53 |
54 | static Widget _errorScreen(String route) => Scaffold(
55 | body: Text('$route : Route Not Found'),
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/article_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:intl/intl.dart';
4 |
5 | import '../../../domain/entities/article/article.dart';
6 | import '../../routes/routes.dart';
7 |
8 | class ArticleItemWidget extends StatelessWidget {
9 | final Article article;
10 |
11 | const ArticleItemWidget({Key key, @required this.article}) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) => Card(
15 | elevation: 3,
16 | clipBehavior: Clip.antiAlias,
17 | margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 7.5),
18 | shape: RoundedRectangleBorder(
19 | borderRadius: BorderRadius.circular(10),
20 | ),
21 | child: InkWell(
22 | onTap: () {
23 | Navigator.of(context).pushNamed(
24 | AppRoutes.articleDetails,
25 | arguments: article,
26 | );
27 | },
28 | child: Padding(
29 | padding: const EdgeInsets.all(10),
30 | child: Column(
31 | crossAxisAlignment: CrossAxisAlignment.stretch,
32 | children: [
33 | ClipRRect(
34 | borderRadius: BorderRadius.circular(10),
35 | child: AspectRatio(
36 | aspectRatio: 2,
37 | child: CachedNetworkImage(
38 | imageUrl:
39 | '${AppRoutes.host}files/articles/${article.id}/${article.images[0]}',
40 | fit: BoxFit.cover,
41 | ),
42 | ),
43 | ),
44 | const SizedBox(
45 | height: 20,
46 | ),
47 | Text(
48 | DateFormat('yyyy/MM/dd HH:mm').format(
49 | DateTime.parse(article.createdAt),
50 | ),
51 | style: TextStyle(
52 | color: Colors.grey,
53 | fontSize: 12,
54 | fontWeight: FontWeight.w300,
55 | fontStyle: FontStyle.italic,
56 | ),
57 | ),
58 | const SizedBox(
59 | height: 10,
60 | ),
61 | Text(
62 | article.title,
63 | style: TextStyle(
64 | color: Colors.black87,
65 | fontSize: 18,
66 | fontWeight: FontWeight.w600,
67 | ),
68 | ),
69 | const SizedBox(
70 | height: 10,
71 | ),
72 | ],
73 | ),
74 | ),
75 | ),
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/buttons/clear_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ClearButtonWidget extends StatelessWidget {
4 | final VoidCallback onPressed;
5 | final Widget child;
6 |
7 | const ClearButtonWidget({Key key, @required this.onPressed, this.child})
8 | : super(key: key);
9 |
10 | @override
11 | Widget build(BuildContext context) => InkWell(
12 | onTap: onPressed,
13 | child: child ??
14 | Icon(
15 | Icons.close,
16 | color: Colors.white,
17 | ),
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/buttons/rounded_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppRoundedButton extends StatelessWidget {
4 | final VoidCallback onPressed;
5 | final String title;
6 | final EdgeInsetsGeometry padding;
7 | final double radius;
8 | final double elevation;
9 |
10 | const AppRoundedButton({
11 | Key key,
12 | @required this.onPressed,
13 | @required this.title,
14 | this.padding = const EdgeInsets.symmetric(horizontal: 30, vertical: 17),
15 | this.radius = 34,
16 | this.elevation,
17 | }) : super(key: key);
18 |
19 | @override
20 | Widget build(BuildContext context) => RaisedButton(
21 | elevation: elevation,
22 | onPressed: onPressed,
23 | color: Theme.of(context).primaryColor,
24 | padding: padding,
25 | shape: RoundedRectangleBorder(
26 | borderRadius: BorderRadius.circular(radius),
27 | ),
28 | child: Text(
29 | title,
30 | style: Theme.of(context).textTheme.button.copyWith(
31 | color: Colors.white,
32 | ),
33 | ),
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/buttons/rounded_outline_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppRoundedOutlineButton extends StatelessWidget {
4 | final VoidCallback onPressed;
5 | final String title;
6 |
7 | const AppRoundedOutlineButton({
8 | Key key,
9 | @required this.onPressed,
10 | @required this.title,
11 | }) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) => OutlineButton(
15 | onPressed: onPressed,
16 | color: Theme.of(context).primaryColor,
17 | padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 17),
18 | borderSide: BorderSide(
19 | color: Theme.of(context).primaryColor,
20 | ),
21 | shape: RoundedRectangleBorder(
22 | borderRadius: BorderRadius.circular(34),
23 | ),
24 | child: Text(
25 | title,
26 | style: Theme.of(context).textTheme.button.copyWith(
27 | color: Theme.of(context).primaryColor,
28 | ),
29 | ),
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/dialogs/question.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppDialogQuestion extends StatelessWidget {
4 | final String title;
5 | final String question;
6 | final List actions;
7 |
8 | const AppDialogQuestion({
9 | Key key,
10 | this.title,
11 | @required this.question,
12 | this.actions,
13 | }) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) => SimpleDialog(
17 | contentPadding: const EdgeInsets.all(15),
18 | shape: RoundedRectangleBorder(
19 | borderRadius: BorderRadius.circular(10),
20 | ),
21 | title: title != null ? Text(title) : const SizedBox(),
22 | children: [
23 | Text(
24 | question,
25 | style: Theme.of(context).textTheme.bodyText2,
26 | ),
27 | const SizedBox(
28 | height: 20,
29 | ),
30 | if (actions != null)
31 | Row(
32 | mainAxisAlignment: MainAxisAlignment.end,
33 | children: actions,
34 | ),
35 | ],
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/dialogs/waiting.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppDialogWaiting extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) => Dialog(
6 | //contentPadding: const EdgeInsets.all(15),
7 | shape: RoundedRectangleBorder(
8 | borderRadius: BorderRadius.circular(10),
9 | ),
10 | child: Padding(
11 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
12 | child: Row(
13 | children: [
14 | CircularProgressIndicator(),
15 | const SizedBox(
16 | width: 20,
17 | ),
18 | Text('Please Wait...'),
19 | ],
20 | ),
21 | ),
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/loading.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoadingWidget extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) => Center(
6 | child: SizedBox(
7 | height: 25,
8 | width: 25,
9 | child: CircularProgressIndicator(
10 | strokeWidth: 2.5,
11 | ),
12 | ),
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/login.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 |
4 | import '../../routes/routes.dart';
5 | import 'buttons/rounded_button.dart';
6 | import 'buttons/rounded_outline_button.dart';
7 |
8 | class AskLoginWidget extends StatelessWidget {
9 | @override
10 | Widget build(BuildContext context) => Center(
11 | child: Column(
12 | mainAxisSize: MainAxisSize.min,
13 | children: [
14 | SvgPicture.asset(
15 | 'assets/images/shield.svg',
16 | width: 200,
17 | ),
18 | const SizedBox(
19 | height: 50,
20 | ),
21 | SizedBox(
22 | width: 200,
23 | child: AppRoundedButton(
24 | onPressed: () {
25 | Navigator.of(context).pushNamed(AppRoutes.signIn);
26 | },
27 | title: 'SIGN IN',
28 | ),
29 | ),
30 | const SizedBox(
31 | height: 20,
32 | ),
33 | SizedBox(
34 | width: 200,
35 | child: AppRoundedOutlineButton(
36 | onPressed: () {
37 | Navigator.of(context).pushNamed(AppRoutes.signUp);
38 | },
39 | title: 'SIGN UP',
40 | ),
41 | ),
42 | ],
43 | ),
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/my_article_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:intl/intl.dart';
5 |
6 | import '../../../domain/entities/article/article.dart';
7 | import '../../routes/routes.dart';
8 |
9 | class MyArticleItemWidget extends StatelessWidget {
10 | final Article article;
11 | final VoidCallback onDelete;
12 |
13 | const MyArticleItemWidget({Key key, @required this.article, this.onDelete})
14 | : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) => Card(
18 | elevation: 3,
19 | clipBehavior: Clip.antiAlias,
20 | margin: const EdgeInsets.symmetric(vertical: 7.5),
21 | shape: RoundedRectangleBorder(
22 | borderRadius: BorderRadius.circular(10),
23 | ),
24 | child: InkWell(
25 | onTap: () {
26 | Navigator.of(context).pushNamed(
27 | AppRoutes.articleDetails,
28 | arguments: article,
29 | );
30 | },
31 | child: Padding(
32 | padding: const EdgeInsets.all(10),
33 | child: Row(
34 | crossAxisAlignment: CrossAxisAlignment.start,
35 | children: [
36 | ClipRRect(
37 | borderRadius: BorderRadius.circular(10),
38 | child: CachedNetworkImage(
39 | imageUrl:
40 | '${AppRoutes.host}files/articles/${article.id}/${article.images[0]}',
41 | fit: BoxFit.cover,
42 | height: 100,
43 | width: 100,
44 | ),
45 | ),
46 | const SizedBox(
47 | width: 10,
48 | ),
49 | Expanded(
50 | child: Column(
51 | crossAxisAlignment: CrossAxisAlignment.start,
52 | children: [
53 | Text(
54 | article.title,
55 | style: TextStyle(
56 | color: Colors.black87,
57 | fontSize: 18,
58 | fontWeight: FontWeight.w600,
59 | ),
60 | ),
61 | const SizedBox(
62 | height: 5,
63 | ),
64 | Text(
65 | article.content,
66 | style: TextStyle(
67 | color: Colors.grey,
68 | fontSize: 12,
69 | fontWeight: FontWeight.w300,
70 | ),
71 | maxLines: 2,
72 | overflow: TextOverflow.ellipsis,
73 | ),
74 | const SizedBox(
75 | height: 10,
76 | ),
77 | Align(
78 | alignment: AlignmentDirectional.bottomEnd,
79 | child: Text(
80 | DateFormat('yyyy/MM/dd HH:mm').format(
81 | DateTime.parse(article.createdAt),
82 | ),
83 | style: TextStyle(
84 | color: Colors.grey,
85 | fontSize: 12,
86 | fontWeight: FontWeight.w300,
87 | fontStyle: FontStyle.italic,
88 | ),
89 | ),
90 | ),
91 | ],
92 | ),
93 | ),
94 | ],
95 | ),
96 | ),
97 | ),
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/text_fields/rounded_outline_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppRoundedOutlineTextFormField extends StatefulWidget {
4 | final TextEditingController controller;
5 | final IconData prefixIcon;
6 | final String hint;
7 | final bool obscureText;
8 | final TextInputAction textInputAction;
9 | final TextInputType keyboardType;
10 | final double borderRadius;
11 | final int maxLines;
12 | final String Function(String) validator;
13 |
14 | const AppRoundedOutlineTextFormField({
15 | Key key,
16 | this.controller,
17 | this.prefixIcon,
18 | this.hint,
19 | this.obscureText = false,
20 | this.textInputAction,
21 | this.keyboardType,
22 | this.borderRadius = 20,
23 | this.maxLines = 1,
24 | this.validator,
25 | }) : super(key: key);
26 |
27 | @override
28 | _AppRoundedOutlineTextFormFieldState createState() =>
29 | _AppRoundedOutlineTextFormFieldState();
30 | }
31 |
32 | class _AppRoundedOutlineTextFormFieldState
33 | extends State {
34 | bool _isObscureText;
35 |
36 | @override
37 | void initState() {
38 | super.initState();
39 | _isObscureText = widget.obscureText;
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) => TextFormField(
44 | validator: widget.validator,
45 | controller: widget.controller,
46 | obscureText: _isObscureText,
47 | textInputAction: widget.textInputAction,
48 | keyboardType: widget.keyboardType,
49 | maxLines: widget.maxLines,
50 | decoration: InputDecoration(
51 | prefixIcon:
52 | (widget.prefixIcon == null) ? null : Icon(widget.prefixIcon),
53 | suffixIcon: widget.obscureText ? _buildObscureIcon() : null,
54 | hintText: widget.hint,
55 | border: OutlineInputBorder(
56 | borderRadius: BorderRadius.circular(
57 | widget.borderRadius,
58 | ),
59 | ),
60 | ),
61 | );
62 |
63 | Widget _buildObscureIcon() => IconButton(
64 | onPressed: () {
65 | setState(() {
66 | _isObscureText = !_isObscureText;
67 | });
68 | },
69 | icon: Icon(
70 | _isObscureText ? Icons.remove_red_eye : Icons.remove_red_eye_outlined,
71 | ),
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/components/text_fields/search_field.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | import '../buttons/clear_button.dart';
6 |
7 | class SearchFieldWidget extends StatefulWidget {
8 | final Function(String) onChanged;
9 | final TextStyle textStyle;
10 | final InputDecoration inputDecoration;
11 | final bool autoFocus;
12 | final Duration textChangeDuration;
13 | final Widget clearButton;
14 | final bool showClearButton;
15 | final int minSearchCharacters;
16 |
17 | const SearchFieldWidget({
18 | Key key,
19 | this.onChanged,
20 | this.inputDecoration,
21 | this.textStyle,
22 | this.autoFocus = true,
23 | this.textChangeDuration = const Duration(milliseconds: 500),
24 | this.clearButton,
25 | this.showClearButton = true,
26 | this.minSearchCharacters = 2,
27 | }) : super(key: key);
28 |
29 | @override
30 | _SearchFieldWidgetState createState() => _SearchFieldWidgetState();
31 | }
32 |
33 | class _SearchFieldWidgetState extends State {
34 | TextEditingController _textEditingController = TextEditingController();
35 | Timer _timer;
36 |
37 | bool _isClearButtonVisible = false;
38 |
39 | void _onChanged() {
40 | if (_textEditingController.text.length < widget.minSearchCharacters) return;
41 |
42 | if (_timer?.isActive ?? false) {
43 | _timer?.cancel();
44 | _timer = null;
45 | }
46 | _timer = Timer(widget.textChangeDuration, () {
47 | widget.onChanged?.call(_textEditingController.text);
48 | });
49 | }
50 |
51 | @override
52 | void initState() {
53 | super.initState();
54 |
55 | _textEditingController.addListener(() {
56 | if (_textEditingController.text.isNotEmpty && !_isClearButtonVisible)
57 | setState(() {
58 | _isClearButtonVisible = true;
59 | });
60 | else if (_textEditingController.text.isEmpty && _isClearButtonVisible)
61 | setState(() {
62 | _isClearButtonVisible = false;
63 | });
64 | });
65 | }
66 |
67 | @override
68 | Widget build(BuildContext context) => Row(
69 | children: [
70 | Icon(
71 | Icons.search,
72 | color: Colors.grey[400],
73 | ),
74 | const SizedBox(
75 | width: 5,
76 | ),
77 | Expanded(
78 | child: TextField(
79 | onChanged: (_) {
80 | _onChanged();
81 | },
82 | style: widget.textStyle ??
83 | TextStyle(
84 | color: Colors.white,
85 | fontSize: 18,
86 | ),
87 | decoration: widget.inputDecoration ??
88 | InputDecoration(
89 | hintText: 'Search',
90 | hintStyle: TextStyle(
91 | color: Colors.white.withOpacity(.5),
92 | fontSize: 18,
93 | ),
94 | border: InputBorder.none,
95 | ),
96 | controller: _textEditingController,
97 | autofocus: widget.autoFocus ?? true,
98 | textInputAction: TextInputAction.search,
99 | ),
100 | ),
101 | if (widget.showClearButton)
102 | Visibility(
103 | visible: _isClearButtonVisible,
104 | child: ClearButtonWidget(
105 | onPressed: () {
106 | _textEditingController.clear();
107 | },
108 | child: widget.clearButton,
109 | ),
110 | ),
111 | ],
112 | );
113 |
114 | @override
115 | void dispose() {
116 | _textEditingController.dispose();
117 | _timer?.cancel();
118 | super.dispose();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/article_create/article_create.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart' as dio;
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:fluttertoast/fluttertoast.dart';
5 |
6 | import '../../../../application/article_create/bloc.dart';
7 | import '../../../../application/articles/bloc.dart';
8 | import '../../../../domain/entities/category/category.dart';
9 | import '../../../../infrastructure/core/preferences.dart';
10 | import '../../../../injection.dart';
11 | import '../../../utils/extensions.dart';
12 | import '../../components/buttons/rounded_button.dart';
13 | import '../../components/dialogs/waiting.dart';
14 | import '../../components/text_fields/rounded_outline_text_field.dart';
15 | import 'widgets/widgets.dart';
16 |
17 | class ArticleCreatePage extends StatefulWidget {
18 | @override
19 | _ArticleCreatePageState createState() => _ArticleCreatePageState();
20 | }
21 |
22 | class _ArticleCreatePageState extends State {
23 | final _bloc = getIt();
24 | final _preferences = getIt();
25 |
26 | final _formKey = GlobalKey();
27 |
28 | final _titleController = TextEditingController();
29 | final _contentController = TextEditingController();
30 | List _selectedImages = [];
31 | Category _selectedCategory;
32 |
33 | void _createArticle() {
34 | print(_contentController.text);
35 | final data = dio.FormData.fromMap({
36 | 'title': _titleController.text,
37 | 'content': _contentController.text,
38 | 'category_id': _selectedCategory.id,
39 | 'user_id': _preferences.userId,
40 | });
41 | data.files.addAll(
42 | _selectedImages
43 | .map((path) => MapEntry(path, dio.MultipartFile.fromFileSync(path)))
44 | .toList(),
45 | );
46 |
47 | _bloc.createArticle(data);
48 |
49 | FocusScope.of(context).unfocus();
50 |
51 | context.showAppDialog(
52 | isDismissible: false,
53 | child: AppDialogWaiting(),
54 | );
55 | }
56 |
57 | void _validForm() {
58 | if (_selectedImages?.isEmpty ?? true) {
59 | Fluttertoast.showToast(msg: 'Please select some images');
60 | return;
61 | }
62 |
63 | if (_selectedCategory == null) {
64 | Fluttertoast.showToast(msg: 'Please select category');
65 | return;
66 | }
67 |
68 | bool isFormValid = _formKey.currentState.validate();
69 | if (!isFormValid) return;
70 |
71 | _createArticle();
72 | }
73 |
74 | @override
75 | void initState() {
76 | super.initState();
77 | _bloc.listen((state) {
78 | state.articleCreateState.fold(
79 | () => null,
80 | (either) => either.fold(
81 | (apiError) {
82 | Navigator.pop(context);
83 | Fluttertoast.showToast(msg: apiError.message);
84 | },
85 | (result) {
86 | Navigator.pop(context);
87 | Fluttertoast.showToast(msg: result.message);
88 | Navigator.pop(context);
89 | },
90 | ),
91 | );
92 | });
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) => Scaffold(
97 | appBar: AppBar(
98 | title: Text('Create Article'),
99 | ),
100 | body: BlocProvider(
101 | create: (_) => getIt()..getCategories(),
102 | child: Form(
103 | key: _formKey,
104 | child: ListView(
105 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
106 | children: [
107 | ImagesWidget(
108 | onSelectImages: (images) {
109 | _selectedImages = images;
110 | },
111 | ),
112 | const SizedBox(
113 | height: 30,
114 | ),
115 | CategoriesWidget(
116 | onSelectCategory: (category) {
117 | _selectedCategory = category;
118 | },
119 | ),
120 | const SizedBox(
121 | height: 20,
122 | ),
123 | AppRoundedOutlineTextFormField(
124 | controller: _titleController,
125 | textInputAction: TextInputAction.next,
126 | validator: (text) {
127 | // TODO just for test purpose this is not the best way to validate a text
128 | if ((text?.isEmpty ?? true) || text.length < 4)
129 | return 'Please insert valid title!';
130 | return null;
131 | },
132 | hint: 'Title',
133 | borderRadius: 10,
134 | ),
135 | const SizedBox(
136 | height: 20,
137 | ),
138 | AppRoundedOutlineTextFormField(
139 | controller: _contentController,
140 | hint: 'Content',
141 | validator: (text) {
142 | // TODO just for test purpose this is not the best way to validate a text
143 | if ((text?.isEmpty ?? true) || text.length < 10)
144 | return 'Please insert valid content!';
145 | return null;
146 | },
147 | maxLines: 7,
148 | borderRadius: 10,
149 | ),
150 | const SizedBox(
151 | height: 30,
152 | ),
153 | AppRoundedButton(
154 | onPressed: _validForm,
155 | radius: 10,
156 | title: 'Create Article',
157 | ),
158 | ],
159 | ),
160 | ),
161 | ),
162 | );
163 |
164 | @override
165 | void dispose() {
166 | _titleController.dispose();
167 | _contentController.dispose();
168 | super.dispose();
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/article_create/widgets/categories.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../../application/articles/bloc.dart';
5 | import '../../../../../domain/entities/category/category.dart';
6 | import '../../../components/loading.dart';
7 |
8 | class CategoriesWidget extends StatefulWidget {
9 | final Function(Category) onSelectCategory;
10 |
11 | const CategoriesWidget({Key key, this.onSelectCategory}) : super(key: key);
12 |
13 | @override
14 | _CategoriesWidgetState createState() => _CategoriesWidgetState();
15 | }
16 |
17 | class _CategoriesWidgetState extends State {
18 | Category _selectedCategory;
19 |
20 | void _selectCategory(Category category) {
21 | setState(() {
22 | if (_selectedCategory?.id == category?.id)
23 | _selectedCategory = null;
24 | else
25 | _selectedCategory = category;
26 | });
27 |
28 | widget.onSelectCategory?.call(_selectedCategory);
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) =>
33 | BlocBuilder(
34 | builder: (context, state) => state.categoriesState.fold(
35 | () {
36 | _selectedCategory = null;
37 | return LoadingWidget();
38 | },
39 | (either) => either.fold(
40 | (apiError) => Text(apiError.message ?? 'Unknown Error!'),
41 | (result) => Column(
42 | crossAxisAlignment: CrossAxisAlignment.start,
43 | children: [
44 | Text(
45 | 'Select Category',
46 | style: TextStyle(
47 | fontSize: 20,
48 | fontWeight: FontWeight.w700,
49 | ),
50 | ),
51 | const SizedBox(
52 | height: 20,
53 | ),
54 | Wrap(
55 | spacing: 10,
56 | children: result.data.map((category) {
57 | bool isSelected = _selectedCategory?.id == category.id;
58 | return InkWell(
59 | onTap: () {
60 | _selectCategory(category);
61 | },
62 | child: Chip(
63 | backgroundColor:
64 | isSelected ? Theme.of(context).primaryColor : null,
65 | label: Text(
66 | category.name,
67 | style: TextStyle(
68 | color: isSelected ? Colors.white : null,
69 | ),
70 | ),
71 | ),
72 | );
73 | }).toList(),
74 | ),
75 | ],
76 | ),
77 | ),
78 | ),
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/article_create/widgets/image.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class ImageWidget extends StatelessWidget {
6 | final String path;
7 | final VoidCallback onDelete;
8 |
9 | const ImageWidget({Key key, @required this.path, this.onDelete})
10 | : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) => AspectRatio(
14 | aspectRatio: 1.5,
15 | child: Stack(
16 | children: [
17 | Positioned.fill(
18 | child: Container(
19 | height: 120,
20 | clipBehavior: Clip.antiAlias,
21 | decoration: BoxDecoration(
22 | color: Colors.grey[400],
23 | borderRadius: BorderRadius.circular(10),
24 | ),
25 | child: path != null
26 | ? Image.file(
27 | File(path),
28 | fit: BoxFit.cover,
29 | )
30 | : Icon(
31 | Icons.image,
32 | size: 70,
33 | color: Colors.white,
34 | ),
35 | ),
36 | ),
37 | PositionedDirectional(
38 | top: 5,
39 | end: 5,
40 | child: InkWell(
41 | onTap: onDelete,
42 | child: SizedBox(
43 | height: 30,
44 | width: 30,
45 | child: CircleAvatar(
46 | backgroundColor: Colors.white,
47 | child: Icon(Icons.close),
48 | ),
49 | ),
50 | ),
51 | ),
52 | ],
53 | ),
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/article_create/widgets/images.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:image_picker/image_picker.dart';
3 |
4 | import 'image.dart';
5 |
6 | class ImagesWidget extends StatefulWidget {
7 | final Function(List) onSelectImages;
8 |
9 | const ImagesWidget({Key key, this.onSelectImages}) : super(key: key);
10 |
11 | @override
12 | _ImagesWidgetState createState() => _ImagesWidgetState();
13 | }
14 |
15 | class _ImagesWidgetState extends State {
16 | List _selectedImages = [];
17 |
18 | final _imagePicker = ImagePicker();
19 |
20 | void _addImage() async {
21 | final pickedImage = await _imagePicker.getImage(
22 | source: ImageSource.gallery,
23 | );
24 | if (pickedImage == null) return;
25 |
26 | setState(() {
27 | _selectedImages.add(pickedImage.path);
28 | });
29 |
30 | widget.onSelectImages?.call(_selectedImages);
31 | }
32 |
33 | void _deleteImage(int index) async {
34 | setState(() {
35 | _selectedImages.removeAt(index);
36 | });
37 |
38 | widget.onSelectImages?.call(_selectedImages);
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) => Column(
43 | mainAxisSize: MainAxisSize.min,
44 | crossAxisAlignment: CrossAxisAlignment.start,
45 | children: [
46 | const SizedBox(
47 | height: 20,
48 | ),
49 | Text(
50 | 'Select Images',
51 | style: TextStyle(
52 | fontSize: 20,
53 | fontWeight: FontWeight.w700,
54 | ),
55 | ),
56 | const SizedBox(
57 | height: 20,
58 | ),
59 | Container(
60 | height: 150,
61 | padding: const EdgeInsets.all(10),
62 | decoration: BoxDecoration(
63 | border: Border.all(
64 | color: Colors.grey,
65 | ),
66 | borderRadius: BorderRadius.circular(10),
67 | ),
68 | child: ListView.separated(
69 | scrollDirection: Axis.horizontal,
70 | itemCount: _selectedImages.length + 1,
71 | itemBuilder: (_, index) => index < _selectedImages.length
72 | ? ImageWidget(
73 | path: _selectedImages[index],
74 | onDelete: () {
75 | _deleteImage(index);
76 | },
77 | )
78 | : InkWell(
79 | onTap: _addImage,
80 | child: Container(
81 | height: 120,
82 | width: 120,
83 | decoration: BoxDecoration(
84 | color: Colors.grey[300],
85 | borderRadius: BorderRadius.circular(10),
86 | ),
87 | child: Icon(
88 | Icons.add,
89 | color: Colors.grey,
90 | size: 50,
91 | ),
92 | ),
93 | ),
94 | separatorBuilder: (_, __) => const SizedBox(
95 | width: 10,
96 | ),
97 | ),
98 | ),
99 | ],
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/article_create/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'categories.dart';
2 | export 'images.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/articles/articles.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../application/articles/bloc.dart';
5 | import '../../../../infrastructure/core/preferences.dart';
6 | import '../../../../injection.dart';
7 | import '../../../routes/routes.dart';
8 | import 'widgets/widgets.dart';
9 |
10 | class ArticlesPage extends StatefulWidget {
11 | @override
12 | _ArticlesPageState createState() => _ArticlesPageState();
13 | }
14 |
15 | class _ArticlesPageState extends State
16 | with AutomaticKeepAliveClientMixin {
17 | final _bloc = getIt();
18 |
19 | final _preferences = getIt();
20 |
21 | void _loadData() {
22 | _bloc
23 | ..getCategories()
24 | ..getArticles();
25 | }
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 |
31 | _loadData();
32 | }
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | super.build(context);
37 | return Scaffold(
38 | body: SafeArea(
39 | child: RefreshIndicator(
40 | onRefresh: () {
41 | _loadData();
42 | return Future.value();
43 | },
44 | child: BlocProvider(
45 | create: (_) => _bloc,
46 | child: CustomScrollView(
47 | slivers: [
48 | SliverPadding(
49 | padding: const EdgeInsets.only(
50 | top: 30,
51 | left: 20,
52 | right: 20,
53 | bottom: 50,
54 | ),
55 | sliver: SliverToBoxAdapter(
56 | child: Row(
57 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
58 | children: [
59 | Expanded(
60 | child: Text(
61 | 'Hi There 👋 📰',
62 | style: TextStyle(
63 | color: Colors.black,
64 | fontSize: 30,
65 | fontWeight: FontWeight.w900,
66 | ),
67 | ),
68 | ),
69 | const SizedBox(
70 | width: 5,
71 | ),
72 | if (_preferences.isLoggedIn)
73 | SizedBox(
74 | height: 50,
75 | width: 50,
76 | child: CircleAvatar(
77 | backgroundColor: Colors.grey[200],
78 | child: IconButton(
79 | tooltip: 'Add Article',
80 | onPressed: () {
81 | Navigator.of(context).pushNamed(
82 | AppRoutes.articleCreate,
83 | );
84 | },
85 | icon: Icon(Icons.post_add),
86 | ),
87 | ),
88 | ),
89 | ],
90 | ),
91 | ),
92 | ),
93 | SliverPadding(
94 | padding: const EdgeInsets.only(bottom: 10),
95 | sliver: SliverToBoxAdapter(
96 | child: CategoriesWidget(
97 | onSelectCategory: (category) {
98 | if (category != null)
99 | _bloc.getArticlesByCategory(category.id);
100 | else
101 | _bloc.getArticles();
102 | },
103 | ),
104 | ),
105 | ),
106 | SliverPadding(
107 | padding: const EdgeInsets.only(bottom: 20),
108 | sliver: ArticlesWidget(),
109 | ),
110 | SliverToBoxAdapter(
111 | child: const SizedBox(
112 | height: 100,
113 | ),
114 | ),
115 | ],
116 | ),
117 | ),
118 | ),
119 | ),
120 | );
121 | }
122 |
123 | @override
124 | bool get wantKeepAlive => true;
125 | }
126 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/articles/widgets/articles.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../../application/articles/bloc.dart';
5 | import '../../../components/article_item.dart';
6 | import '../../../components/loading.dart';
7 |
8 | class ArticlesWidget extends StatefulWidget {
9 | @override
10 | _ArticlesWidgetState createState() => _ArticlesWidgetState();
11 | }
12 |
13 | class _ArticlesWidgetState extends State {
14 | @override
15 | Widget build(BuildContext context) =>
16 | BlocBuilder(
17 | builder: (context, state) => state.articlesState.fold(
18 | () => SliverToBoxAdapter(
19 | child: LoadingWidget(),
20 | ),
21 | (either) => either.fold(
22 | (apiError) => SliverToBoxAdapter(
23 | child: Text(apiError.message ?? 'Unknown Error!'),
24 | ),
25 | (result) => SliverList(
26 | delegate: SliverChildBuilderDelegate(
27 | (_, index) => ArticleItemWidget(
28 | article: result.data[index],
29 | ),
30 | childCount: result.data.length,
31 | ),
32 | ),
33 | ),
34 | ),
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/articles/widgets/categories.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../../application/articles/bloc.dart';
5 | import '../../../../../domain/entities/category/category.dart';
6 | import '../../../components/loading.dart';
7 |
8 | class CategoriesWidget extends StatefulWidget {
9 | final Function(Category) onSelectCategory;
10 |
11 | const CategoriesWidget({Key key, this.onSelectCategory}) : super(key: key);
12 |
13 | @override
14 | _CategoriesWidgetState createState() => _CategoriesWidgetState();
15 | }
16 |
17 | class _CategoriesWidgetState extends State {
18 | Category _selectedCategory;
19 |
20 | void _selectCategory(Category category) {
21 | setState(() {
22 | if (_selectedCategory?.id == category?.id)
23 | _selectedCategory = null;
24 | else
25 | _selectedCategory = category;
26 | });
27 |
28 | widget.onSelectCategory?.call(_selectedCategory);
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) =>
33 | BlocBuilder(
34 | builder: (context, state) => state.categoriesState.fold(
35 | () {
36 | _selectedCategory = null;
37 | return LoadingWidget();
38 | },
39 | (either) => either.fold(
40 | (apiError) => Text(apiError.message ?? 'Unknown Error!'),
41 | (result) => Column(
42 | crossAxisAlignment: CrossAxisAlignment.start,
43 | children: [
44 | Padding(
45 | padding: const EdgeInsets.only(
46 | left: 20,
47 | right: 20,
48 | bottom: 10,
49 | ),
50 | child: Text(
51 | 'Categories',
52 | style: TextStyle(
53 | fontSize: 20,
54 | fontWeight: FontWeight.w700,
55 | ),
56 | ),
57 | ),
58 | SizedBox(
59 | height: 50,
60 | child: ListView.separated(
61 | itemCount: result.data.length,
62 | scrollDirection: Axis.horizontal,
63 | padding: const EdgeInsets.symmetric(horizontal: 20),
64 | itemBuilder: (_, index) {
65 | bool isSelected =
66 | _selectedCategory?.id == result.data[index].id;
67 | return InkWell(
68 | onTap: () {
69 | _selectCategory(result.data[index]);
70 | },
71 | child: Chip(
72 | backgroundColor: isSelected
73 | ? Theme.of(context).primaryColor
74 | : null,
75 | label: Text(
76 | result.data[index].name,
77 | style: TextStyle(
78 | color: isSelected ? Colors.white : null,
79 | ),
80 | ),
81 | ),
82 | );
83 | },
84 | separatorBuilder: (_, __) => const SizedBox(
85 | width: 10,
86 | ),
87 | ),
88 | ),
89 | ],
90 | ),
91 | ),
92 | ),
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/articles/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'articles.dart';
2 | export 'categories.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/favorites/favorites.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../application/favorites/bloc.dart';
5 | import '../../../../infrastructure/core/preferences.dart';
6 | import '../../../../injection.dart';
7 | import '../../components/article_item.dart';
8 | import '../../components/loading.dart';
9 | import '../../components/login.dart';
10 |
11 | class FavoritesPage extends StatefulWidget {
12 | @override
13 | _FavoritesPageState createState() => _FavoritesPageState();
14 | }
15 |
16 | class _FavoritesPageState extends State {
17 | final _bloc = getIt();
18 |
19 | final _preferences = getIt();
20 |
21 | String _userId;
22 |
23 | Future _loadData() async {
24 | _bloc.getFavorites(_userId);
25 | return Future.value();
26 | }
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 |
32 | if (_preferences.isLoggedIn) {
33 | _userId = _preferences.userId;
34 | _loadData();
35 | }
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) => SafeArea(
40 | child: _preferences.isLoggedIn
41 | ? RefreshIndicator(
42 | onRefresh: _loadData,
43 | child: BlocBuilder(
44 | cubit: _bloc,
45 | builder: (_, state) => state.favoritesState.fold(
46 | () => LoadingWidget(),
47 | (either) => either.fold(
48 | (apiError) => Text(apiError.message ?? 'Unknown error!'),
49 | (result) => ListView.builder(
50 | padding: const EdgeInsets.only(
51 | top: 20,
52 | bottom: 100,
53 | ),
54 | itemCount: result.data.length,
55 | itemBuilder: (_, index) => ArticleItemWidget(
56 | article: result.data[index].article,
57 | ),
58 | ),
59 | ),
60 | ),
61 | ),
62 | )
63 | : AskLoginWidget(),
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/home/home.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../articles/articles.dart';
4 | import '../favorites/favorites.dart';
5 | import '../profile/profile.dart';
6 | import '../search/search.dart';
7 | import 'widgets/widgets.dart';
8 |
9 | class HomePage extends StatefulWidget {
10 | @override
11 | _HomePageState createState() => _HomePageState();
12 | }
13 |
14 | class _HomePageState extends State
15 | with AutomaticKeepAliveClientMixin {
16 | final _controller = PageController();
17 |
18 | final _indexNotifier = ValueNotifier(0);
19 |
20 | List _screens = [
21 | ArticlesPage(),
22 | SearchPage(),
23 | FavoritesPage(),
24 | ProfilePage(),
25 | ];
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 | _indexNotifier.addListener(() {
31 | _controller.jumpToPage(_indexNotifier.value);
32 | });
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | super.build(context);
38 | return Scaffold(
39 | body: Stack(
40 | children: [
41 | Positioned.fill(
42 | child: PageView(
43 | controller: _controller,
44 | onPageChanged: (index) {
45 | _indexNotifier.value = index;
46 | },
47 | children: _screens,
48 | ),
49 | ),
50 | Positioned(
51 | bottom: 0,
52 | left: 0,
53 | right: 0,
54 | child: BottomNavigationWidget(
55 | indexNotifier: _indexNotifier,
56 | ),
57 | ),
58 | ],
59 | ),
60 | );
61 | }
62 |
63 | @override
64 | bool get wantKeepAlive => true;
65 | }
66 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/home/widgets/bottom_tab_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_nav_bar/google_nav_bar.dart';
3 |
4 | class BottomNavigationWidget extends StatelessWidget {
5 | final ValueNotifier indexNotifier;
6 |
7 | BottomNavigationWidget({Key key, @required this.indexNotifier})
8 | : super(key: key);
9 |
10 | final List _tabs = [
11 | _buildTab(title: 'Articles', icon: Icons.article_outlined),
12 | _buildTab(title: 'Search', icon: Icons.search),
13 | _buildTab(title: 'Favorite', icon: Icons.favorite_border),
14 | _buildTab(title: 'Profile', icon: Icons.account_circle_outlined),
15 | ];
16 |
17 | @override
18 | Widget build(BuildContext context) => ValueListenableBuilder(
19 | valueListenable: indexNotifier,
20 | builder: (_, index, __) => Card(
21 | elevation: 10,
22 | margin: const EdgeInsets.all(10),
23 | shape: RoundedRectangleBorder(
24 | borderRadius: BorderRadius.circular(20),
25 | ),
26 | child: GNav(
27 | gap: 10,
28 | iconSize: 20,
29 | selectedIndex: index,
30 | tabBackgroundColor: Colors.grey[200],
31 | activeColor: Theme.of(context).primaryColor,
32 | padding: const EdgeInsets.symmetric(
33 | horizontal: 20,
34 | vertical: 10,
35 | ),
36 | onTabChange: (index) {
37 | indexNotifier.value = index;
38 | },
39 | tabs: _tabs,
40 | ),
41 | ),
42 | );
43 |
44 | static GButton _buildTab({String title, IconData icon}) => GButton(
45 | text: title,
46 | icon: icon,
47 | borderRadius: BorderRadius.circular(12.5),
48 | margin: const EdgeInsets.all(10),
49 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/home/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'bottom_tab_bar.dart';
2 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/on_boarding/on_boarding.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../../routes/routes.dart';
4 | import '../../components/buttons/rounded_button.dart';
5 | import '../../components/buttons/rounded_outline_button.dart';
6 | import 'widgets/widgets.dart';
7 |
8 | class OnBoardingPage extends StatefulWidget {
9 | @override
10 | _OnBoardingPageState createState() => _OnBoardingPageState();
11 | }
12 |
13 | class _OnBoardingPageState extends State {
14 | final ValueNotifier indexNotifier = ValueNotifier(0);
15 |
16 | final List _pages = [
17 | PageWidget(
18 | image: 'assets/images/space-man.svg',
19 | title: 'Let\'s Get Started',
20 | subtitle:
21 | 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
22 | ),
23 | PageWidget(
24 | image: 'assets/images/woman-laugh.svg',
25 | title: 'Let\'s Get Started',
26 | subtitle:
27 | 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
28 | ),
29 | PageWidget(
30 | image: 'assets/images/identity.svg',
31 | title: 'Let\'s Get Started',
32 | subtitle:
33 | 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
34 | ),
35 | ];
36 |
37 | @override
38 | Widget build(BuildContext context) => Scaffold(
39 | appBar: AppBar(
40 | elevation: 0,
41 | backgroundColor: Colors.transparent,
42 | ),
43 | body: Column(
44 | children: [
45 | SizedBox(
46 | height: MediaQuery.of(context).size.height / 2,
47 | child: Column(
48 | children: [
49 | Expanded(
50 | flex: 10,
51 | child: PageView(
52 | onPageChanged: (index) => indexNotifier.value = index,
53 | children: _pages,
54 | ),
55 | ),
56 | const Spacer(),
57 | IndicatorWidget(
58 | length: _pages.length,
59 | indexNotifier: indexNotifier,
60 | ),
61 | ],
62 | ),
63 | ),
64 | const Spacer(),
65 | SizedBox(
66 | width: MediaQuery.of(context).size.width * .7,
67 | child: Column(
68 | crossAxisAlignment: CrossAxisAlignment.stretch,
69 | children: [
70 | AppRoundedButton(
71 | onPressed: () {
72 | Navigator.of(context).pushNamed(AppRoutes.signIn);
73 | },
74 | title: 'Sign in',
75 | ),
76 | const SizedBox(
77 | height: 15,
78 | ),
79 | AppRoundedOutlineButton(
80 | onPressed: () {
81 | Navigator.of(context).pushNamed(AppRoutes.signUp);
82 | },
83 | title: 'Sign up',
84 | ),
85 | ],
86 | ),
87 | ),
88 | const Spacer(),
89 | MaterialButton(
90 | onPressed: () {
91 | Navigator.of(context).pushNamedAndRemoveUntil(
92 | AppRoutes.home,
93 | ModalRoute.withName('/'),
94 | );
95 | },
96 | child: Text(
97 | 'Getting Started',
98 | style: Theme.of(context).textTheme.button.copyWith(
99 | fontSize: 16,
100 | fontWeight: FontWeight.bold,
101 | decoration: TextDecoration.underline,
102 | ),
103 | ),
104 | ),
105 | const Spacer(),
106 | ],
107 | ),
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/on_boarding/widgets/dote.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DoteWidget extends StatelessWidget {
4 | final bool isSelected;
5 |
6 | const DoteWidget({Key key, this.isSelected = false}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Container(
11 | height: isSelected ? 7 : 5,
12 | width: isSelected ? 7 : 5,
13 | margin: const EdgeInsets.symmetric(horizontal: 5),
14 | decoration: BoxDecoration(
15 | color: isSelected ? Theme.of(context).primaryColor : Colors.grey,
16 | shape: BoxShape.circle,
17 | ),
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/on_boarding/widgets/indicator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'dote.dart';
4 |
5 | class IndicatorWidget extends StatelessWidget {
6 | final int length;
7 | final ValueNotifier indexNotifier;
8 |
9 | const IndicatorWidget({
10 | Key key,
11 | @required this.length,
12 | @required this.indexNotifier,
13 | }) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) => ValueListenableBuilder(
17 | valueListenable: indexNotifier,
18 | builder: (_, value, __) => Row(
19 | mainAxisAlignment: MainAxisAlignment.center,
20 | children: List.generate(
21 | length,
22 | (index) => DoteWidget(
23 | isSelected: value == index,
24 | ),
25 | ),
26 | ),
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/on_boarding/widgets/page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_svg/flutter_svg.dart';
3 |
4 | class PageWidget extends StatelessWidget {
5 | final String image;
6 | final String title;
7 | final String subtitle;
8 |
9 | const PageWidget({
10 | Key key,
11 | @required this.image,
12 | @required this.title,
13 | @required this.subtitle,
14 | }) : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) => Padding(
18 | padding: EdgeInsets.symmetric(
19 | horizontal: MediaQuery.of(context).size.width * .2,
20 | ),
21 | child: Column(
22 | children: [
23 | Expanded(
24 | child: SvgPicture.asset(
25 | image,
26 | ),
27 | ),
28 | const SizedBox(
29 | height: 25,
30 | ),
31 | Text(
32 | title,
33 | style: TextStyle(
34 | color: Colors.black,
35 | fontSize: 25,
36 | fontWeight: FontWeight.w900,
37 | ),
38 | ),
39 | const SizedBox(
40 | height: 15,
41 | ),
42 | Text(
43 | subtitle,
44 | style: TextStyle(
45 | color: Colors.grey[900],
46 | fontSize: 16,
47 | fontWeight: FontWeight.w300,
48 | ),
49 | textAlign: TextAlign.center,
50 | )
51 | ],
52 | ),
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/on_boarding/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'indicator.dart';
2 | export 'page.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/profile/profile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 |
4 | import '../../../../application/profile/bloc.dart';
5 | import '../../../../infrastructure/core/preferences.dart';
6 | import '../../../../injection.dart';
7 | import '../../../routes/routes.dart';
8 | import '../../../utils/extensions.dart';
9 | import '../../components/buttons/rounded_button.dart';
10 | import '../../components/dialogs/question.dart';
11 | import '../../components/dialogs/waiting.dart';
12 | import '../../components/loading.dart';
13 | import '../../components/login.dart';
14 | import '../../components/my_article_item.dart';
15 | import 'widgets/widgets.dart';
16 |
17 | class ProfilePage extends StatefulWidget {
18 | @override
19 | _ProfilePageState createState() => _ProfilePageState();
20 | }
21 |
22 | class _ProfilePageState extends State
23 | with AutomaticKeepAliveClientMixin {
24 | final _bloc = getIt();
25 |
26 | final _preferences = getIt();
27 |
28 | String _userId;
29 |
30 | void _logout() async {
31 | context.showAppDialog(
32 | isDismissible: false,
33 | child: AppDialogWaiting(),
34 | );
35 | await _preferences.clear();
36 | Navigator.of(context).pushNamedAndRemoveUntil(
37 | AppRoutes.onBoarding,
38 | ModalRoute.withName('/'),
39 | );
40 | }
41 |
42 | void _askLogout() {
43 | context.showAppDialog(
44 | child: AppDialogQuestion(
45 | title: 'Logout',
46 | question: 'Are you sure you want to leave us?',
47 | actions: [
48 | MaterialButton(
49 | onPressed: () {
50 | Navigator.pop(context);
51 | },
52 | child: Text('No'),
53 | ),
54 | AppRoundedButton(
55 | onPressed: () {
56 | Navigator.pop(context);
57 | _logout();
58 | },
59 | padding: const EdgeInsets.symmetric(horizontal: 20),
60 | radius: 5,
61 | elevation: 0,
62 | title: 'Yes',
63 | ),
64 | ],
65 | ),
66 | );
67 | }
68 |
69 | void _loadData() {
70 | _bloc
71 | ..getUserInformation(_userId)
72 | ..getArticlesByUser(_userId);
73 | }
74 |
75 | @override
76 | void initState() {
77 | super.initState();
78 |
79 | if (_preferences.isLoggedIn) {
80 | _userId = _preferences.userId;
81 | _loadData();
82 | }
83 | }
84 |
85 | @override
86 | Widget build(BuildContext context) {
87 | super.build(context);
88 | return SafeArea(
89 | child: _preferences.isLoggedIn
90 | ? BlocBuilder(
91 | cubit: _bloc,
92 | builder: (_, state) => RefreshIndicator(
93 | onRefresh: () {
94 | _loadData();
95 | return Future.value();
96 | },
97 | child: CustomScrollView(
98 | slivers: [
99 | state.userInformationState.fold(
100 | () => SliverToBoxAdapter(
101 | child: LoadingWidget(),
102 | ),
103 | (either) => either.fold(
104 | (apiError) => SliverToBoxAdapter(),
105 | (result) => SliverPadding(
106 | padding: const EdgeInsets.only(bottom: 30),
107 | sliver: SliverToBoxAdapter(
108 | child: HeaderWidget(
109 | onLogout: _askLogout,
110 | user: result.data,
111 | ),
112 | ),
113 | ),
114 | ),
115 | ),
116 | SliverPadding(
117 | padding: const EdgeInsets.symmetric(horizontal: 20),
118 | sliver: SliverToBoxAdapter(
119 | child: Text(
120 | 'My Articles',
121 | style: TextStyle(
122 | fontSize: 20,
123 | fontWeight: FontWeight.w700,
124 | ),
125 | ),
126 | ),
127 | ),
128 | state.articlesState.fold(
129 | () => SliverToBoxAdapter(
130 | child: LoadingWidget(),
131 | ),
132 | (either) => either.fold(
133 | (apiError) => SliverToBoxAdapter(),
134 | (result) => SliverList(
135 | delegate: SliverChildBuilderDelegate(
136 | (_, index) => Padding(
137 | padding: const EdgeInsets.symmetric(
138 | horizontal: 20,
139 | ),
140 | child: MyArticleItemWidget(
141 | article: result.data[index],
142 | ),
143 | ),
144 | childCount: result.data.length,
145 | ),
146 | ),
147 | ),
148 | ),
149 | SliverPadding(
150 | padding: const EdgeInsets.only(bottom: 100),
151 | ),
152 | ],
153 | ),
154 | ),
155 | )
156 | : AskLoginWidget(),
157 | );
158 | }
159 |
160 | @override
161 | bool get wantKeepAlive => true;
162 | }
163 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/profile/widgets/header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../../../../domain/entities/user/user.dart';
4 |
5 | class HeaderWidget extends StatelessWidget {
6 | final User user;
7 | final VoidCallback onLogout;
8 |
9 | const HeaderWidget({Key key, @required this.user, this.onLogout})
10 | : super(key: key);
11 |
12 | @override
13 | Widget build(BuildContext context) => Container(
14 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
15 | decoration: BoxDecoration(
16 | color: Theme.of(context).primaryColor,
17 | borderRadius: BorderRadius.vertical(
18 | bottom: Radius.circular(20),
19 | ),
20 | ),
21 | child: ListTile(
22 | onTap: () {},
23 | contentPadding: const EdgeInsets.all(0),
24 | leading: Icon(
25 | Icons.account_circle,
26 | size: 60,
27 | color: Colors.white,
28 | ),
29 | title: Text(
30 | '${user.firstName} ${user.lastName}',
31 | maxLines: 1,
32 | overflow: TextOverflow.ellipsis,
33 | style: TextStyle(
34 | color: Colors.white,
35 | fontWeight: FontWeight.w700,
36 | ),
37 | ),
38 | subtitle: Text(
39 | user.email,
40 | maxLines: 1,
41 | overflow: TextOverflow.ellipsis,
42 | style: TextStyle(
43 | color: Colors.grey[300],
44 | ),
45 | ),
46 | trailing: IconButton(
47 | onPressed: onLogout,
48 | tooltip: 'Logout',
49 | color: Colors.white,
50 | icon: Icon(Icons.login),
51 | ),
52 | ),
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/profile/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'header.dart';
2 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/search/search.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 |
5 | import '../../../../application/search/bloc.dart';
6 | import '../../../../injection.dart';
7 | import '../../components/article_item.dart';
8 | import '../../components/loading.dart';
9 | import '../../components/text_fields/search_field.dart';
10 |
11 | class SearchPage extends StatefulWidget {
12 | @override
13 | _SearchPageState createState() => _SearchPageState();
14 | }
15 |
16 | class _SearchPageState extends State
17 | with AutomaticKeepAliveClientMixin {
18 | final _bloc = getIt();
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | super.build(context);
23 | return SafeArea(
24 | child: CustomScrollView(
25 | slivers: [
26 | SliverAppBar(
27 | pinned: true,
28 | expandedHeight: 180,
29 | shape: RoundedRectangleBorder(
30 | borderRadius: BorderRadius.vertical(
31 | bottom: Radius.circular(20),
32 | ),
33 | ),
34 | bottom: PreferredSize(
35 | preferredSize: const Size(double.infinity, 40),
36 | child: Container(
37 | margin: const EdgeInsets.all(20),
38 | padding: const EdgeInsets.symmetric(horizontal: 10),
39 | decoration: BoxDecoration(
40 | color: Colors.black.withOpacity(.2),
41 | borderRadius: BorderRadius.circular(25),
42 | ),
43 | child: SearchFieldWidget(
44 | onChanged: _bloc.searchArticles,
45 | autoFocus: false,
46 | ),
47 | ),
48 | ),
49 | leading: const SizedBox(),
50 | ),
51 | BlocBuilder(
52 | cubit: _bloc,
53 | builder: (_, state) => state.searchState.fold(
54 | () => state.isSearching
55 | ? SliverFillRemaining(
56 | child: LoadingWidget(),
57 | )
58 | : SliverFillRemaining(
59 | child: const SizedBox(),
60 | ),
61 | (either) => either.fold(
62 | (apiError) => const SizedBox(),
63 | (result) => SliverList(
64 | delegate: SliverChildBuilderDelegate(
65 | (_, index) => ArticleItemWidget(
66 | article: result.data[index],
67 | ),
68 | childCount: result.data.length,
69 | ),
70 | ),
71 | ),
72 | ),
73 | ),
74 | SliverPadding(
75 | padding: const EdgeInsets.only(bottom: 100),
76 | ),
77 | ],
78 | ),
79 | );
80 | }
81 |
82 | @override
83 | bool get wantKeepAlive => true;
84 | }
85 |
--------------------------------------------------------------------------------
/lib/src/presentation/ui/pages/sign_in/sign_in.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttertoast/fluttertoast.dart';
4 |
5 | import '../../../../application/sign_in/bloc.dart';
6 | import '../../../../domain/entities/user/user.dart';
7 | import '../../../../infrastructure/core/preferences.dart';
8 | import '../../../../injection.dart';
9 | import '../../../routes/routes.dart';
10 | import '../../../utils/extensions.dart';
11 | import '../../components/buttons/rounded_button.dart';
12 | import '../../components/dialogs/waiting.dart';
13 | import '../../components/text_fields/rounded_outline_text_field.dart';
14 |
15 | class SignInPage extends StatefulWidget {
16 | @override
17 | _SignInPageState createState() => _SignInPageState();
18 | }
19 |
20 | class _SignInPageState extends State {
21 | final _bloc = getIt();
22 |
23 | final _preferences = getIt();
24 |
25 | final _formKey = GlobalKey();
26 |
27 | final _emailController = TextEditingController();
28 | final _passwordController = TextEditingController();
29 |
30 | void _saveUserData(User user) async {
31 | await _preferences.setString(AppPreferencesKeys.USER_ID, user.id);
32 | await _preferences.setString(AppPreferencesKeys.TOKEN, user.token);
33 | Navigator.of(context).pushNamedAndRemoveUntil(
34 | AppRoutes.home,
35 | ModalRoute.withName(''),
36 | );
37 | }
38 |
39 | void _validForm() {
40 | bool isFormValid = _formKey.currentState.validate();
41 | if (!isFormValid) return;
42 | final user = User(
43 | email: _emailController.text,
44 | password: _passwordController.text,
45 | );
46 | _bloc.signIn(user);
47 | }
48 |
49 | @override
50 | void initState() {
51 | super.initState();
52 | _bloc.listen(
53 | (state) {
54 | state.signInState.fold(
55 | () => context.showAppDialog(
56 | isDismissible: false,
57 | child: AppDialogWaiting(),
58 | ),
59 | (either) => either.fold(
60 | (apiError) {
61 | Navigator.pop(context);
62 | Fluttertoast.showToast(msg: apiError.message);
63 | },
64 | (result) {
65 | _saveUserData(result.data);
66 | },
67 | ),
68 | );
69 | },
70 | );
71 | }
72 |
73 | @override
74 | Widget build(BuildContext context) => Scaffold(
75 | appBar: AppBar(
76 | elevation: 0,
77 | backgroundColor: Colors.transparent,
78 | leading: IconButton(
79 | onPressed: () {
80 | Navigator.pop(context);
81 | },
82 | icon: Icon(
83 | Icons.arrow_back_ios,
84 | color: Colors.black,
85 | ),
86 | ),
87 | ),
88 | body: ListView(
89 | padding: const EdgeInsets.symmetric(horizontal: 20),
90 | children: [
91 | const SizedBox(
92 | height: 50,
93 | ),
94 | const Text(
95 | 'Sign in',
96 | style: TextStyle(
97 | color: Colors.black,
98 | fontSize: 30,
99 | fontWeight: FontWeight.w900,
100 | ),
101 | ),
102 | SizedBox(
103 | height: MediaQuery.of(context).size.height * .1,
104 | ),
105 | Card(
106 | elevation: 10,
107 | shadowColor: Colors.black.withOpacity(.3),
108 | shape: RoundedRectangleBorder(
109 | borderRadius: BorderRadius.circular(9),
110 | ),
111 | margin: const EdgeInsets.all(0),
112 | child: Form(
113 | key: _formKey,
114 | child: Padding(
115 | padding: const EdgeInsets.all(20),
116 | child: Column(
117 | mainAxisSize: MainAxisSize.min,
118 | crossAxisAlignment: CrossAxisAlignment.stretch,
119 | children: [
120 | AppRoundedOutlineTextFormField(
121 | controller: _emailController,
122 | validator: (value) => value.isValidEmail
123 | ? null
124 | : 'Please insert valid email!',
125 | hint: 'Email',
126 | keyboardType: TextInputType.emailAddress,
127 | textInputAction: TextInputAction.next,
128 | prefixIcon: Icons.email_outlined,
129 | ),
130 | const SizedBox(
131 | height: 10,
132 | ),
133 | AppRoundedOutlineTextFormField(
134 | controller: _passwordController,
135 | validator: (value) => value.isValidPassword
136 | ? null
137 | : 'Please insert valid password!',
138 | hint: 'Password',
139 | obscureText: true,
140 | prefixIcon: Icons.lock_outline,
141 | ),
142 | const SizedBox(
143 | height: 50,
144 | ),
145 | AppRoundedButton(
146 | onPressed: _validForm,
147 | title: 'Sign in',
148 | ),
149 | ],
150 | ),
151 | ),
152 | ),
153 | ),
154 | ],
155 | ),
156 | );
157 |
158 | @override
159 | void dispose() {
160 | _emailController.dispose();
161 | _passwordController.dispose();
162 | super.dispose();
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/lib/src/presentation/utils/extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'validators.dart' as validators;
4 |
5 | extension StringExtensions on String {
6 | bool get isValidEmail => validators.isValidEmail(this);
7 |
8 | bool get isValidPassword => validators.isValidPassword(this);
9 | }
10 |
11 | extension BuildContextExtensions on BuildContext {
12 | Future showAppDialog({
13 | @required Widget child,
14 | bool isDismissible = true,
15 | }) =>
16 | showDialog(
17 | context: this,
18 | barrierDismissible: isDismissible,
19 | builder: (_) => WillPopScope(
20 | onWillPop: () async {
21 | return isDismissible;
22 | },
23 | child: child,
24 | ),
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/presentation/utils/validators.dart:
--------------------------------------------------------------------------------
1 | const _pattern =
2 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
3 |
4 | final _regex = RegExp(_pattern);
5 | bool isValidEmail(String value) => _regex.hasMatch(value);
6 |
7 | bool isValidPassword(String value) => value.length >= 5;
8 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: Blog
2 | description: A Flutter blog.
3 |
4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: ">=2.7.0 <3.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 | cached_network_image: ^2.5.0
15 | cupertino_icons: ^1.0.0
16 | dartz: ^0.9.2
17 | flutter_bloc: ^6.1.1
18 | flutter_svg: any
19 | fluttertoast: ^7.1.6
20 | freezed_annotation: ^0.12.0
21 | get_it: ^5.0.3
22 | google_nav_bar: ^3.1.0
23 | image_picker: ^0.6.7+21
24 | injectable: ^1.0.7
25 | intl: ^0.16.1
26 | logger: ^0.9.4
27 | retrofit: ^1.3.4+1
28 | shared_preferences: ^0.5.12+4
29 | smooth_page_indicator: ^0.2.0
30 |
31 | dev_dependencies:
32 | flutter_test:
33 | sdk: flutter
34 | build_runner: ^1.10.11
35 | freezed: ^0.12.6
36 | json_serializable: ^3.5.1
37 | injectable_generator: ^1.0.7
38 | mocktail:
39 | retrofit_generator: ^1.4.1+1
40 |
41 | # For information on the generic Dart part of this file, see the
42 | # following page: https://dart.dev/tools/pub/pubspec
43 |
44 | # The following section is specific to Flutter.
45 | flutter:
46 |
47 | # The following line ensures that the Material Icons font is
48 | # included with your application, so that you can use the icons in
49 | # the material Icons class.
50 | uses-material-design: true
51 |
52 | # To add assets to your application, add an assets section, like this:
53 | assets:
54 | - assets/images/
55 | # - images/a_dot_ham.jpeg
56 |
57 | # An image asset can refer to one or more resolution-specific "variants", see
58 | # https://flutter.dev/assets-and-images/#resolution-aware.
59 |
60 | # For details regarding adding assets from package dependencies, see
61 | # https://flutter.dev/assets-and-images/#from-packages
62 |
63 | # To add custom fonts to your application, add a fonts section here,
64 | # in this "flutter" section. Each entry in this list should have a
65 | # "family" key with the font family name, and a "fonts" key with a
66 | # list giving the asset and other descriptors for the font. For
67 | # example:
68 | # fonts:
69 | # - family: Schyler
70 | # fonts:
71 | # - asset: fonts/Schyler-Regular.ttf
72 | # - asset: fonts/Schyler-Italic.ttf
73 | # style: italic
74 | # - family: Trajan Pro
75 | # fonts:
76 | # - asset: fonts/TrajanPro.ttf
77 | # - asset: fonts/TrajanPro_Bold.ttf
78 | # weight: 700
79 | #
80 | # For details regarding fonts from package dependencies,
81 | # see https://flutter.dev/custom-fonts/#from-packages
82 |
--------------------------------------------------------------------------------
/screenshot/flutter_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_01.png
--------------------------------------------------------------------------------
/screenshot/flutter_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_02.png
--------------------------------------------------------------------------------
/screenshot/flutter_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_03.png
--------------------------------------------------------------------------------
/screenshot/flutter_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_04.png
--------------------------------------------------------------------------------
/screenshot/flutter_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_05.png
--------------------------------------------------------------------------------
/screenshot/flutter_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_06.png
--------------------------------------------------------------------------------
/screenshot/flutter_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_07.png
--------------------------------------------------------------------------------
/screenshot/flutter_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeekAbdelouahed/Flutter-Blog/1f1d9ea881992dc867fa79eed40c6496c405c2fa/screenshot/flutter_08.png
--------------------------------------------------------------------------------
/test/extensions_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import '../lib/src/presentation/utils/extensions.dart';
3 |
4 | void main() {
5 | group('String extensions Test', () {
6 | test('Email without username test', () async {
7 | String email = '@gmail.com';
8 |
9 | bool isValid = email.isValidEmail;
10 |
11 | expect(isValid, false);
12 | });
13 |
14 | test('Email without symbole @ test', () async {
15 | String email = 'hello.gmail';
16 |
17 | bool isValid = email.isValidEmail;
18 |
19 | expect(isValid, false);
20 | });
21 |
22 | test('Email without mail server test', () async {
23 | String email = 'hello@.com';
24 |
25 | bool isValid = email.isValidEmail;
26 |
27 | expect(isValid, false);
28 | });
29 |
30 | test('Email without domain test', () async {
31 | String email = 'hello@gmail';
32 |
33 | bool isValid = email.isValidEmail;
34 |
35 | expect(isValid, false);
36 | });
37 |
38 | test('Valid email test', () async {
39 | String email = 'hello@gmail.com';
40 |
41 | bool isValid = email.isValidEmail;
42 |
43 | expect(isValid, true);
44 | });
45 |
46 | test('Not valid password test', () async {
47 | String email = '123';
48 |
49 | bool isValid = email.isValidPassword;
50 |
51 | expect(isValid, false);
52 | });
53 |
54 | test('Valid password test', () async {
55 | String email = 'helloworld';
56 |
57 | bool isValid = email.isValidPassword;
58 |
59 | expect(isValid, true);
60 | });
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/test/test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartz/dartz.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:mocktail/mocktail.dart';
4 |
5 | import '../lib/src/domain/entities/api_response/api_response.dart';
6 | import '../lib/src/domain/entities/article/article.dart';
7 | import '../lib/src/domain/articles/i_articles_facade.dart';
8 |
9 | class MockArticlesFacade extends Mock implements IArticlesFacade {}
10 |
11 | void main() {
12 | MockArticlesFacade articlesFacade = MockArticlesFacade();
13 |
14 | group('Articles Test', () {
15 | test('Not empty articles', () async {
16 | when(() => articlesFacade.getArticles()).thenAnswer(
17 | ((_) async {
18 | return right(
19 | ApiResponse