diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..1d331a6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,20 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + sort_pub_dependencies: false + prefer_relative_imports: true -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +analyzer: + errors: + missing_required_param: error + missing_return: error + must_be_immutable: error + sort_unnamed_constructors_first: ignore + invalid_annotation_target: ignore + use_build_context_synchronously: ignore + deprecated_member_use: ignore + exclude: + - test/generated/** + - "**/**.g.dart" + - "**/**.freezed.dart" diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 275aab3..0ff489c 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -6,9 +6,9 @@ plugins { } android { - namespace = "com.example.enaklo" + namespace = "com.appskel.enaklo" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.enaklo" + applicationId = "com.appskel.enaklo" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 63ab43a..f58f6ea 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,15 @@ + + + + + + + + android:icon="@mipmap/launcher_icon"> + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000..2f03537 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..5ccbb99 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..0163b65 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..23b553b Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..bddf826 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ab98328 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #ffffff + \ No newline at end of file diff --git a/assets/audio/bell_ding.mp3 b/assets/audio/bell_ding.mp3 new file mode 100644 index 0000000..c53cd50 Binary files /dev/null and b/assets/audio/bell_ding.mp3 differ diff --git a/assets/audio/big_win.mp3 b/assets/audio/big_win.mp3 new file mode 100644 index 0000000..d13902f Binary files /dev/null and b/assets/audio/big_win.mp3 differ diff --git a/assets/audio/button_tap.mp3 b/assets/audio/button_tap.mp3 new file mode 100644 index 0000000..32337ba Binary files /dev/null and b/assets/audio/button_tap.mp3 differ diff --git a/assets/audio/carnaval_main_theme.mp3 b/assets/audio/carnaval_main_theme.mp3 new file mode 100644 index 0000000..65f20db Binary files /dev/null and b/assets/audio/carnaval_main_theme.mp3 differ diff --git a/assets/audio/token_sound.mp3 b/assets/audio/token_sound.mp3 new file mode 100644 index 0000000..d3dcd2c Binary files /dev/null and b/assets/audio/token_sound.mp3 differ diff --git a/assets/audio/wheel_spin.mp3 b/assets/audio/wheel_spin.mp3 new file mode 100644 index 0000000..4d49b91 Binary files /dev/null and b/assets/audio/wheel_spin.mp3 differ diff --git a/assets/fonts/quicksand/Quicksand-Bold.ttf b/assets/fonts/quicksand/Quicksand-Bold.ttf new file mode 100644 index 0000000..0106805 Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Bold.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Light.ttf b/assets/fonts/quicksand/Quicksand-Light.ttf new file mode 100644 index 0000000..fcf86af Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Light.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Medium.ttf b/assets/fonts/quicksand/Quicksand-Medium.ttf new file mode 100644 index 0000000..afca2d9 Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Medium.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Regular.ttf b/assets/fonts/quicksand/Quicksand-Regular.ttf new file mode 100644 index 0000000..c03548a Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Regular.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-SemiBold.ttf b/assets/fonts/quicksand/Quicksand-SemiBold.ttf new file mode 100644 index 0000000..83475ea Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-SemiBold.ttf differ diff --git a/assets/images/bakso343.jpeg b/assets/images/bakso343.jpeg new file mode 100644 index 0000000..ef67f5e Binary files /dev/null and b/assets/images/bakso343.jpeg differ diff --git a/assets/images/launcher.png b/assets/images/launcher.png new file mode 100644 index 0000000..e135668 Binary files /dev/null and b/assets/images/launcher.png differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..ddc7ef9 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/onboarding1.png b/assets/images/onboarding1.png new file mode 100644 index 0000000..915d15c Binary files /dev/null and b/assets/images/onboarding1.png differ diff --git a/assets/images/onboarding2.png b/assets/images/onboarding2.png new file mode 100644 index 0000000..3241ae5 Binary files /dev/null and b/assets/images/onboarding2.png differ diff --git a/assets/images/onboarding3.png b/assets/images/onboarding3.png new file mode 100644 index 0000000..efeb1ea Binary files /dev/null and b/assets/images/onboarding3.png differ diff --git a/assets/images/ramenmulu.jpeg b/assets/images/ramenmulu.jpeg new file mode 100644 index 0000000..6ee1919 Binary files /dev/null and b/assets/images/ramenmulu.jpeg differ diff --git a/assets/images/tumulu.jpeg b/assets/images/tumulu.jpeg new file mode 100644 index 0000000..227af1a Binary files /dev/null and b/assets/images/tumulu.jpeg differ diff --git a/assets/images/woku.jpeg b/assets/images/woku.jpeg new file mode 100644 index 0000000..543bce2 Binary files /dev/null and b/assets/images/woku.jpeg differ diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2e93610..daae6ee 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -384,7 +384,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -401,7 +401,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -416,7 +416,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -427,7 +427,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -484,7 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -547,7 +547,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -569,7 +569,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..7fe0a2b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..e6ee88e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..a72e2e2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..495424b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..ca919a9 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..afb8b24 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..3038495 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..a72e2e2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..343e05b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..2041602 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..74b237f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..2800cbc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..030df31 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..92676ff Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..2041602 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..fd73bc2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..2f03537 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..23b553b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..510b6d2 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..cc4b908 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..c89ad08 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/launcher_icon.yaml b/launcher_icon.yaml new file mode 100644 index 0000000..c74c4bb --- /dev/null +++ b/launcher_icon.yaml @@ -0,0 +1,17 @@ +# Generate: dart run flutter_launcher_icons -f launcher_icon.yaml + +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/images/launcher.png" + remove_alpha_ios: true + min_sdk_android: 21 # android min sdk min:16, default 21 + adaptive_icon_background: "#ffffff" + adaptive_icon_foreground: "assets/images/launcher.png" + web: + generate: true + image_path: "assets/images/launcher.png" + windows: + generate: true + image_path: "assets/images/launcher.png" + icon_size: 48 diff --git a/lib/application/auth/auth_bloc.dart b/lib/application/auth/auth_bloc.dart new file mode 100644 index 0000000..0ca87c5 --- /dev/null +++ b/lib/application/auth/auth_bloc.dart @@ -0,0 +1,49 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../domain/auth/auth.dart'; + +part 'auth_event.dart'; +part 'auth_state.dart'; +part 'auth_bloc.freezed.dart'; + +@injectable +class AuthBloc extends Bloc { + final IAuthRepository _repository; + AuthBloc(this._repository) : super(AuthState.initial()) { + on(_onAuthEvent); + } + + Future _onAuthEvent(AuthEvent event, Emitter emit) { + return event.map( + fetchCurrentUser: (e) async { + emit(state.copyWith(failureOption: none())); + + final token = await _repository.hasToken(); + + final failureOrAuth = await _repository.currentUser(); + + failureOrAuth.fold( + (f) => emit( + state.copyWith( + failureOption: optionOf(f), + status: token + ? AuthStatus.authenticated() + : AuthStatus.unauthenticated(), + ), + ), + (user) => emit( + state.copyWith( + user: user, + status: token + ? AuthStatus.authenticated() + : AuthStatus.unauthenticated(), + ), + ), + ); + }, + ); + } +} diff --git a/lib/application/auth/auth_bloc.freezed.dart b/lib/application/auth/auth_bloc.freezed.dart new file mode 100644 index 0000000..7fd3e02 --- /dev/null +++ b/lib/application/auth/auth_bloc.freezed.dart @@ -0,0 +1,806 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$AuthEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetchCurrentUser, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_FetchCurrentUser value) fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_FetchCurrentUser value)? fetchCurrentUser, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_FetchCurrentUser value)? fetchCurrentUser, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthEventCopyWith<$Res> { + factory $AuthEventCopyWith(AuthEvent value, $Res Function(AuthEvent) then) = + _$AuthEventCopyWithImpl<$Res, AuthEvent>; +} + +/// @nodoc +class _$AuthEventCopyWithImpl<$Res, $Val extends AuthEvent> + implements $AuthEventCopyWith<$Res> { + _$AuthEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchCurrentUserImplCopyWith<$Res> { + factory _$$FetchCurrentUserImplCopyWith( + _$FetchCurrentUserImpl value, + $Res Function(_$FetchCurrentUserImpl) then, + ) = __$$FetchCurrentUserImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchCurrentUserImplCopyWithImpl<$Res> + extends _$AuthEventCopyWithImpl<$Res, _$FetchCurrentUserImpl> + implements _$$FetchCurrentUserImplCopyWith<$Res> { + __$$FetchCurrentUserImplCopyWithImpl( + _$FetchCurrentUserImpl _value, + $Res Function(_$FetchCurrentUserImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchCurrentUserImpl implements _FetchCurrentUser { + const _$FetchCurrentUserImpl(); + + @override + String toString() { + return 'AuthEvent.fetchCurrentUser()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchCurrentUserImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() fetchCurrentUser, + }) { + return fetchCurrentUser(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetchCurrentUser, + }) { + return fetchCurrentUser?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetchCurrentUser, + required TResult orElse(), + }) { + if (fetchCurrentUser != null) { + return fetchCurrentUser(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_FetchCurrentUser value) fetchCurrentUser, + }) { + return fetchCurrentUser(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_FetchCurrentUser value)? fetchCurrentUser, + }) { + return fetchCurrentUser?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_FetchCurrentUser value)? fetchCurrentUser, + required TResult orElse(), + }) { + if (fetchCurrentUser != null) { + return fetchCurrentUser(this); + } + return orElse(); + } +} + +abstract class _FetchCurrentUser implements AuthEvent { + const factory _FetchCurrentUser() = _$FetchCurrentUserImpl; +} + +/// @nodoc +mixin _$AuthState { + User get user => throw _privateConstructorUsedError; + AuthStatus get status => throw _privateConstructorUsedError; + Option get failureOption => throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AuthStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStateCopyWith<$Res> { + factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = + _$AuthStateCopyWithImpl<$Res, AuthState>; + @useResult + $Res call({ + User user, + AuthStatus status, + Option failureOption, + bool isFetching, + }); + + $UserCopyWith<$Res> get user; + $AuthStatusCopyWith<$Res> get status; +} + +/// @nodoc +class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> + implements $AuthStateCopyWith<$Res> { + _$AuthStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? user = null, + Object? status = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserCopyWith<$Res> get user { + return $UserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AuthStatusCopyWith<$Res> get status { + return $AuthStatusCopyWith<$Res>(_value.status, (value) { + return _then(_value.copyWith(status: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AuthStateImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthStateImplCopyWith( + _$AuthStateImpl value, + $Res Function(_$AuthStateImpl) then, + ) = __$$AuthStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + User user, + AuthStatus status, + Option failureOption, + bool isFetching, + }); + + @override + $UserCopyWith<$Res> get user; + @override + $AuthStatusCopyWith<$Res> get status; +} + +/// @nodoc +class __$$AuthStateImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateImpl> + implements _$$AuthStateImplCopyWith<$Res> { + __$$AuthStateImplCopyWithImpl( + _$AuthStateImpl _value, + $Res Function(_$AuthStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? user = null, + Object? status = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _$AuthStateImpl( + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$AuthStateImpl extends _AuthState { + const _$AuthStateImpl({ + required this.user, + this.status = const AuthStatus.initial(), + required this.failureOption, + this.isFetching = false, + }) : super._(); + + @override + final User user; + @override + @JsonKey() + final AuthStatus status; + @override + final Option failureOption; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'AuthState(user: $user, status: $status, failureOption: $failureOption, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateImpl && + (identical(other.user, user) || other.user == user) && + (identical(other.status, status) || other.status == status) && + (identical(other.failureOption, failureOption) || + other.failureOption == failureOption) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => + Object.hash(runtimeType, user, status, failureOption, isFetching); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + __$$AuthStateImplCopyWithImpl<_$AuthStateImpl>(this, _$identity); +} + +abstract class _AuthState extends AuthState { + const factory _AuthState({ + required final User user, + final AuthStatus status, + required final Option failureOption, + final bool isFetching, + }) = _$AuthStateImpl; + const _AuthState._() : super._(); + + @override + User get user; + @override + AuthStatus get status; + @override + Option get failureOption; + @override + bool get isFetching; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$AuthStatus { + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStatusCopyWith<$Res> { + factory $AuthStatusCopyWith( + AuthStatus value, + $Res Function(AuthStatus) then, + ) = _$AuthStatusCopyWithImpl<$Res, AuthStatus>; +} + +/// @nodoc +class _$AuthStatusCopyWithImpl<$Res, $Val extends AuthStatus> + implements $AuthStatusCopyWith<$Res> { + _$AuthStatusCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$AuthenticatedImplCopyWith<$Res> { + factory _$$AuthenticatedImplCopyWith( + _$AuthenticatedImpl value, + $Res Function(_$AuthenticatedImpl) then, + ) = __$$AuthenticatedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$AuthenticatedImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$AuthenticatedImpl> + implements _$$AuthenticatedImplCopyWith<$Res> { + __$$AuthenticatedImplCopyWithImpl( + _$AuthenticatedImpl _value, + $Res Function(_$AuthenticatedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$AuthenticatedImpl implements _Authenticated { + const _$AuthenticatedImpl(); + + @override + String toString() { + return 'AuthStatus.authenticated()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$AuthenticatedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return authenticated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return authenticated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return authenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return authenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(this); + } + return orElse(); + } +} + +abstract class _Authenticated implements AuthStatus { + const factory _Authenticated() = _$AuthenticatedImpl; +} + +/// @nodoc +abstract class _$$UnauthenticatedImplCopyWith<$Res> { + factory _$$UnauthenticatedImplCopyWith( + _$UnauthenticatedImpl value, + $Res Function(_$UnauthenticatedImpl) then, + ) = __$$UnauthenticatedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnauthenticatedImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$UnauthenticatedImpl> + implements _$$UnauthenticatedImplCopyWith<$Res> { + __$$UnauthenticatedImplCopyWithImpl( + _$UnauthenticatedImpl _value, + $Res Function(_$UnauthenticatedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnauthenticatedImpl implements _Unauthenticated { + const _$UnauthenticatedImpl(); + + @override + String toString() { + return 'AuthStatus.unauthenticated()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnauthenticatedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return unauthenticated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return unauthenticated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return unauthenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return unauthenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(this); + } + return orElse(); + } +} + +abstract class _Unauthenticated implements AuthStatus { + const factory _Unauthenticated() = _$UnauthenticatedImpl; +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, + $Res Function(_$InitialImpl) then, + ) = __$$InitialImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$AuthStatusCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, + $Res Function(_$InitialImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthStatus + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'AuthStatus.initial()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$InitialImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() authenticated, + required TResult Function() unauthenticated, + required TResult Function() initial, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? authenticated, + TResult? Function()? unauthenticated, + TResult? Function()? initial, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? authenticated, + TResult Function()? unauthenticated, + TResult Function()? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Authenticated value) authenticated, + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_Initial value) initial, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Authenticated value)? authenticated, + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_Initial value)? initial, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Authenticated value)? authenticated, + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements AuthStatus { + const factory _Initial() = _$InitialImpl; +} diff --git a/lib/application/auth/auth_event.dart b/lib/application/auth/auth_event.dart new file mode 100644 index 0000000..6c3c293 --- /dev/null +++ b/lib/application/auth/auth_event.dart @@ -0,0 +1,6 @@ +part of 'auth_bloc.dart'; + +@freezed +class AuthEvent with _$AuthEvent { + const factory AuthEvent.fetchCurrentUser() = _FetchCurrentUser; +} diff --git a/lib/application/auth/auth_state.dart b/lib/application/auth/auth_state.dart new file mode 100644 index 0000000..483740d --- /dev/null +++ b/lib/application/auth/auth_state.dart @@ -0,0 +1,26 @@ +part of 'auth_bloc.dart'; + +@freezed +class AuthState with _$AuthState { + const AuthState._(); + + const factory AuthState({ + required User user, + @Default(AuthStatus.initial()) AuthStatus status, + required Option failureOption, + @Default(false) bool isFetching, + }) = _AuthState; + + factory AuthState.initial() => + AuthState(user: User.empty(), failureOption: none()); + + bool get isAuthenticated => status == const AuthStatus.authenticated(); + bool get isInitial => status == const AuthStatus.initial(); +} + +@freezed +sealed class AuthStatus with _$AuthStatus { + const factory AuthStatus.authenticated() = _Authenticated; + const factory AuthStatus.unauthenticated() = _Unauthenticated; + const factory AuthStatus.initial() = _Initial; +} diff --git a/lib/application/auth/check_phone_form/check_phone_form_bloc.dart b/lib/application/auth/check_phone_form/check_phone_form_bloc.dart new file mode 100644 index 0000000..e559b46 --- /dev/null +++ b/lib/application/auth/check_phone_form/check_phone_form_bloc.dart @@ -0,0 +1,57 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/function/app_function.dart'; +import '../../../domain/auth/auth.dart'; + +part 'check_phone_form_event.dart'; +part 'check_phone_form_state.dart'; +part 'check_phone_form_bloc.freezed.dart'; + +@injectable +class CheckPhoneFormBloc + extends Bloc { + final IAuthRepository _repository; + CheckPhoneFormBloc(this._repository) : super(CheckPhoneFormState.initial()) { + on(_onCheckPhoneFormEvent); + } + + Future _onCheckPhoneFormEvent( + CheckPhoneFormEvent event, + Emitter emit, + ) { + return event.map( + phoneNumberChanged: (e) async { + emit( + state.copyWith( + phoneNumber: e.phoneNumber, + failureOrCheckPhoneOption: none(), + ), + ); + }, + submitted: (e) async { + Either? failureOrCheckPhone; + emit( + state.copyWith(isSubmitting: true, failureOrCheckPhoneOption: none()), + ); + + final phoneNumberValid = state.phoneNumber.isNotEmpty; + + if (phoneNumberValid) { + failureOrCheckPhone = await _repository.checkPhone( + phoneNumber: getNormalizePhone(state.phoneNumber), + ); + emit( + state.copyWith( + isSubmitting: false, + failureOrCheckPhoneOption: optionOf(failureOrCheckPhone), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/check_phone_form/check_phone_form_bloc.freezed.dart b/lib/application/auth/check_phone_form/check_phone_form_bloc.freezed.dart new file mode 100644 index 0000000..4a86bec --- /dev/null +++ b/lib/application/auth/check_phone_form/check_phone_form_bloc.freezed.dart @@ -0,0 +1,552 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'check_phone_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$CheckPhoneFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CheckPhoneFormEventCopyWith<$Res> { + factory $CheckPhoneFormEventCopyWith( + CheckPhoneFormEvent value, + $Res Function(CheckPhoneFormEvent) then, + ) = _$CheckPhoneFormEventCopyWithImpl<$Res, CheckPhoneFormEvent>; +} + +/// @nodoc +class _$CheckPhoneFormEventCopyWithImpl<$Res, $Val extends CheckPhoneFormEvent> + implements $CheckPhoneFormEventCopyWith<$Res> { + _$CheckPhoneFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CheckPhoneFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$PhoneNumberChangedImplCopyWith<$Res> { + factory _$$PhoneNumberChangedImplCopyWith( + _$PhoneNumberChangedImpl value, + $Res Function(_$PhoneNumberChangedImpl) then, + ) = __$$PhoneNumberChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String phoneNumber}); +} + +/// @nodoc +class __$$PhoneNumberChangedImplCopyWithImpl<$Res> + extends _$CheckPhoneFormEventCopyWithImpl<$Res, _$PhoneNumberChangedImpl> + implements _$$PhoneNumberChangedImplCopyWith<$Res> { + __$$PhoneNumberChangedImplCopyWithImpl( + _$PhoneNumberChangedImpl _value, + $Res Function(_$PhoneNumberChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhoneFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = null}) { + return _then( + _$PhoneNumberChangedImpl( + null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PhoneNumberChangedImpl implements _PhoneNumberChanged { + const _$PhoneNumberChangedImpl(this.phoneNumber); + + @override + final String phoneNumber; + + @override + String toString() { + return 'CheckPhoneFormEvent.phoneNumberChanged(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PhoneNumberChangedImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + /// Create a copy of CheckPhoneFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + __$$PhoneNumberChangedImplCopyWithImpl<_$PhoneNumberChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitted, + }) { + return phoneNumberChanged(phoneNumber); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitted, + }) { + return phoneNumberChanged?.call(phoneNumber); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(phoneNumber); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_Submitted value) submitted, + }) { + return phoneNumberChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return phoneNumberChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberChanged implements CheckPhoneFormEvent { + const factory _PhoneNumberChanged(final String phoneNumber) = + _$PhoneNumberChangedImpl; + + String get phoneNumber; + + /// Create a copy of CheckPhoneFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$CheckPhoneFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhoneFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'CheckPhoneFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements CheckPhoneFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$CheckPhoneFormState { + String get phoneNumber => throw _privateConstructorUsedError; + Option> get failureOrCheckPhoneOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of CheckPhoneFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CheckPhoneFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CheckPhoneFormStateCopyWith<$Res> { + factory $CheckPhoneFormStateCopyWith( + CheckPhoneFormState value, + $Res Function(CheckPhoneFormState) then, + ) = _$CheckPhoneFormStateCopyWithImpl<$Res, CheckPhoneFormState>; + @useResult + $Res call({ + String phoneNumber, + Option> failureOrCheckPhoneOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$CheckPhoneFormStateCopyWithImpl<$Res, $Val extends CheckPhoneFormState> + implements $CheckPhoneFormStateCopyWith<$Res> { + _$CheckPhoneFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CheckPhoneFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? failureOrCheckPhoneOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + failureOrCheckPhoneOption: null == failureOrCheckPhoneOption + ? _value.failureOrCheckPhoneOption + : failureOrCheckPhoneOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CheckPhoneFormStateImplCopyWith<$Res> + implements $CheckPhoneFormStateCopyWith<$Res> { + factory _$$CheckPhoneFormStateImplCopyWith( + _$CheckPhoneFormStateImpl value, + $Res Function(_$CheckPhoneFormStateImpl) then, + ) = __$$CheckPhoneFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String phoneNumber, + Option> failureOrCheckPhoneOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$CheckPhoneFormStateImplCopyWithImpl<$Res> + extends _$CheckPhoneFormStateCopyWithImpl<$Res, _$CheckPhoneFormStateImpl> + implements _$$CheckPhoneFormStateImplCopyWith<$Res> { + __$$CheckPhoneFormStateImplCopyWithImpl( + _$CheckPhoneFormStateImpl _value, + $Res Function(_$CheckPhoneFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhoneFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? failureOrCheckPhoneOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$CheckPhoneFormStateImpl( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + failureOrCheckPhoneOption: null == failureOrCheckPhoneOption + ? _value.failureOrCheckPhoneOption + : failureOrCheckPhoneOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$CheckPhoneFormStateImpl implements _CheckPhoneFormState { + const _$CheckPhoneFormStateImpl({ + required this.phoneNumber, + required this.failureOrCheckPhoneOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String phoneNumber; + @override + final Option> failureOrCheckPhoneOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'CheckPhoneFormState(phoneNumber: $phoneNumber, failureOrCheckPhoneOption: $failureOrCheckPhoneOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CheckPhoneFormStateImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical( + other.failureOrCheckPhoneOption, + failureOrCheckPhoneOption, + ) || + other.failureOrCheckPhoneOption == failureOrCheckPhoneOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + phoneNumber, + failureOrCheckPhoneOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of CheckPhoneFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CheckPhoneFormStateImplCopyWith<_$CheckPhoneFormStateImpl> get copyWith => + __$$CheckPhoneFormStateImplCopyWithImpl<_$CheckPhoneFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _CheckPhoneFormState implements CheckPhoneFormState { + const factory _CheckPhoneFormState({ + required final String phoneNumber, + required final Option> + failureOrCheckPhoneOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$CheckPhoneFormStateImpl; + + @override + String get phoneNumber; + @override + Option> get failureOrCheckPhoneOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of CheckPhoneFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CheckPhoneFormStateImplCopyWith<_$CheckPhoneFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/check_phone_form/check_phone_form_event.dart b/lib/application/auth/check_phone_form/check_phone_form_event.dart new file mode 100644 index 0000000..5c5aaff --- /dev/null +++ b/lib/application/auth/check_phone_form/check_phone_form_event.dart @@ -0,0 +1,8 @@ +part of 'check_phone_form_bloc.dart'; + +@freezed +class CheckPhoneFormEvent with _$CheckPhoneFormEvent { + const factory CheckPhoneFormEvent.phoneNumberChanged(String phoneNumber) = + _PhoneNumberChanged; + const factory CheckPhoneFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/check_phone_form/check_phone_form_state.dart b/lib/application/auth/check_phone_form/check_phone_form_state.dart new file mode 100644 index 0000000..1ff0d4b --- /dev/null +++ b/lib/application/auth/check_phone_form/check_phone_form_state.dart @@ -0,0 +1,14 @@ +part of 'check_phone_form_bloc.dart'; + +@freezed +class CheckPhoneFormState with _$CheckPhoneFormState { + const factory CheckPhoneFormState({ + required String phoneNumber, + required Option> failureOrCheckPhoneOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _CheckPhoneFormState; + + factory CheckPhoneFormState.initial() => + CheckPhoneFormState(phoneNumber: '', failureOrCheckPhoneOption: none()); +} diff --git a/lib/application/auth/login_form/login_form_bloc.dart b/lib/application/auth/login_form/login_form_bloc.dart new file mode 100644 index 0000000..47d7a75 --- /dev/null +++ b/lib/application/auth/login_form/login_form_bloc.dart @@ -0,0 +1,67 @@ +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'login_form_event.dart'; +part 'login_form_state.dart'; +part 'login_form_bloc.freezed.dart'; + +@injectable +class LoginFormBloc extends Bloc { + final IAuthRepository _authRepository; + LoginFormBloc(this._authRepository) : super(LoginFormState.initial()) { + on(_onLoginFormEvent); + } + + Future _onLoginFormEvent( + LoginFormEvent event, + Emitter emit, + ) { + return event.map( + phoneNumberChanged: (e) async { + emit( + state.copyWith( + phoneNumber: e.phoneNumber, + failureOrLoginOption: none(), + ), + ); + }, + passwordChanged: (e) async { + emit( + state.copyWith(password: e.password, failureOrLoginOption: none()), + ); + }, + submitted: (e) async { + Either? failureOrLogin; + emit(state.copyWith(isSubmitting: true, failureOrLoginOption: none())); + + final phoneNumberValid = state.phoneNumber.isNotEmpty; + final passwordValid = state.password.isNotEmpty; + + log( + 'phoneNumberValid: $phoneNumberValid, passwordValid: $passwordValid, phoneNumber: ${state.phoneNumber}, password: ${state.password}', + ); + + if (phoneNumberValid && passwordValid) { + failureOrLogin = await _authRepository.login( + phoneNumber: state.phoneNumber, + password: state.password, + ); + + emit( + state.copyWith( + isSubmitting: false, + failureOrLoginOption: optionOf(failureOrLogin), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/login_form/login_form_bloc.freezed.dart b/lib/application/auth/login_form/login_form_bloc.freezed.dart new file mode 100644 index 0000000..8387862 --- /dev/null +++ b/lib/application/auth/login_form/login_form_bloc.freezed.dart @@ -0,0 +1,740 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$LoginFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String password) passwordChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String password)? passwordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginFormEventCopyWith<$Res> { + factory $LoginFormEventCopyWith( + LoginFormEvent value, + $Res Function(LoginFormEvent) then, + ) = _$LoginFormEventCopyWithImpl<$Res, LoginFormEvent>; +} + +/// @nodoc +class _$LoginFormEventCopyWithImpl<$Res, $Val extends LoginFormEvent> + implements $LoginFormEventCopyWith<$Res> { + _$LoginFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$PhoneNumberChangedImplCopyWith<$Res> { + factory _$$PhoneNumberChangedImplCopyWith( + _$PhoneNumberChangedImpl value, + $Res Function(_$PhoneNumberChangedImpl) then, + ) = __$$PhoneNumberChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String phoneNumber}); +} + +/// @nodoc +class __$$PhoneNumberChangedImplCopyWithImpl<$Res> + extends _$LoginFormEventCopyWithImpl<$Res, _$PhoneNumberChangedImpl> + implements _$$PhoneNumberChangedImplCopyWith<$Res> { + __$$PhoneNumberChangedImplCopyWithImpl( + _$PhoneNumberChangedImpl _value, + $Res Function(_$PhoneNumberChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = null}) { + return _then( + _$PhoneNumberChangedImpl( + null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PhoneNumberChangedImpl implements _PhoneNumberChanged { + const _$PhoneNumberChangedImpl(this.phoneNumber); + + @override + final String phoneNumber; + + @override + String toString() { + return 'LoginFormEvent.phoneNumberChanged(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PhoneNumberChangedImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + __$$PhoneNumberChangedImplCopyWithImpl<_$PhoneNumberChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String password) passwordChanged, + required TResult Function() submitted, + }) { + return phoneNumberChanged(phoneNumber); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function()? submitted, + }) { + return phoneNumberChanged?.call(phoneNumber); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String password)? passwordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(phoneNumber); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return phoneNumberChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return phoneNumberChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberChanged implements LoginFormEvent { + const factory _PhoneNumberChanged(final String phoneNumber) = + _$PhoneNumberChangedImpl; + + String get phoneNumber; + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PasswordChangedImplCopyWith<$Res> { + factory _$$PasswordChangedImplCopyWith( + _$PasswordChangedImpl value, + $Res Function(_$PasswordChangedImpl) then, + ) = __$$PasswordChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String password}); +} + +/// @nodoc +class __$$PasswordChangedImplCopyWithImpl<$Res> + extends _$LoginFormEventCopyWithImpl<$Res, _$PasswordChangedImpl> + implements _$$PasswordChangedImplCopyWith<$Res> { + __$$PasswordChangedImplCopyWithImpl( + _$PasswordChangedImpl _value, + $Res Function(_$PasswordChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? password = null}) { + return _then( + _$PasswordChangedImpl( + null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PasswordChangedImpl implements _PasswordChanged { + const _$PasswordChangedImpl(this.password); + + @override + final String password; + + @override + String toString() { + return 'LoginFormEvent.passwordChanged(password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PasswordChangedImpl && + (identical(other.password, password) || + other.password == password)); + } + + @override + int get hashCode => Object.hash(runtimeType, password); + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => + __$$PasswordChangedImplCopyWithImpl<_$PasswordChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String password) passwordChanged, + required TResult Function() submitted, + }) { + return passwordChanged(password); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function()? submitted, + }) { + return passwordChanged?.call(password); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String password)? passwordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(password); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return passwordChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return passwordChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(this); + } + return orElse(); + } +} + +abstract class _PasswordChanged implements LoginFormEvent { + const factory _PasswordChanged(final String password) = _$PasswordChangedImpl; + + String get password; + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$LoginFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'LoginFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String password) passwordChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String password)? passwordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements LoginFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$LoginFormState { + String get phoneNumber => throw _privateConstructorUsedError; + String get password => throw _privateConstructorUsedError; + Option> get failureOrLoginOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of LoginFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginFormStateCopyWith<$Res> { + factory $LoginFormStateCopyWith( + LoginFormState value, + $Res Function(LoginFormState) then, + ) = _$LoginFormStateCopyWithImpl<$Res, LoginFormState>; + @useResult + $Res call({ + String phoneNumber, + String password, + Option> failureOrLoginOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$LoginFormStateCopyWithImpl<$Res, $Val extends LoginFormState> + implements $LoginFormStateCopyWith<$Res> { + _$LoginFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? password = null, + Object? failureOrLoginOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + failureOrLoginOption: null == failureOrLoginOption + ? _value.failureOrLoginOption + : failureOrLoginOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LoginFormStateImplCopyWith<$Res> + implements $LoginFormStateCopyWith<$Res> { + factory _$$LoginFormStateImplCopyWith( + _$LoginFormStateImpl value, + $Res Function(_$LoginFormStateImpl) then, + ) = __$$LoginFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String phoneNumber, + String password, + Option> failureOrLoginOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$LoginFormStateImplCopyWithImpl<$Res> + extends _$LoginFormStateCopyWithImpl<$Res, _$LoginFormStateImpl> + implements _$$LoginFormStateImplCopyWith<$Res> { + __$$LoginFormStateImplCopyWithImpl( + _$LoginFormStateImpl _value, + $Res Function(_$LoginFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? password = null, + Object? failureOrLoginOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$LoginFormStateImpl( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + failureOrLoginOption: null == failureOrLoginOption + ? _value.failureOrLoginOption + : failureOrLoginOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$LoginFormStateImpl implements _LoginFormState { + const _$LoginFormStateImpl({ + required this.phoneNumber, + required this.password, + required this.failureOrLoginOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String phoneNumber; + @override + final String password; + @override + final Option> failureOrLoginOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'LoginFormState(phoneNumber: $phoneNumber, password: $password, failureOrLoginOption: $failureOrLoginOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginFormStateImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.password, password) || + other.password == password) && + (identical(other.failureOrLoginOption, failureOrLoginOption) || + other.failureOrLoginOption == failureOrLoginOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + phoneNumber, + password, + failureOrLoginOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of LoginFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginFormStateImplCopyWith<_$LoginFormStateImpl> get copyWith => + __$$LoginFormStateImplCopyWithImpl<_$LoginFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _LoginFormState implements LoginFormState { + const factory _LoginFormState({ + required final String phoneNumber, + required final String password, + required final Option> failureOrLoginOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$LoginFormStateImpl; + + @override + String get phoneNumber; + @override + String get password; + @override + Option> get failureOrLoginOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of LoginFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginFormStateImplCopyWith<_$LoginFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/login_form/login_form_event.dart b/lib/application/auth/login_form/login_form_event.dart new file mode 100644 index 0000000..ca70b88 --- /dev/null +++ b/lib/application/auth/login_form/login_form_event.dart @@ -0,0 +1,10 @@ +part of 'login_form_bloc.dart'; + +@freezed +class LoginFormEvent with _$LoginFormEvent { + const factory LoginFormEvent.phoneNumberChanged(String phoneNumber) = + _PhoneNumberChanged; + const factory LoginFormEvent.passwordChanged(String password) = + _PasswordChanged; + const factory LoginFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/login_form/login_form_state.dart b/lib/application/auth/login_form/login_form_state.dart new file mode 100644 index 0000000..3f51301 --- /dev/null +++ b/lib/application/auth/login_form/login_form_state.dart @@ -0,0 +1,18 @@ +part of 'login_form_bloc.dart'; + +@freezed +class LoginFormState with _$LoginFormState { + const factory LoginFormState({ + required String phoneNumber, + required String password, + required Option> failureOrLoginOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _LoginFormState; + + factory LoginFormState.initial() => LoginFormState( + phoneNumber: '', + password: '', + failureOrLoginOption: none(), + ); +} diff --git a/lib/application/auth/logout_form/logout_form_bloc.dart b/lib/application/auth/logout_form/logout_form_bloc.dart new file mode 100644 index 0000000..e07ca2f --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_bloc.dart @@ -0,0 +1,37 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'logout_form_event.dart'; +part 'logout_form_state.dart'; +part 'logout_form_bloc.freezed.dart'; + +@injectable +class LogoutFormBloc extends Bloc { + final IAuthRepository _repository; + + LogoutFormBloc(this._repository) : super(LogoutFormState.initial()) { + on(_onLogoutFormEvent); + } + + Future _onLogoutFormEvent( + LogoutFormEvent event, + Emitter emit, + ) { + return event.map( + submitted: (e) async { + emit(state.copyWith(isSubmitting: true, failureOrAuthOption: none())); + final failureOrAuth = await _repository.logout(); + emit( + state.copyWith( + isSubmitting: false, + failureOrAuthOption: optionOf(failureOrAuth), + ), + ); + }, + ); + } +} diff --git a/lib/application/auth/logout_form/logout_form_bloc.freezed.dart b/lib/application/auth/logout_form/logout_form_bloc.freezed.dart new file mode 100644 index 0000000..2bdad02 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_bloc.freezed.dart @@ -0,0 +1,335 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'logout_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$LogoutFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LogoutFormEventCopyWith<$Res> { + factory $LogoutFormEventCopyWith( + LogoutFormEvent value, + $Res Function(LogoutFormEvent) then, + ) = _$LogoutFormEventCopyWithImpl<$Res, LogoutFormEvent>; +} + +/// @nodoc +class _$LogoutFormEventCopyWithImpl<$Res, $Val extends LogoutFormEvent> + implements $LogoutFormEventCopyWith<$Res> { + _$LogoutFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LogoutFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$LogoutFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LogoutFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'LogoutFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements LogoutFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$LogoutFormState { + Option> get failureOrAuthOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LogoutFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LogoutFormStateCopyWith<$Res> { + factory $LogoutFormStateCopyWith( + LogoutFormState value, + $Res Function(LogoutFormState) then, + ) = _$LogoutFormStateCopyWithImpl<$Res, LogoutFormState>; + @useResult + $Res call({ + Option> failureOrAuthOption, + bool isSubmitting, + }); +} + +/// @nodoc +class _$LogoutFormStateCopyWithImpl<$Res, $Val extends LogoutFormState> + implements $LogoutFormStateCopyWith<$Res> { + _$LogoutFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) { + return _then( + _value.copyWith( + failureOrAuthOption: null == failureOrAuthOption + ? _value.failureOrAuthOption + : failureOrAuthOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LogoutFormStateImplCopyWith<$Res> + implements $LogoutFormStateCopyWith<$Res> { + factory _$$LogoutFormStateImplCopyWith( + _$LogoutFormStateImpl value, + $Res Function(_$LogoutFormStateImpl) then, + ) = __$$LogoutFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + Option> failureOrAuthOption, + bool isSubmitting, + }); +} + +/// @nodoc +class __$$LogoutFormStateImplCopyWithImpl<$Res> + extends _$LogoutFormStateCopyWithImpl<$Res, _$LogoutFormStateImpl> + implements _$$LogoutFormStateImplCopyWith<$Res> { + __$$LogoutFormStateImplCopyWithImpl( + _$LogoutFormStateImpl _value, + $Res Function(_$LogoutFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) { + return _then( + _$LogoutFormStateImpl( + failureOrAuthOption: null == failureOrAuthOption + ? _value.failureOrAuthOption + : failureOrAuthOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$LogoutFormStateImpl implements _LogoutFormState { + const _$LogoutFormStateImpl({ + required this.failureOrAuthOption, + this.isSubmitting = false, + }); + + @override + final Option> failureOrAuthOption; + @override + @JsonKey() + final bool isSubmitting; + + @override + String toString() { + return 'LogoutFormState(failureOrAuthOption: $failureOrAuthOption, isSubmitting: $isSubmitting)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LogoutFormStateImpl && + (identical(other.failureOrAuthOption, failureOrAuthOption) || + other.failureOrAuthOption == failureOrAuthOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting)); + } + + @override + int get hashCode => + Object.hash(runtimeType, failureOrAuthOption, isSubmitting); + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith => + __$$LogoutFormStateImplCopyWithImpl<_$LogoutFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _LogoutFormState implements LogoutFormState { + const factory _LogoutFormState({ + required final Option> failureOrAuthOption, + final bool isSubmitting, + }) = _$LogoutFormStateImpl; + + @override + Option> get failureOrAuthOption; + @override + bool get isSubmitting; + + /// Create a copy of LogoutFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/logout_form/logout_form_event.dart b/lib/application/auth/logout_form/logout_form_event.dart new file mode 100644 index 0000000..606bf91 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_event.dart @@ -0,0 +1,6 @@ +part of 'logout_form_bloc.dart'; + +@freezed +class LogoutFormEvent with _$LogoutFormEvent { + const factory LogoutFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/logout_form/logout_form_state.dart b/lib/application/auth/logout_form/logout_form_state.dart new file mode 100644 index 0000000..613c5e1 --- /dev/null +++ b/lib/application/auth/logout_form/logout_form_state.dart @@ -0,0 +1,12 @@ +part of 'logout_form_bloc.dart'; + +@freezed +class LogoutFormState with _$LogoutFormState { + const factory LogoutFormState({ + required Option> failureOrAuthOption, + @Default(false) bool isSubmitting, + }) = _LogoutFormState; + + factory LogoutFormState.initial() => + LogoutFormState(failureOrAuthOption: none(), isSubmitting: false); +} diff --git a/lib/application/auth/register_form/register_form_bloc.dart b/lib/application/auth/register_form/register_form_bloc.dart new file mode 100644 index 0000000..697a260 --- /dev/null +++ b/lib/application/auth/register_form/register_form_bloc.dart @@ -0,0 +1,69 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'register_form_event.dart'; +part 'register_form_state.dart'; +part 'register_form_bloc.freezed.dart'; + +@injectable +class RegisterFormBloc extends Bloc { + final IAuthRepository _repository; + RegisterFormBloc(this._repository) : super(RegisterFormState.initial()) { + on(_onRegisterFormEvent); + } + + Future _onRegisterFormEvent( + RegisterFormEvent event, + Emitter emit, + ) { + return event.map( + phoneNumberChanged: (e) async { + emit( + state.copyWith( + phoneNumber: e.phoneNumber, + failureOrRegisterOption: none(), + ), + ); + }, + nameChanged: (e) async { + emit(state.copyWith(name: e.name, failureOrRegisterOption: none())); + }, + birthDateChanged: (e) async { + emit( + state.copyWith( + birthDate: e.birthDate, + failureOrRegisterOption: none(), + ), + ); + }, + submitted: (e) async { + Either? failureOrRegister; + emit( + state.copyWith(isSubmitting: true, failureOrRegisterOption: none()), + ); + + final phoneNumberValid = state.phoneNumber.isNotEmpty; + final nameValid = state.name.isNotEmpty; + + if (phoneNumberValid && nameValid) { + failureOrRegister = await _repository.register( + phoneNumber: state.phoneNumber, + name: state.name, + birthDate: state.birthDate, + ); + emit( + state.copyWith( + isSubmitting: false, + failureOrRegisterOption: optionOf(failureOrRegister), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/register_form/register_form_bloc.freezed.dart b/lib/application/auth/register_form/register_form_bloc.freezed.dart new file mode 100644 index 0000000..cbd87c8 --- /dev/null +++ b/lib/application/auth/register_form/register_form_bloc.freezed.dart @@ -0,0 +1,944 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'register_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$RegisterFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String name) nameChanged, + required TResult Function(DateTime birthDate) birthDateChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(DateTime birthDate)? birthDateChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String name)? nameChanged, + TResult Function(DateTime birthDate)? birthDateChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_BirthDateChanged value) birthDateChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_BirthDateChanged value)? birthDateChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_BirthDateChanged value)? birthDateChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RegisterFormEventCopyWith<$Res> { + factory $RegisterFormEventCopyWith( + RegisterFormEvent value, + $Res Function(RegisterFormEvent) then, + ) = _$RegisterFormEventCopyWithImpl<$Res, RegisterFormEvent>; +} + +/// @nodoc +class _$RegisterFormEventCopyWithImpl<$Res, $Val extends RegisterFormEvent> + implements $RegisterFormEventCopyWith<$Res> { + _$RegisterFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$PhoneNumberChangedImplCopyWith<$Res> { + factory _$$PhoneNumberChangedImplCopyWith( + _$PhoneNumberChangedImpl value, + $Res Function(_$PhoneNumberChangedImpl) then, + ) = __$$PhoneNumberChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String phoneNumber}); +} + +/// @nodoc +class __$$PhoneNumberChangedImplCopyWithImpl<$Res> + extends _$RegisterFormEventCopyWithImpl<$Res, _$PhoneNumberChangedImpl> + implements _$$PhoneNumberChangedImplCopyWith<$Res> { + __$$PhoneNumberChangedImplCopyWithImpl( + _$PhoneNumberChangedImpl _value, + $Res Function(_$PhoneNumberChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = null}) { + return _then( + _$PhoneNumberChangedImpl( + null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PhoneNumberChangedImpl implements _PhoneNumberChanged { + const _$PhoneNumberChangedImpl(this.phoneNumber); + + @override + final String phoneNumber; + + @override + String toString() { + return 'RegisterFormEvent.phoneNumberChanged(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PhoneNumberChangedImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + __$$PhoneNumberChangedImplCopyWithImpl<_$PhoneNumberChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String name) nameChanged, + required TResult Function(DateTime birthDate) birthDateChanged, + required TResult Function() submitted, + }) { + return phoneNumberChanged(phoneNumber); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(DateTime birthDate)? birthDateChanged, + TResult? Function()? submitted, + }) { + return phoneNumberChanged?.call(phoneNumber); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String name)? nameChanged, + TResult Function(DateTime birthDate)? birthDateChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(phoneNumber); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_BirthDateChanged value) birthDateChanged, + required TResult Function(_Submitted value) submitted, + }) { + return phoneNumberChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_BirthDateChanged value)? birthDateChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return phoneNumberChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_BirthDateChanged value)? birthDateChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberChanged implements RegisterFormEvent { + const factory _PhoneNumberChanged(final String phoneNumber) = + _$PhoneNumberChangedImpl; + + String get phoneNumber; + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$NameChangedImplCopyWith<$Res> { + factory _$$NameChangedImplCopyWith( + _$NameChangedImpl value, + $Res Function(_$NameChangedImpl) then, + ) = __$$NameChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String name}); +} + +/// @nodoc +class __$$NameChangedImplCopyWithImpl<$Res> + extends _$RegisterFormEventCopyWithImpl<$Res, _$NameChangedImpl> + implements _$$NameChangedImplCopyWith<$Res> { + __$$NameChangedImplCopyWithImpl( + _$NameChangedImpl _value, + $Res Function(_$NameChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null}) { + return _then( + _$NameChangedImpl( + null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$NameChangedImpl implements _NameChanged { + const _$NameChangedImpl(this.name); + + @override + final String name; + + @override + String toString() { + return 'RegisterFormEvent.nameChanged(name: $name)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NameChangedImpl && + (identical(other.name, name) || other.name == name)); + } + + @override + int get hashCode => Object.hash(runtimeType, name); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NameChangedImplCopyWith<_$NameChangedImpl> get copyWith => + __$$NameChangedImplCopyWithImpl<_$NameChangedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String name) nameChanged, + required TResult Function(DateTime birthDate) birthDateChanged, + required TResult Function() submitted, + }) { + return nameChanged(name); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(DateTime birthDate)? birthDateChanged, + TResult? Function()? submitted, + }) { + return nameChanged?.call(name); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String name)? nameChanged, + TResult Function(DateTime birthDate)? birthDateChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (nameChanged != null) { + return nameChanged(name); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_BirthDateChanged value) birthDateChanged, + required TResult Function(_Submitted value) submitted, + }) { + return nameChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_BirthDateChanged value)? birthDateChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return nameChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_BirthDateChanged value)? birthDateChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (nameChanged != null) { + return nameChanged(this); + } + return orElse(); + } +} + +abstract class _NameChanged implements RegisterFormEvent { + const factory _NameChanged(final String name) = _$NameChangedImpl; + + String get name; + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NameChangedImplCopyWith<_$NameChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$BirthDateChangedImplCopyWith<$Res> { + factory _$$BirthDateChangedImplCopyWith( + _$BirthDateChangedImpl value, + $Res Function(_$BirthDateChangedImpl) then, + ) = __$$BirthDateChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({DateTime birthDate}); +} + +/// @nodoc +class __$$BirthDateChangedImplCopyWithImpl<$Res> + extends _$RegisterFormEventCopyWithImpl<$Res, _$BirthDateChangedImpl> + implements _$$BirthDateChangedImplCopyWith<$Res> { + __$$BirthDateChangedImplCopyWithImpl( + _$BirthDateChangedImpl _value, + $Res Function(_$BirthDateChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? birthDate = null}) { + return _then( + _$BirthDateChangedImpl( + null == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc + +class _$BirthDateChangedImpl implements _BirthDateChanged { + const _$BirthDateChangedImpl(this.birthDate); + + @override + final DateTime birthDate; + + @override + String toString() { + return 'RegisterFormEvent.birthDateChanged(birthDate: $birthDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BirthDateChangedImpl && + (identical(other.birthDate, birthDate) || + other.birthDate == birthDate)); + } + + @override + int get hashCode => Object.hash(runtimeType, birthDate); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BirthDateChangedImplCopyWith<_$BirthDateChangedImpl> get copyWith => + __$$BirthDateChangedImplCopyWithImpl<_$BirthDateChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String name) nameChanged, + required TResult Function(DateTime birthDate) birthDateChanged, + required TResult Function() submitted, + }) { + return birthDateChanged(birthDate); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(DateTime birthDate)? birthDateChanged, + TResult? Function()? submitted, + }) { + return birthDateChanged?.call(birthDate); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String name)? nameChanged, + TResult Function(DateTime birthDate)? birthDateChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (birthDateChanged != null) { + return birthDateChanged(birthDate); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_BirthDateChanged value) birthDateChanged, + required TResult Function(_Submitted value) submitted, + }) { + return birthDateChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_BirthDateChanged value)? birthDateChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return birthDateChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_BirthDateChanged value)? birthDateChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (birthDateChanged != null) { + return birthDateChanged(this); + } + return orElse(); + } +} + +abstract class _BirthDateChanged implements RegisterFormEvent { + const factory _BirthDateChanged(final DateTime birthDate) = + _$BirthDateChangedImpl; + + DateTime get birthDate; + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BirthDateChangedImplCopyWith<_$BirthDateChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$RegisterFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'RegisterFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String name) nameChanged, + required TResult Function(DateTime birthDate) birthDateChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(DateTime birthDate)? birthDateChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String name)? nameChanged, + TResult Function(DateTime birthDate)? birthDateChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_BirthDateChanged value) birthDateChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_BirthDateChanged value)? birthDateChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_BirthDateChanged value)? birthDateChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements RegisterFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$RegisterFormState { + String get phoneNumber => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + DateTime get birthDate => throw _privateConstructorUsedError; + Option> get failureOrRegisterOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of RegisterFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RegisterFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RegisterFormStateCopyWith<$Res> { + factory $RegisterFormStateCopyWith( + RegisterFormState value, + $Res Function(RegisterFormState) then, + ) = _$RegisterFormStateCopyWithImpl<$Res, RegisterFormState>; + @useResult + $Res call({ + String phoneNumber, + String name, + DateTime birthDate, + Option> failureOrRegisterOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$RegisterFormStateCopyWithImpl<$Res, $Val extends RegisterFormState> + implements $RegisterFormStateCopyWith<$Res> { + _$RegisterFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RegisterFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? name = null, + Object? birthDate = null, + Object? failureOrRegisterOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + birthDate: null == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as DateTime, + failureOrRegisterOption: null == failureOrRegisterOption + ? _value.failureOrRegisterOption + : failureOrRegisterOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$RegisterFormStateImplCopyWith<$Res> + implements $RegisterFormStateCopyWith<$Res> { + factory _$$RegisterFormStateImplCopyWith( + _$RegisterFormStateImpl value, + $Res Function(_$RegisterFormStateImpl) then, + ) = __$$RegisterFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String phoneNumber, + String name, + DateTime birthDate, + Option> failureOrRegisterOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$RegisterFormStateImplCopyWithImpl<$Res> + extends _$RegisterFormStateCopyWithImpl<$Res, _$RegisterFormStateImpl> + implements _$$RegisterFormStateImplCopyWith<$Res> { + __$$RegisterFormStateImplCopyWithImpl( + _$RegisterFormStateImpl _value, + $Res Function(_$RegisterFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? name = null, + Object? birthDate = null, + Object? failureOrRegisterOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$RegisterFormStateImpl( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + birthDate: null == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as DateTime, + failureOrRegisterOption: null == failureOrRegisterOption + ? _value.failureOrRegisterOption + : failureOrRegisterOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$RegisterFormStateImpl implements _RegisterFormState { + const _$RegisterFormStateImpl({ + required this.phoneNumber, + required this.name, + required this.birthDate, + required this.failureOrRegisterOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String phoneNumber; + @override + final String name; + @override + final DateTime birthDate; + @override + final Option> failureOrRegisterOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'RegisterFormState(phoneNumber: $phoneNumber, name: $name, birthDate: $birthDate, failureOrRegisterOption: $failureOrRegisterOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegisterFormStateImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.name, name) || other.name == name) && + (identical(other.birthDate, birthDate) || + other.birthDate == birthDate) && + (identical( + other.failureOrRegisterOption, + failureOrRegisterOption, + ) || + other.failureOrRegisterOption == failureOrRegisterOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + phoneNumber, + name, + birthDate, + failureOrRegisterOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of RegisterFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegisterFormStateImplCopyWith<_$RegisterFormStateImpl> get copyWith => + __$$RegisterFormStateImplCopyWithImpl<_$RegisterFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _RegisterFormState implements RegisterFormState { + const factory _RegisterFormState({ + required final String phoneNumber, + required final String name, + required final DateTime birthDate, + required final Option> + failureOrRegisterOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$RegisterFormStateImpl; + + @override + String get phoneNumber; + @override + String get name; + @override + DateTime get birthDate; + @override + Option> get failureOrRegisterOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of RegisterFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegisterFormStateImplCopyWith<_$RegisterFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/register_form/register_form_event.dart b/lib/application/auth/register_form/register_form_event.dart new file mode 100644 index 0000000..b92677a --- /dev/null +++ b/lib/application/auth/register_form/register_form_event.dart @@ -0,0 +1,11 @@ +part of 'register_form_bloc.dart'; + +@freezed +class RegisterFormEvent with _$RegisterFormEvent { + const factory RegisterFormEvent.phoneNumberChanged(String phoneNumber) = + _PhoneNumberChanged; + const factory RegisterFormEvent.nameChanged(String name) = _NameChanged; + const factory RegisterFormEvent.birthDateChanged(DateTime birthDate) = + _BirthDateChanged; + const factory RegisterFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/register_form/register_form_state.dart b/lib/application/auth/register_form/register_form_state.dart new file mode 100644 index 0000000..4ed224b --- /dev/null +++ b/lib/application/auth/register_form/register_form_state.dart @@ -0,0 +1,20 @@ +part of 'register_form_bloc.dart'; + +@freezed +class RegisterFormState with _$RegisterFormState { + const factory RegisterFormState({ + required String phoneNumber, + required String name, + required DateTime birthDate, + required Option> failureOrRegisterOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _RegisterFormState; + + factory RegisterFormState.initial() => RegisterFormState( + phoneNumber: '', + failureOrRegisterOption: none(), + name: '', + birthDate: DateTime.now(), + ); +} diff --git a/lib/application/auth/resend_form/resend_form_bloc.dart b/lib/application/auth/resend_form/resend_form_bloc.dart new file mode 100644 index 0000000..1aecbdc --- /dev/null +++ b/lib/application/auth/resend_form/resend_form_bloc.dart @@ -0,0 +1,59 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'resend_form_event.dart'; +part 'resend_form_state.dart'; +part 'resend_form_bloc.freezed.dart'; + +@injectable +class ResendFormBloc extends Bloc { + final IAuthRepository _repository; + ResendFormBloc(this._repository) : super(ResendFormState.initial()) { + on(_onResendFormEvent); + } + + Future _onResendFormEvent( + ResendFormEvent event, + Emitter emit, + ) { + return event.map( + phoneNumberChanged: (e) async { + emit( + state.copyWith( + phoneNumber: e.phoneNumber, + failureOrResendOption: none(), + ), + ); + }, + purposeChanged: (e) async { + emit(state.copyWith(purpose: e.purpose, failureOrResendOption: none())); + }, + submitted: (e) async { + Either? failureOrResend; + emit(state.copyWith(isSubmitting: true, failureOrResendOption: none())); + + final phoneNumberValid = state.phoneNumber.isNotEmpty; + final purposeValid = state.purpose.isNotEmpty; + + if (phoneNumberValid && purposeValid) { + failureOrResend = await _repository.resend( + phoneNumber: state.phoneNumber, + purpose: state.purpose, + ); + + emit( + state.copyWith( + isSubmitting: false, + failureOrResendOption: optionOf(failureOrResend), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/resend_form/resend_form_bloc.freezed.dart b/lib/application/auth/resend_form/resend_form_bloc.freezed.dart new file mode 100644 index 0000000..65aefe4 --- /dev/null +++ b/lib/application/auth/resend_form/resend_form_bloc.freezed.dart @@ -0,0 +1,738 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'resend_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ResendFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String purpose) purposeChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String purpose)? purposeChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String purpose)? purposeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PurposeChanged value) purposeChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PurposeChanged value)? purposeChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PurposeChanged value)? purposeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResendFormEventCopyWith<$Res> { + factory $ResendFormEventCopyWith( + ResendFormEvent value, + $Res Function(ResendFormEvent) then, + ) = _$ResendFormEventCopyWithImpl<$Res, ResendFormEvent>; +} + +/// @nodoc +class _$ResendFormEventCopyWithImpl<$Res, $Val extends ResendFormEvent> + implements $ResendFormEventCopyWith<$Res> { + _$ResendFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$PhoneNumberChangedImplCopyWith<$Res> { + factory _$$PhoneNumberChangedImplCopyWith( + _$PhoneNumberChangedImpl value, + $Res Function(_$PhoneNumberChangedImpl) then, + ) = __$$PhoneNumberChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String phoneNumber}); +} + +/// @nodoc +class __$$PhoneNumberChangedImplCopyWithImpl<$Res> + extends _$ResendFormEventCopyWithImpl<$Res, _$PhoneNumberChangedImpl> + implements _$$PhoneNumberChangedImplCopyWith<$Res> { + __$$PhoneNumberChangedImplCopyWithImpl( + _$PhoneNumberChangedImpl _value, + $Res Function(_$PhoneNumberChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = null}) { + return _then( + _$PhoneNumberChangedImpl( + null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PhoneNumberChangedImpl implements _PhoneNumberChanged { + const _$PhoneNumberChangedImpl(this.phoneNumber); + + @override + final String phoneNumber; + + @override + String toString() { + return 'ResendFormEvent.phoneNumberChanged(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PhoneNumberChangedImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + __$$PhoneNumberChangedImplCopyWithImpl<_$PhoneNumberChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String purpose) purposeChanged, + required TResult Function() submitted, + }) { + return phoneNumberChanged(phoneNumber); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String purpose)? purposeChanged, + TResult? Function()? submitted, + }) { + return phoneNumberChanged?.call(phoneNumber); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String purpose)? purposeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(phoneNumber); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PurposeChanged value) purposeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return phoneNumberChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PurposeChanged value)? purposeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return phoneNumberChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PurposeChanged value)? purposeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (phoneNumberChanged != null) { + return phoneNumberChanged(this); + } + return orElse(); + } +} + +abstract class _PhoneNumberChanged implements ResendFormEvent { + const factory _PhoneNumberChanged(final String phoneNumber) = + _$PhoneNumberChangedImpl; + + String get phoneNumber; + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PhoneNumberChangedImplCopyWith<_$PhoneNumberChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PurposeChangedImplCopyWith<$Res> { + factory _$$PurposeChangedImplCopyWith( + _$PurposeChangedImpl value, + $Res Function(_$PurposeChangedImpl) then, + ) = __$$PurposeChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String purpose}); +} + +/// @nodoc +class __$$PurposeChangedImplCopyWithImpl<$Res> + extends _$ResendFormEventCopyWithImpl<$Res, _$PurposeChangedImpl> + implements _$$PurposeChangedImplCopyWith<$Res> { + __$$PurposeChangedImplCopyWithImpl( + _$PurposeChangedImpl _value, + $Res Function(_$PurposeChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? purpose = null}) { + return _then( + _$PurposeChangedImpl( + null == purpose + ? _value.purpose + : purpose // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PurposeChangedImpl implements _PurposeChanged { + const _$PurposeChangedImpl(this.purpose); + + @override + final String purpose; + + @override + String toString() { + return 'ResendFormEvent.purposeChanged(purpose: $purpose)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PurposeChangedImpl && + (identical(other.purpose, purpose) || other.purpose == purpose)); + } + + @override + int get hashCode => Object.hash(runtimeType, purpose); + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PurposeChangedImplCopyWith<_$PurposeChangedImpl> get copyWith => + __$$PurposeChangedImplCopyWithImpl<_$PurposeChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String purpose) purposeChanged, + required TResult Function() submitted, + }) { + return purposeChanged(purpose); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String purpose)? purposeChanged, + TResult? Function()? submitted, + }) { + return purposeChanged?.call(purpose); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String purpose)? purposeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (purposeChanged != null) { + return purposeChanged(purpose); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PurposeChanged value) purposeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return purposeChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PurposeChanged value)? purposeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return purposeChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PurposeChanged value)? purposeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (purposeChanged != null) { + return purposeChanged(this); + } + return orElse(); + } +} + +abstract class _PurposeChanged implements ResendFormEvent { + const factory _PurposeChanged(final String purpose) = _$PurposeChangedImpl; + + String get purpose; + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PurposeChangedImplCopyWith<_$PurposeChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$ResendFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'ResendFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String phoneNumber) phoneNumberChanged, + required TResult Function(String purpose) purposeChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String phoneNumber)? phoneNumberChanged, + TResult? Function(String purpose)? purposeChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String phoneNumber)? phoneNumberChanged, + TResult Function(String purpose)? purposeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_PhoneNumberChanged value) phoneNumberChanged, + required TResult Function(_PurposeChanged value) purposeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult? Function(_PurposeChanged value)? purposeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_PhoneNumberChanged value)? phoneNumberChanged, + TResult Function(_PurposeChanged value)? purposeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements ResendFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$ResendFormState { + String get phoneNumber => throw _privateConstructorUsedError; + String get purpose => throw _privateConstructorUsedError; + Option> get failureOrResendOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of ResendFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ResendFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResendFormStateCopyWith<$Res> { + factory $ResendFormStateCopyWith( + ResendFormState value, + $Res Function(ResendFormState) then, + ) = _$ResendFormStateCopyWithImpl<$Res, ResendFormState>; + @useResult + $Res call({ + String phoneNumber, + String purpose, + Option> failureOrResendOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$ResendFormStateCopyWithImpl<$Res, $Val extends ResendFormState> + implements $ResendFormStateCopyWith<$Res> { + _$ResendFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ResendFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? purpose = null, + Object? failureOrResendOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + purpose: null == purpose + ? _value.purpose + : purpose // ignore: cast_nullable_to_non_nullable + as String, + failureOrResendOption: null == failureOrResendOption + ? _value.failureOrResendOption + : failureOrResendOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ResendFormStateImplCopyWith<$Res> + implements $ResendFormStateCopyWith<$Res> { + factory _$$ResendFormStateImplCopyWith( + _$ResendFormStateImpl value, + $Res Function(_$ResendFormStateImpl) then, + ) = __$$ResendFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String phoneNumber, + String purpose, + Option> failureOrResendOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$ResendFormStateImplCopyWithImpl<$Res> + extends _$ResendFormStateCopyWithImpl<$Res, _$ResendFormStateImpl> + implements _$$ResendFormStateImplCopyWith<$Res> { + __$$ResendFormStateImplCopyWithImpl( + _$ResendFormStateImpl _value, + $Res Function(_$ResendFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? phoneNumber = null, + Object? purpose = null, + Object? failureOrResendOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$ResendFormStateImpl( + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + purpose: null == purpose + ? _value.purpose + : purpose // ignore: cast_nullable_to_non_nullable + as String, + failureOrResendOption: null == failureOrResendOption + ? _value.failureOrResendOption + : failureOrResendOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$ResendFormStateImpl implements _ResendFormState { + const _$ResendFormStateImpl({ + required this.phoneNumber, + required this.purpose, + required this.failureOrResendOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String phoneNumber; + @override + final String purpose; + @override + final Option> failureOrResendOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'ResendFormState(phoneNumber: $phoneNumber, purpose: $purpose, failureOrResendOption: $failureOrResendOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ResendFormStateImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.purpose, purpose) || other.purpose == purpose) && + (identical(other.failureOrResendOption, failureOrResendOption) || + other.failureOrResendOption == failureOrResendOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + phoneNumber, + purpose, + failureOrResendOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of ResendFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ResendFormStateImplCopyWith<_$ResendFormStateImpl> get copyWith => + __$$ResendFormStateImplCopyWithImpl<_$ResendFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _ResendFormState implements ResendFormState { + const factory _ResendFormState({ + required final String phoneNumber, + required final String purpose, + required final Option> failureOrResendOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$ResendFormStateImpl; + + @override + String get phoneNumber; + @override + String get purpose; + @override + Option> get failureOrResendOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of ResendFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ResendFormStateImplCopyWith<_$ResendFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/resend_form/resend_form_event.dart b/lib/application/auth/resend_form/resend_form_event.dart new file mode 100644 index 0000000..0d6267b --- /dev/null +++ b/lib/application/auth/resend_form/resend_form_event.dart @@ -0,0 +1,10 @@ +part of 'resend_form_bloc.dart'; + +@freezed +class ResendFormEvent with _$ResendFormEvent { + const factory ResendFormEvent.phoneNumberChanged(String phoneNumber) = + _PhoneNumberChanged; + const factory ResendFormEvent.purposeChanged(String purpose) = + _PurposeChanged; + const factory ResendFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/resend_form/resend_form_state.dart b/lib/application/auth/resend_form/resend_form_state.dart new file mode 100644 index 0000000..92fdfd5 --- /dev/null +++ b/lib/application/auth/resend_form/resend_form_state.dart @@ -0,0 +1,18 @@ +part of 'resend_form_bloc.dart'; + +@freezed +class ResendFormState with _$ResendFormState { + const factory ResendFormState({ + required String phoneNumber, + required String purpose, + required Option> failureOrResendOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _ResendFormState; + + factory ResendFormState.initial() => ResendFormState( + phoneNumber: '', + purpose: '', + failureOrResendOption: none(), + ); +} diff --git a/lib/application/auth/set_password/set_password_form_bloc.dart b/lib/application/auth/set_password/set_password_form_bloc.dart new file mode 100644 index 0000000..774113c --- /dev/null +++ b/lib/application/auth/set_password/set_password_form_bloc.dart @@ -0,0 +1,81 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'set_password_form_event.dart'; +part 'set_password_form_state.dart'; +part 'set_password_form_bloc.freezed.dart'; + +@injectable +class SetPasswordFormBloc + extends Bloc { + final IAuthRepository _repository; + SetPasswordFormBloc(this._repository) + : super(SetPasswordFormState.initial()) { + on(_onSetPasswordFormEvent); + } + + Future _onSetPasswordFormEvent( + SetPasswordFormEvent event, + Emitter emit, + ) { + return event.map( + registrationTokenChanged: (e) async { + emit( + state.copyWith( + registrationToken: e.registrationToken, + failureOrSetPasswordOption: none(), + ), + ); + }, + passwordChanged: (e) async { + emit( + state.copyWith( + password: e.password, + failureOrSetPasswordOption: none(), + ), + ); + }, + confirmPasswordChanged: (e) async { + emit( + state.copyWith( + confirmPassword: e.confirmPassword, + failureOrSetPasswordOption: none(), + ), + ); + }, + submitted: (e) async { + Either? failureOrSetPassword; + emit( + state.copyWith( + isSubmitting: true, + failureOrSetPasswordOption: none(), + ), + ); + + final registrationTokenValid = state.registrationToken.isNotEmpty; + final passwordValid = state.password.isNotEmpty; + final confirmPasswordValid = state.confirmPassword.isNotEmpty; + + if (registrationTokenValid && passwordValid && confirmPasswordValid) { + failureOrSetPassword = await _repository.setPassword( + registrationToken: state.registrationToken, + password: state.password, + confirmPassword: state.confirmPassword, + ); + + emit( + state.copyWith( + isSubmitting: false, + failureOrSetPasswordOption: optionOf(failureOrSetPassword), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/set_password/set_password_form_bloc.freezed.dart b/lib/application/auth/set_password/set_password_form_bloc.freezed.dart new file mode 100644 index 0000000..c83d9c6 --- /dev/null +++ b/lib/application/auth/set_password/set_password_form_bloc.freezed.dart @@ -0,0 +1,980 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'set_password_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$SetPasswordFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String password) passwordChanged, + required TResult Function(String confirmPassword) confirmPasswordChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function(String confirmPassword)? confirmPasswordChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String confirmPassword)? confirmPasswordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SetPasswordFormEventCopyWith<$Res> { + factory $SetPasswordFormEventCopyWith( + SetPasswordFormEvent value, + $Res Function(SetPasswordFormEvent) then, + ) = _$SetPasswordFormEventCopyWithImpl<$Res, SetPasswordFormEvent>; +} + +/// @nodoc +class _$SetPasswordFormEventCopyWithImpl< + $Res, + $Val extends SetPasswordFormEvent +> + implements $SetPasswordFormEventCopyWith<$Res> { + _$SetPasswordFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$RegistrationTokenChangedImplCopyWith<$Res> { + factory _$$RegistrationTokenChangedImplCopyWith( + _$RegistrationTokenChangedImpl value, + $Res Function(_$RegistrationTokenChangedImpl) then, + ) = __$$RegistrationTokenChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String registrationToken}); +} + +/// @nodoc +class __$$RegistrationTokenChangedImplCopyWithImpl<$Res> + extends + _$SetPasswordFormEventCopyWithImpl<$Res, _$RegistrationTokenChangedImpl> + implements _$$RegistrationTokenChangedImplCopyWith<$Res> { + __$$RegistrationTokenChangedImplCopyWithImpl( + _$RegistrationTokenChangedImpl _value, + $Res Function(_$RegistrationTokenChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? registrationToken = null}) { + return _then( + _$RegistrationTokenChangedImpl( + null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$RegistrationTokenChangedImpl implements _RegistrationTokenChanged { + const _$RegistrationTokenChangedImpl(this.registrationToken); + + @override + final String registrationToken; + + @override + String toString() { + return 'SetPasswordFormEvent.registrationTokenChanged(registrationToken: $registrationToken)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegistrationTokenChangedImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken)); + } + + @override + int get hashCode => Object.hash(runtimeType, registrationToken); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegistrationTokenChangedImplCopyWith<_$RegistrationTokenChangedImpl> + get copyWith => + __$$RegistrationTokenChangedImplCopyWithImpl< + _$RegistrationTokenChangedImpl + >(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String password) passwordChanged, + required TResult Function(String confirmPassword) confirmPasswordChanged, + required TResult Function() submitted, + }) { + return registrationTokenChanged(registrationToken); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function(String confirmPassword)? confirmPasswordChanged, + TResult? Function()? submitted, + }) { + return registrationTokenChanged?.call(registrationToken); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String confirmPassword)? confirmPasswordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (registrationTokenChanged != null) { + return registrationTokenChanged(registrationToken); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return registrationTokenChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return registrationTokenChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (registrationTokenChanged != null) { + return registrationTokenChanged(this); + } + return orElse(); + } +} + +abstract class _RegistrationTokenChanged implements SetPasswordFormEvent { + const factory _RegistrationTokenChanged(final String registrationToken) = + _$RegistrationTokenChangedImpl; + + String get registrationToken; + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegistrationTokenChangedImplCopyWith<_$RegistrationTokenChangedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PasswordChangedImplCopyWith<$Res> { + factory _$$PasswordChangedImplCopyWith( + _$PasswordChangedImpl value, + $Res Function(_$PasswordChangedImpl) then, + ) = __$$PasswordChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String password}); +} + +/// @nodoc +class __$$PasswordChangedImplCopyWithImpl<$Res> + extends _$SetPasswordFormEventCopyWithImpl<$Res, _$PasswordChangedImpl> + implements _$$PasswordChangedImplCopyWith<$Res> { + __$$PasswordChangedImplCopyWithImpl( + _$PasswordChangedImpl _value, + $Res Function(_$PasswordChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? password = null}) { + return _then( + _$PasswordChangedImpl( + null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PasswordChangedImpl implements _PasswordChanged { + const _$PasswordChangedImpl(this.password); + + @override + final String password; + + @override + String toString() { + return 'SetPasswordFormEvent.passwordChanged(password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PasswordChangedImpl && + (identical(other.password, password) || + other.password == password)); + } + + @override + int get hashCode => Object.hash(runtimeType, password); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => + __$$PasswordChangedImplCopyWithImpl<_$PasswordChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String password) passwordChanged, + required TResult Function(String confirmPassword) confirmPasswordChanged, + required TResult Function() submitted, + }) { + return passwordChanged(password); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function(String confirmPassword)? confirmPasswordChanged, + TResult? Function()? submitted, + }) { + return passwordChanged?.call(password); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String confirmPassword)? confirmPasswordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(password); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return passwordChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return passwordChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (passwordChanged != null) { + return passwordChanged(this); + } + return orElse(); + } +} + +abstract class _PasswordChanged implements SetPasswordFormEvent { + const factory _PasswordChanged(final String password) = _$PasswordChangedImpl; + + String get password; + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PasswordChangedImplCopyWith<_$PasswordChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ConfirmPasswordChangedImplCopyWith<$Res> { + factory _$$ConfirmPasswordChangedImplCopyWith( + _$ConfirmPasswordChangedImpl value, + $Res Function(_$ConfirmPasswordChangedImpl) then, + ) = __$$ConfirmPasswordChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String confirmPassword}); +} + +/// @nodoc +class __$$ConfirmPasswordChangedImplCopyWithImpl<$Res> + extends + _$SetPasswordFormEventCopyWithImpl<$Res, _$ConfirmPasswordChangedImpl> + implements _$$ConfirmPasswordChangedImplCopyWith<$Res> { + __$$ConfirmPasswordChangedImplCopyWithImpl( + _$ConfirmPasswordChangedImpl _value, + $Res Function(_$ConfirmPasswordChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? confirmPassword = null}) { + return _then( + _$ConfirmPasswordChangedImpl( + null == confirmPassword + ? _value.confirmPassword + : confirmPassword // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$ConfirmPasswordChangedImpl implements _ConfirmPasswordChanged { + const _$ConfirmPasswordChangedImpl(this.confirmPassword); + + @override + final String confirmPassword; + + @override + String toString() { + return 'SetPasswordFormEvent.confirmPasswordChanged(confirmPassword: $confirmPassword)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ConfirmPasswordChangedImpl && + (identical(other.confirmPassword, confirmPassword) || + other.confirmPassword == confirmPassword)); + } + + @override + int get hashCode => Object.hash(runtimeType, confirmPassword); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ConfirmPasswordChangedImplCopyWith<_$ConfirmPasswordChangedImpl> + get copyWith => + __$$ConfirmPasswordChangedImplCopyWithImpl<_$ConfirmPasswordChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String password) passwordChanged, + required TResult Function(String confirmPassword) confirmPasswordChanged, + required TResult Function() submitted, + }) { + return confirmPasswordChanged(confirmPassword); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function(String confirmPassword)? confirmPasswordChanged, + TResult? Function()? submitted, + }) { + return confirmPasswordChanged?.call(confirmPassword); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String confirmPassword)? confirmPasswordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (confirmPasswordChanged != null) { + return confirmPasswordChanged(confirmPassword); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return confirmPasswordChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return confirmPasswordChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (confirmPasswordChanged != null) { + return confirmPasswordChanged(this); + } + return orElse(); + } +} + +abstract class _ConfirmPasswordChanged implements SetPasswordFormEvent { + const factory _ConfirmPasswordChanged(final String confirmPassword) = + _$ConfirmPasswordChangedImpl; + + String get confirmPassword; + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ConfirmPasswordChangedImplCopyWith<_$ConfirmPasswordChangedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$SetPasswordFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SetPasswordFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'SetPasswordFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String password) passwordChanged, + required TResult Function(String confirmPassword) confirmPasswordChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String password)? passwordChanged, + TResult? Function(String confirmPassword)? confirmPasswordChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String confirmPassword)? confirmPasswordChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_PasswordChanged value) passwordChanged, + required TResult Function(_ConfirmPasswordChanged value) + confirmPasswordChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_PasswordChanged value)? passwordChanged, + TResult? Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_PasswordChanged value)? passwordChanged, + TResult Function(_ConfirmPasswordChanged value)? confirmPasswordChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements SetPasswordFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$SetPasswordFormState { + String get registrationToken => throw _privateConstructorUsedError; + String get password => throw _privateConstructorUsedError; + String get confirmPassword => throw _privateConstructorUsedError; + Option> get failureOrSetPasswordOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of SetPasswordFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SetPasswordFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SetPasswordFormStateCopyWith<$Res> { + factory $SetPasswordFormStateCopyWith( + SetPasswordFormState value, + $Res Function(SetPasswordFormState) then, + ) = _$SetPasswordFormStateCopyWithImpl<$Res, SetPasswordFormState>; + @useResult + $Res call({ + String registrationToken, + String password, + String confirmPassword, + Option> failureOrSetPasswordOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$SetPasswordFormStateCopyWithImpl< + $Res, + $Val extends SetPasswordFormState +> + implements $SetPasswordFormStateCopyWith<$Res> { + _$SetPasswordFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SetPasswordFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = null, + Object? password = null, + Object? confirmPassword = null, + Object? failureOrSetPasswordOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + confirmPassword: null == confirmPassword + ? _value.confirmPassword + : confirmPassword // ignore: cast_nullable_to_non_nullable + as String, + failureOrSetPasswordOption: null == failureOrSetPasswordOption + ? _value.failureOrSetPasswordOption + : failureOrSetPasswordOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$SetPasswordFormStateImplCopyWith<$Res> + implements $SetPasswordFormStateCopyWith<$Res> { + factory _$$SetPasswordFormStateImplCopyWith( + _$SetPasswordFormStateImpl value, + $Res Function(_$SetPasswordFormStateImpl) then, + ) = __$$SetPasswordFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String registrationToken, + String password, + String confirmPassword, + Option> failureOrSetPasswordOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$SetPasswordFormStateImplCopyWithImpl<$Res> + extends _$SetPasswordFormStateCopyWithImpl<$Res, _$SetPasswordFormStateImpl> + implements _$$SetPasswordFormStateImplCopyWith<$Res> { + __$$SetPasswordFormStateImplCopyWithImpl( + _$SetPasswordFormStateImpl _value, + $Res Function(_$SetPasswordFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of SetPasswordFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = null, + Object? password = null, + Object? confirmPassword = null, + Object? failureOrSetPasswordOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$SetPasswordFormStateImpl( + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + confirmPassword: null == confirmPassword + ? _value.confirmPassword + : confirmPassword // ignore: cast_nullable_to_non_nullable + as String, + failureOrSetPasswordOption: null == failureOrSetPasswordOption + ? _value.failureOrSetPasswordOption + : failureOrSetPasswordOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$SetPasswordFormStateImpl implements _SetPasswordFormState { + const _$SetPasswordFormStateImpl({ + required this.registrationToken, + required this.password, + required this.confirmPassword, + required this.failureOrSetPasswordOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String registrationToken; + @override + final String password; + @override + final String confirmPassword; + @override + final Option> failureOrSetPasswordOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'SetPasswordFormState(registrationToken: $registrationToken, password: $password, confirmPassword: $confirmPassword, failureOrSetPasswordOption: $failureOrSetPasswordOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SetPasswordFormStateImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken) && + (identical(other.password, password) || + other.password == password) && + (identical(other.confirmPassword, confirmPassword) || + other.confirmPassword == confirmPassword) && + (identical( + other.failureOrSetPasswordOption, + failureOrSetPasswordOption, + ) || + other.failureOrSetPasswordOption == + failureOrSetPasswordOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + registrationToken, + password, + confirmPassword, + failureOrSetPasswordOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of SetPasswordFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SetPasswordFormStateImplCopyWith<_$SetPasswordFormStateImpl> + get copyWith => + __$$SetPasswordFormStateImplCopyWithImpl<_$SetPasswordFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _SetPasswordFormState implements SetPasswordFormState { + const factory _SetPasswordFormState({ + required final String registrationToken, + required final String password, + required final String confirmPassword, + required final Option> + failureOrSetPasswordOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$SetPasswordFormStateImpl; + + @override + String get registrationToken; + @override + String get password; + @override + String get confirmPassword; + @override + Option> get failureOrSetPasswordOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of SetPasswordFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SetPasswordFormStateImplCopyWith<_$SetPasswordFormStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/set_password/set_password_form_event.dart b/lib/application/auth/set_password/set_password_form_event.dart new file mode 100644 index 0000000..32dc640 --- /dev/null +++ b/lib/application/auth/set_password/set_password_form_event.dart @@ -0,0 +1,14 @@ +part of 'set_password_form_bloc.dart'; + +@freezed +class SetPasswordFormEvent with _$SetPasswordFormEvent { + const factory SetPasswordFormEvent.registrationTokenChanged( + String registrationToken, + ) = _RegistrationTokenChanged; + const factory SetPasswordFormEvent.passwordChanged(String password) = + _PasswordChanged; + const factory SetPasswordFormEvent.confirmPasswordChanged( + String confirmPassword, + ) = _ConfirmPasswordChanged; + const factory SetPasswordFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/set_password/set_password_form_state.dart b/lib/application/auth/set_password/set_password_form_state.dart new file mode 100644 index 0000000..849755e --- /dev/null +++ b/lib/application/auth/set_password/set_password_form_state.dart @@ -0,0 +1,20 @@ +part of 'set_password_form_bloc.dart'; + +@freezed +class SetPasswordFormState with _$SetPasswordFormState { + const factory SetPasswordFormState({ + required String registrationToken, + required String password, + required String confirmPassword, + required Option> failureOrSetPasswordOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _SetPasswordFormState; + + factory SetPasswordFormState.initial() => SetPasswordFormState( + registrationToken: '', + password: '', + confirmPassword: '', + failureOrSetPasswordOption: none(), + ); +} diff --git a/lib/application/auth/verify_form/verify_form_bloc.dart b/lib/application/auth/verify_form/verify_form_bloc.dart new file mode 100644 index 0000000..1331681 --- /dev/null +++ b/lib/application/auth/verify_form/verify_form_bloc.dart @@ -0,0 +1,58 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; + +part 'verify_form_event.dart'; +part 'verify_form_state.dart'; +part 'verify_form_bloc.freezed.dart'; + +@injectable +class VerifyFormBloc extends Bloc { + final IAuthRepository _repository; + VerifyFormBloc(this._repository) : super(VerifyFormState.initial()) { + on(_onVerifyFormEvent); + } + + Future _onVerifyFormEvent( + VerifyFormEvent event, + Emitter emit, + ) { + return event.map( + registrationTokenChanged: (e) async { + emit( + state.copyWith( + registrationToken: e.registrationToken, + failureOrVerifyOption: none(), + ), + ); + }, + otpCodeChanged: (e) async { + emit(state.copyWith(otpCode: e.otpCode, failureOrVerifyOption: none())); + }, + submitted: (e) async { + Either? failureOrVerify; + emit(state.copyWith(isSubmitting: true, failureOrVerifyOption: none())); + + final otpCodeValid = state.otpCode.isNotEmpty; + final registrationTokenValid = state.registrationToken.isNotEmpty; + + if (registrationTokenValid && otpCodeValid) { + failureOrVerify = await _repository.verify( + registrationToken: state.registrationToken, + otpCode: state.otpCode, + ); + emit( + state.copyWith( + isSubmitting: false, + failureOrVerifyOption: optionOf(failureOrVerify), + ), + ); + } + emit(state.copyWith(showErrorMessages: true, isSubmitting: false)); + }, + ); + } +} diff --git a/lib/application/auth/verify_form/verify_form_bloc.freezed.dart b/lib/application/auth/verify_form/verify_form_bloc.freezed.dart new file mode 100644 index 0000000..89b0ddd --- /dev/null +++ b/lib/application/auth/verify_form/verify_form_bloc.freezed.dart @@ -0,0 +1,750 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'verify_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$VerifyFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String otpCode) otpCodeChanged, + required TResult Function() submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String otpCode)? otpCodeChanged, + TResult? Function()? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String otpCode)? otpCodeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_OtpCodeChanged value) otpCodeChanged, + required TResult Function(_Submitted value) submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_OtpCodeChanged value)? otpCodeChanged, + TResult? Function(_Submitted value)? submitted, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_OtpCodeChanged value)? otpCodeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyFormEventCopyWith<$Res> { + factory $VerifyFormEventCopyWith( + VerifyFormEvent value, + $Res Function(VerifyFormEvent) then, + ) = _$VerifyFormEventCopyWithImpl<$Res, VerifyFormEvent>; +} + +/// @nodoc +class _$VerifyFormEventCopyWithImpl<$Res, $Val extends VerifyFormEvent> + implements $VerifyFormEventCopyWith<$Res> { + _$VerifyFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$RegistrationTokenChangedImplCopyWith<$Res> { + factory _$$RegistrationTokenChangedImplCopyWith( + _$RegistrationTokenChangedImpl value, + $Res Function(_$RegistrationTokenChangedImpl) then, + ) = __$$RegistrationTokenChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String registrationToken}); +} + +/// @nodoc +class __$$RegistrationTokenChangedImplCopyWithImpl<$Res> + extends _$VerifyFormEventCopyWithImpl<$Res, _$RegistrationTokenChangedImpl> + implements _$$RegistrationTokenChangedImplCopyWith<$Res> { + __$$RegistrationTokenChangedImplCopyWithImpl( + _$RegistrationTokenChangedImpl _value, + $Res Function(_$RegistrationTokenChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? registrationToken = null}) { + return _then( + _$RegistrationTokenChangedImpl( + null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$RegistrationTokenChangedImpl implements _RegistrationTokenChanged { + const _$RegistrationTokenChangedImpl(this.registrationToken); + + @override + final String registrationToken; + + @override + String toString() { + return 'VerifyFormEvent.registrationTokenChanged(registrationToken: $registrationToken)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegistrationTokenChangedImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken)); + } + + @override + int get hashCode => Object.hash(runtimeType, registrationToken); + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegistrationTokenChangedImplCopyWith<_$RegistrationTokenChangedImpl> + get copyWith => + __$$RegistrationTokenChangedImplCopyWithImpl< + _$RegistrationTokenChangedImpl + >(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String otpCode) otpCodeChanged, + required TResult Function() submitted, + }) { + return registrationTokenChanged(registrationToken); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String otpCode)? otpCodeChanged, + TResult? Function()? submitted, + }) { + return registrationTokenChanged?.call(registrationToken); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String otpCode)? otpCodeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (registrationTokenChanged != null) { + return registrationTokenChanged(registrationToken); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_OtpCodeChanged value) otpCodeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return registrationTokenChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_OtpCodeChanged value)? otpCodeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return registrationTokenChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_OtpCodeChanged value)? otpCodeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (registrationTokenChanged != null) { + return registrationTokenChanged(this); + } + return orElse(); + } +} + +abstract class _RegistrationTokenChanged implements VerifyFormEvent { + const factory _RegistrationTokenChanged(final String registrationToken) = + _$RegistrationTokenChangedImpl; + + String get registrationToken; + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegistrationTokenChangedImplCopyWith<_$RegistrationTokenChangedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$OtpCodeChangedImplCopyWith<$Res> { + factory _$$OtpCodeChangedImplCopyWith( + _$OtpCodeChangedImpl value, + $Res Function(_$OtpCodeChangedImpl) then, + ) = __$$OtpCodeChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String otpCode}); +} + +/// @nodoc +class __$$OtpCodeChangedImplCopyWithImpl<$Res> + extends _$VerifyFormEventCopyWithImpl<$Res, _$OtpCodeChangedImpl> + implements _$$OtpCodeChangedImplCopyWith<$Res> { + __$$OtpCodeChangedImplCopyWithImpl( + _$OtpCodeChangedImpl _value, + $Res Function(_$OtpCodeChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? otpCode = null}) { + return _then( + _$OtpCodeChangedImpl( + null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$OtpCodeChangedImpl implements _OtpCodeChanged { + const _$OtpCodeChangedImpl(this.otpCode); + + @override + final String otpCode; + + @override + String toString() { + return 'VerifyFormEvent.otpCodeChanged(otpCode: $otpCode)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$OtpCodeChangedImpl && + (identical(other.otpCode, otpCode) || other.otpCode == otpCode)); + } + + @override + int get hashCode => Object.hash(runtimeType, otpCode); + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$OtpCodeChangedImplCopyWith<_$OtpCodeChangedImpl> get copyWith => + __$$OtpCodeChangedImplCopyWithImpl<_$OtpCodeChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String otpCode) otpCodeChanged, + required TResult Function() submitted, + }) { + return otpCodeChanged(otpCode); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String otpCode)? otpCodeChanged, + TResult? Function()? submitted, + }) { + return otpCodeChanged?.call(otpCode); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String otpCode)? otpCodeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (otpCodeChanged != null) { + return otpCodeChanged(otpCode); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_OtpCodeChanged value) otpCodeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return otpCodeChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_OtpCodeChanged value)? otpCodeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return otpCodeChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_OtpCodeChanged value)? otpCodeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (otpCodeChanged != null) { + return otpCodeChanged(this); + } + return orElse(); + } +} + +abstract class _OtpCodeChanged implements VerifyFormEvent { + const factory _OtpCodeChanged(final String otpCode) = _$OtpCodeChangedImpl; + + String get otpCode; + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$OtpCodeChangedImplCopyWith<_$OtpCodeChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SubmittedImplCopyWith<$Res> { + factory _$$SubmittedImplCopyWith( + _$SubmittedImpl value, + $Res Function(_$SubmittedImpl) then, + ) = __$$SubmittedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SubmittedImplCopyWithImpl<$Res> + extends _$VerifyFormEventCopyWithImpl<$Res, _$SubmittedImpl> + implements _$$SubmittedImplCopyWith<$Res> { + __$$SubmittedImplCopyWithImpl( + _$SubmittedImpl _value, + $Res Function(_$SubmittedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SubmittedImpl implements _Submitted { + const _$SubmittedImpl(); + + @override + String toString() { + return 'VerifyFormEvent.submitted()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SubmittedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String registrationToken) + registrationTokenChanged, + required TResult Function(String otpCode) otpCodeChanged, + required TResult Function() submitted, + }) { + return submitted(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String registrationToken)? registrationTokenChanged, + TResult? Function(String otpCode)? otpCodeChanged, + TResult? Function()? submitted, + }) { + return submitted?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String registrationToken)? registrationTokenChanged, + TResult Function(String otpCode)? otpCodeChanged, + TResult Function()? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RegistrationTokenChanged value) + registrationTokenChanged, + required TResult Function(_OtpCodeChanged value) otpCodeChanged, + required TResult Function(_Submitted value) submitted, + }) { + return submitted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RegistrationTokenChanged value)? + registrationTokenChanged, + TResult? Function(_OtpCodeChanged value)? otpCodeChanged, + TResult? Function(_Submitted value)? submitted, + }) { + return submitted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RegistrationTokenChanged value)? registrationTokenChanged, + TResult Function(_OtpCodeChanged value)? otpCodeChanged, + TResult Function(_Submitted value)? submitted, + required TResult orElse(), + }) { + if (submitted != null) { + return submitted(this); + } + return orElse(); + } +} + +abstract class _Submitted implements VerifyFormEvent { + const factory _Submitted() = _$SubmittedImpl; +} + +/// @nodoc +mixin _$VerifyFormState { + String get registrationToken => throw _privateConstructorUsedError; + String get otpCode => throw _privateConstructorUsedError; + Option> get failureOrVerifyOption => + throw _privateConstructorUsedError; + bool get isSubmitting => throw _privateConstructorUsedError; + bool get showErrorMessages => throw _privateConstructorUsedError; + + /// Create a copy of VerifyFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VerifyFormStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyFormStateCopyWith<$Res> { + factory $VerifyFormStateCopyWith( + VerifyFormState value, + $Res Function(VerifyFormState) then, + ) = _$VerifyFormStateCopyWithImpl<$Res, VerifyFormState>; + @useResult + $Res call({ + String registrationToken, + String otpCode, + Option> failureOrVerifyOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class _$VerifyFormStateCopyWithImpl<$Res, $Val extends VerifyFormState> + implements $VerifyFormStateCopyWith<$Res> { + _$VerifyFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VerifyFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = null, + Object? otpCode = null, + Object? failureOrVerifyOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _value.copyWith( + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + otpCode: null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as String, + failureOrVerifyOption: null == failureOrVerifyOption + ? _value.failureOrVerifyOption + : failureOrVerifyOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$VerifyFormStateImplCopyWith<$Res> + implements $VerifyFormStateCopyWith<$Res> { + factory _$$VerifyFormStateImplCopyWith( + _$VerifyFormStateImpl value, + $Res Function(_$VerifyFormStateImpl) then, + ) = __$$VerifyFormStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String registrationToken, + String otpCode, + Option> failureOrVerifyOption, + bool isSubmitting, + bool showErrorMessages, + }); +} + +/// @nodoc +class __$$VerifyFormStateImplCopyWithImpl<$Res> + extends _$VerifyFormStateCopyWithImpl<$Res, _$VerifyFormStateImpl> + implements _$$VerifyFormStateImplCopyWith<$Res> { + __$$VerifyFormStateImplCopyWithImpl( + _$VerifyFormStateImpl _value, + $Res Function(_$VerifyFormStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = null, + Object? otpCode = null, + Object? failureOrVerifyOption = null, + Object? isSubmitting = null, + Object? showErrorMessages = null, + }) { + return _then( + _$VerifyFormStateImpl( + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + otpCode: null == otpCode + ? _value.otpCode + : otpCode // ignore: cast_nullable_to_non_nullable + as String, + failureOrVerifyOption: null == failureOrVerifyOption + ? _value.failureOrVerifyOption + : failureOrVerifyOption // ignore: cast_nullable_to_non_nullable + as Option>, + isSubmitting: null == isSubmitting + ? _value.isSubmitting + : isSubmitting // ignore: cast_nullable_to_non_nullable + as bool, + showErrorMessages: null == showErrorMessages + ? _value.showErrorMessages + : showErrorMessages // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$VerifyFormStateImpl implements _VerifyFormState { + const _$VerifyFormStateImpl({ + required this.registrationToken, + required this.otpCode, + required this.failureOrVerifyOption, + this.isSubmitting = false, + this.showErrorMessages = false, + }); + + @override + final String registrationToken; + @override + final String otpCode; + @override + final Option> failureOrVerifyOption; + @override + @JsonKey() + final bool isSubmitting; + @override + @JsonKey() + final bool showErrorMessages; + + @override + String toString() { + return 'VerifyFormState(registrationToken: $registrationToken, otpCode: $otpCode, failureOrVerifyOption: $failureOrVerifyOption, isSubmitting: $isSubmitting, showErrorMessages: $showErrorMessages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VerifyFormStateImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken) && + (identical(other.otpCode, otpCode) || other.otpCode == otpCode) && + (identical(other.failureOrVerifyOption, failureOrVerifyOption) || + other.failureOrVerifyOption == failureOrVerifyOption) && + (identical(other.isSubmitting, isSubmitting) || + other.isSubmitting == isSubmitting) && + (identical(other.showErrorMessages, showErrorMessages) || + other.showErrorMessages == showErrorMessages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + registrationToken, + otpCode, + failureOrVerifyOption, + isSubmitting, + showErrorMessages, + ); + + /// Create a copy of VerifyFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VerifyFormStateImplCopyWith<_$VerifyFormStateImpl> get copyWith => + __$$VerifyFormStateImplCopyWithImpl<_$VerifyFormStateImpl>( + this, + _$identity, + ); +} + +abstract class _VerifyFormState implements VerifyFormState { + const factory _VerifyFormState({ + required final String registrationToken, + required final String otpCode, + required final Option> failureOrVerifyOption, + final bool isSubmitting, + final bool showErrorMessages, + }) = _$VerifyFormStateImpl; + + @override + String get registrationToken; + @override + String get otpCode; + @override + Option> get failureOrVerifyOption; + @override + bool get isSubmitting; + @override + bool get showErrorMessages; + + /// Create a copy of VerifyFormState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VerifyFormStateImplCopyWith<_$VerifyFormStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/auth/verify_form/verify_form_event.dart b/lib/application/auth/verify_form/verify_form_event.dart new file mode 100644 index 0000000..d86587a --- /dev/null +++ b/lib/application/auth/verify_form/verify_form_event.dart @@ -0,0 +1,11 @@ +part of 'verify_form_bloc.dart'; + +@freezed +class VerifyFormEvent with _$VerifyFormEvent { + const factory VerifyFormEvent.registrationTokenChanged( + String registrationToken, + ) = _RegistrationTokenChanged; + const factory VerifyFormEvent.otpCodeChanged(String otpCode) = + _OtpCodeChanged; + const factory VerifyFormEvent.submitted() = _Submitted; +} diff --git a/lib/application/auth/verify_form/verify_form_state.dart b/lib/application/auth/verify_form/verify_form_state.dart new file mode 100644 index 0000000..1955239 --- /dev/null +++ b/lib/application/auth/verify_form/verify_form_state.dart @@ -0,0 +1,18 @@ +part of 'verify_form_bloc.dart'; + +@freezed +class VerifyFormState with _$VerifyFormState { + const factory VerifyFormState({ + required String registrationToken, + required String otpCode, + required Option> failureOrVerifyOption, + @Default(false) bool isSubmitting, + @Default(false) bool showErrorMessages, + }) = _VerifyFormState; + + factory VerifyFormState.initial() => VerifyFormState( + registrationToken: '', + otpCode: '', + failureOrVerifyOption: none(), + ); +} diff --git a/lib/application/customer/customer_point_loader/customer_point_loader_bloc.dart b/lib/application/customer/customer_point_loader/customer_point_loader_bloc.dart new file mode 100644 index 0000000..f64dab9 --- /dev/null +++ b/lib/application/customer/customer_point_loader/customer_point_loader_bloc.dart @@ -0,0 +1,42 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/customer/customer.dart'; + +part 'customer_point_loader_event.dart'; +part 'customer_point_loader_state.dart'; +part 'customer_point_loader_bloc.freezed.dart'; + +@injectable +class CustomerPointLoaderBloc + extends Bloc { + final ICustomerRepository _repository; + CustomerPointLoaderBloc(this._repository) + : super(CustomerPointLoaderState.initial()) { + on(_onCustomerPointLoaderEvent); + } + + Future _onCustomerPointLoaderEvent( + CustomerPointLoaderEvent event, + Emitter emit, + ) { + return event.map( + fetched: (e) async { + emit( + state.copyWith(isFetching: true, failureOptionCustomerPoint: none()), + ); + + final result = await _repository.getPoints(); + + var data = result.fold( + (f) => state.copyWith(failureOptionCustomerPoint: optionOf(f)), + (customerPoint) => state.copyWith(customerPoint: customerPoint), + ); + + emit(data.copyWith(isFetching: false)); + }, + ); + } +} diff --git a/lib/application/customer/customer_point_loader/customer_point_loader_bloc.freezed.dart b/lib/application/customer/customer_point_loader/customer_point_loader_bloc.freezed.dart new file mode 100644 index 0000000..3588ac7 --- /dev/null +++ b/lib/application/customer/customer_point_loader/customer_point_loader_bloc.freezed.dart @@ -0,0 +1,391 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'customer_point_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$CustomerPointLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerPointLoaderEventCopyWith<$Res> { + factory $CustomerPointLoaderEventCopyWith( + CustomerPointLoaderEvent value, + $Res Function(CustomerPointLoaderEvent) then, + ) = _$CustomerPointLoaderEventCopyWithImpl<$Res, CustomerPointLoaderEvent>; +} + +/// @nodoc +class _$CustomerPointLoaderEventCopyWithImpl< + $Res, + $Val extends CustomerPointLoaderEvent +> + implements $CustomerPointLoaderEventCopyWith<$Res> { + _$CustomerPointLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerPointLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchedImplCopyWith<$Res> { + factory _$$FetchedImplCopyWith( + _$FetchedImpl value, + $Res Function(_$FetchedImpl) then, + ) = __$$FetchedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchedImplCopyWithImpl<$Res> + extends _$CustomerPointLoaderEventCopyWithImpl<$Res, _$FetchedImpl> + implements _$$FetchedImplCopyWith<$Res> { + __$$FetchedImplCopyWithImpl( + _$FetchedImpl _value, + $Res Function(_$FetchedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerPointLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchedImpl implements _Fetched { + const _$FetchedImpl(); + + @override + String toString() { + return 'CustomerPointLoaderEvent.fetched()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({required TResult Function() fetched}) { + return fetched(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({TResult? Function()? fetched}) { + return fetched?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) { + return fetched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) { + return fetched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(this); + } + return orElse(); + } +} + +abstract class _Fetched implements CustomerPointLoaderEvent { + const factory _Fetched() = _$FetchedImpl; +} + +/// @nodoc +mixin _$CustomerPointLoaderState { + CustomerPoint get customerPoint => throw _privateConstructorUsedError; + Option get failureOptionCustomerPoint => + throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerPointLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerPointLoaderStateCopyWith<$Res> { + factory $CustomerPointLoaderStateCopyWith( + CustomerPointLoaderState value, + $Res Function(CustomerPointLoaderState) then, + ) = _$CustomerPointLoaderStateCopyWithImpl<$Res, CustomerPointLoaderState>; + @useResult + $Res call({ + CustomerPoint customerPoint, + Option failureOptionCustomerPoint, + bool isFetching, + }); + + $CustomerPointCopyWith<$Res> get customerPoint; +} + +/// @nodoc +class _$CustomerPointLoaderStateCopyWithImpl< + $Res, + $Val extends CustomerPointLoaderState +> + implements $CustomerPointLoaderStateCopyWith<$Res> { + _$CustomerPointLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? customerPoint = null, + Object? failureOptionCustomerPoint = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + customerPoint: null == customerPoint + ? _value.customerPoint + : customerPoint // ignore: cast_nullable_to_non_nullable + as CustomerPoint, + failureOptionCustomerPoint: null == failureOptionCustomerPoint + ? _value.failureOptionCustomerPoint + : failureOptionCustomerPoint // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $CustomerPointCopyWith<$Res> get customerPoint { + return $CustomerPointCopyWith<$Res>(_value.customerPoint, (value) { + return _then(_value.copyWith(customerPoint: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CustomerPointLoaderStateImplCopyWith<$Res> + implements $CustomerPointLoaderStateCopyWith<$Res> { + factory _$$CustomerPointLoaderStateImplCopyWith( + _$CustomerPointLoaderStateImpl value, + $Res Function(_$CustomerPointLoaderStateImpl) then, + ) = __$$CustomerPointLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + CustomerPoint customerPoint, + Option failureOptionCustomerPoint, + bool isFetching, + }); + + @override + $CustomerPointCopyWith<$Res> get customerPoint; +} + +/// @nodoc +class __$$CustomerPointLoaderStateImplCopyWithImpl<$Res> + extends + _$CustomerPointLoaderStateCopyWithImpl< + $Res, + _$CustomerPointLoaderStateImpl + > + implements _$$CustomerPointLoaderStateImplCopyWith<$Res> { + __$$CustomerPointLoaderStateImplCopyWithImpl( + _$CustomerPointLoaderStateImpl _value, + $Res Function(_$CustomerPointLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? customerPoint = null, + Object? failureOptionCustomerPoint = null, + Object? isFetching = null, + }) { + return _then( + _$CustomerPointLoaderStateImpl( + customerPoint: null == customerPoint + ? _value.customerPoint + : customerPoint // ignore: cast_nullable_to_non_nullable + as CustomerPoint, + failureOptionCustomerPoint: null == failureOptionCustomerPoint + ? _value.failureOptionCustomerPoint + : failureOptionCustomerPoint // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$CustomerPointLoaderStateImpl implements _CustomerPointLoaderState { + const _$CustomerPointLoaderStateImpl({ + required this.customerPoint, + required this.failureOptionCustomerPoint, + this.isFetching = false, + }); + + @override + final CustomerPoint customerPoint; + @override + final Option failureOptionCustomerPoint; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'CustomerPointLoaderState(customerPoint: $customerPoint, failureOptionCustomerPoint: $failureOptionCustomerPoint, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CustomerPointLoaderStateImpl && + (identical(other.customerPoint, customerPoint) || + other.customerPoint == customerPoint) && + (identical( + other.failureOptionCustomerPoint, + failureOptionCustomerPoint, + ) || + other.failureOptionCustomerPoint == + failureOptionCustomerPoint) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + customerPoint, + failureOptionCustomerPoint, + isFetching, + ); + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CustomerPointLoaderStateImplCopyWith<_$CustomerPointLoaderStateImpl> + get copyWith => + __$$CustomerPointLoaderStateImplCopyWithImpl< + _$CustomerPointLoaderStateImpl + >(this, _$identity); +} + +abstract class _CustomerPointLoaderState implements CustomerPointLoaderState { + const factory _CustomerPointLoaderState({ + required final CustomerPoint customerPoint, + required final Option failureOptionCustomerPoint, + final bool isFetching, + }) = _$CustomerPointLoaderStateImpl; + + @override + CustomerPoint get customerPoint; + @override + Option get failureOptionCustomerPoint; + @override + bool get isFetching; + + /// Create a copy of CustomerPointLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CustomerPointLoaderStateImplCopyWith<_$CustomerPointLoaderStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/application/customer/customer_point_loader/customer_point_loader_event.dart b/lib/application/customer/customer_point_loader/customer_point_loader_event.dart new file mode 100644 index 0000000..6e45f9c --- /dev/null +++ b/lib/application/customer/customer_point_loader/customer_point_loader_event.dart @@ -0,0 +1,6 @@ +part of 'customer_point_loader_bloc.dart'; + +@freezed +class CustomerPointLoaderEvent with _$CustomerPointLoaderEvent { + const factory CustomerPointLoaderEvent.fetched() = _Fetched; +} diff --git a/lib/application/customer/customer_point_loader/customer_point_loader_state.dart b/lib/application/customer/customer_point_loader/customer_point_loader_state.dart new file mode 100644 index 0000000..b5ab295 --- /dev/null +++ b/lib/application/customer/customer_point_loader/customer_point_loader_state.dart @@ -0,0 +1,15 @@ +part of 'customer_point_loader_bloc.dart'; + +@freezed +class CustomerPointLoaderState with _$CustomerPointLoaderState { + const factory CustomerPointLoaderState({ + required CustomerPoint customerPoint, + required Option failureOptionCustomerPoint, + @Default(false) bool isFetching, + }) = _CustomerPointLoaderState; + + factory CustomerPointLoaderState.initial() => CustomerPointLoaderState( + customerPoint: CustomerPoint.empty(), + failureOptionCustomerPoint: none(), + ); +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart new file mode 100644 index 0000000..3cf713f --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart @@ -0,0 +1,42 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/game/game.dart'; + +part 'ferris_wheel_loader_event.dart'; +part 'ferris_wheel_loader_state.dart'; +part 'ferris_wheel_loader_bloc.freezed.dart'; + +@injectable +class FerrisWheelLoaderBloc + extends Bloc { + final IGameRepository _repository; + FerrisWheelLoaderBloc(this._repository) + : super(FerrisWheelLoaderState.initial()) { + on(_onFerrisWheelLoaderEvent); + } + + Future _onFerrisWheelLoaderEvent( + FerrisWheelLoaderEvent event, + Emitter emit, + ) { + return event.map( + fetched: (e) async { + emit( + state.copyWith(isFetching: true, failureOptionFerrisWheel: none()), + ); + + final result = await _repository.ferrisWheel(); + + var data = result.fold( + (f) => state.copyWith(failureOptionFerrisWheel: optionOf(f)), + (ferrisWheel) => state.copyWith(ferrisWheel: ferrisWheel), + ); + + emit(data.copyWith(isFetching: false)); + }, + ); + } +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart new file mode 100644 index 0000000..0979be9 --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart @@ -0,0 +1,388 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'ferris_wheel_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$FerrisWheelLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FerrisWheelLoaderEventCopyWith<$Res> { + factory $FerrisWheelLoaderEventCopyWith( + FerrisWheelLoaderEvent value, + $Res Function(FerrisWheelLoaderEvent) then, + ) = _$FerrisWheelLoaderEventCopyWithImpl<$Res, FerrisWheelLoaderEvent>; +} + +/// @nodoc +class _$FerrisWheelLoaderEventCopyWithImpl< + $Res, + $Val extends FerrisWheelLoaderEvent +> + implements $FerrisWheelLoaderEventCopyWith<$Res> { + _$FerrisWheelLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FerrisWheelLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchedImplCopyWith<$Res> { + factory _$$FetchedImplCopyWith( + _$FetchedImpl value, + $Res Function(_$FetchedImpl) then, + ) = __$$FetchedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchedImplCopyWithImpl<$Res> + extends _$FerrisWheelLoaderEventCopyWithImpl<$Res, _$FetchedImpl> + implements _$$FetchedImplCopyWith<$Res> { + __$$FetchedImplCopyWithImpl( + _$FetchedImpl _value, + $Res Function(_$FetchedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of FerrisWheelLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchedImpl implements _Fetched { + const _$FetchedImpl(); + + @override + String toString() { + return 'FerrisWheelLoaderEvent.fetched()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({required TResult Function() fetched}) { + return fetched(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({TResult? Function()? fetched}) { + return fetched?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) { + return fetched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) { + return fetched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(this); + } + return orElse(); + } +} + +abstract class _Fetched implements FerrisWheelLoaderEvent { + const factory _Fetched() = _$FetchedImpl; +} + +/// @nodoc +mixin _$FerrisWheelLoaderState { + Game get ferrisWheel => throw _privateConstructorUsedError; + Option get failureOptionFerrisWheel => + throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $FerrisWheelLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FerrisWheelLoaderStateCopyWith<$Res> { + factory $FerrisWheelLoaderStateCopyWith( + FerrisWheelLoaderState value, + $Res Function(FerrisWheelLoaderState) then, + ) = _$FerrisWheelLoaderStateCopyWithImpl<$Res, FerrisWheelLoaderState>; + @useResult + $Res call({ + Game ferrisWheel, + Option failureOptionFerrisWheel, + bool isFetching, + }); + + $GameCopyWith<$Res> get ferrisWheel; +} + +/// @nodoc +class _$FerrisWheelLoaderStateCopyWithImpl< + $Res, + $Val extends FerrisWheelLoaderState +> + implements $FerrisWheelLoaderStateCopyWith<$Res> { + _$FerrisWheelLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ferrisWheel = null, + Object? failureOptionFerrisWheel = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + ferrisWheel: null == ferrisWheel + ? _value.ferrisWheel + : ferrisWheel // ignore: cast_nullable_to_non_nullable + as Game, + failureOptionFerrisWheel: null == failureOptionFerrisWheel + ? _value.failureOptionFerrisWheel + : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $GameCopyWith<$Res> get ferrisWheel { + return $GameCopyWith<$Res>(_value.ferrisWheel, (value) { + return _then(_value.copyWith(ferrisWheel: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$FerrisWheelLoaderStateImplCopyWith<$Res> + implements $FerrisWheelLoaderStateCopyWith<$Res> { + factory _$$FerrisWheelLoaderStateImplCopyWith( + _$FerrisWheelLoaderStateImpl value, + $Res Function(_$FerrisWheelLoaderStateImpl) then, + ) = __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + Game ferrisWheel, + Option failureOptionFerrisWheel, + bool isFetching, + }); + + @override + $GameCopyWith<$Res> get ferrisWheel; +} + +/// @nodoc +class __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res> + extends + _$FerrisWheelLoaderStateCopyWithImpl<$Res, _$FerrisWheelLoaderStateImpl> + implements _$$FerrisWheelLoaderStateImplCopyWith<$Res> { + __$$FerrisWheelLoaderStateImplCopyWithImpl( + _$FerrisWheelLoaderStateImpl _value, + $Res Function(_$FerrisWheelLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ferrisWheel = null, + Object? failureOptionFerrisWheel = null, + Object? isFetching = null, + }) { + return _then( + _$FerrisWheelLoaderStateImpl( + ferrisWheel: null == ferrisWheel + ? _value.ferrisWheel + : ferrisWheel // ignore: cast_nullable_to_non_nullable + as Game, + failureOptionFerrisWheel: null == failureOptionFerrisWheel + ? _value.failureOptionFerrisWheel + : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$FerrisWheelLoaderStateImpl implements _FerrisWheelLoaderState { + const _$FerrisWheelLoaderStateImpl({ + required this.ferrisWheel, + required this.failureOptionFerrisWheel, + this.isFetching = false, + }); + + @override + final Game ferrisWheel; + @override + final Option failureOptionFerrisWheel; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'FerrisWheelLoaderState(ferrisWheel: $ferrisWheel, failureOptionFerrisWheel: $failureOptionFerrisWheel, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FerrisWheelLoaderStateImpl && + (identical(other.ferrisWheel, ferrisWheel) || + other.ferrisWheel == ferrisWheel) && + (identical( + other.failureOptionFerrisWheel, + failureOptionFerrisWheel, + ) || + other.failureOptionFerrisWheel == failureOptionFerrisWheel) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + ferrisWheel, + failureOptionFerrisWheel, + isFetching, + ); + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl> + get copyWith => + __$$FerrisWheelLoaderStateImplCopyWithImpl<_$FerrisWheelLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _FerrisWheelLoaderState implements FerrisWheelLoaderState { + const factory _FerrisWheelLoaderState({ + required final Game ferrisWheel, + required final Option failureOptionFerrisWheel, + final bool isFetching, + }) = _$FerrisWheelLoaderStateImpl; + + @override + Game get ferrisWheel; + @override + Option get failureOptionFerrisWheel; + @override + bool get isFetching; + + /// Create a copy of FerrisWheelLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart new file mode 100644 index 0000000..c250a80 --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart @@ -0,0 +1,6 @@ +part of 'ferris_wheel_loader_bloc.dart'; + +@freezed +class FerrisWheelLoaderEvent with _$FerrisWheelLoaderEvent { + const factory FerrisWheelLoaderEvent.fetched() = _Fetched; +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart new file mode 100644 index 0000000..795a34c --- /dev/null +++ b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart @@ -0,0 +1,15 @@ +part of 'ferris_wheel_loader_bloc.dart'; + +@freezed +class FerrisWheelLoaderState with _$FerrisWheelLoaderState { + const factory FerrisWheelLoaderState({ + required Game ferrisWheel, + required Option failureOptionFerrisWheel, + @Default(false) bool isFetching, + }) = _FerrisWheelLoaderState; + + factory FerrisWheelLoaderState.initial() => FerrisWheelLoaderState( + ferrisWheel: Game.empty(), + failureOptionFerrisWheel: none(), + ); +} diff --git a/lib/common/api/api_client.dart b/lib/common/api/api_client.dart new file mode 100644 index 0000000..5e2aafe --- /dev/null +++ b/lib/common/api/api_client.dart @@ -0,0 +1,240 @@ +// ignore: depend_on_referenced_packages +import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../env.dart'; +import 'api_failure.dart'; +import 'errors/bad_network_error.dart'; +import 'errors/bad_request_error.dart'; +import 'errors/connection_timeout_error.dart'; +import 'errors/internal_server_error.dart'; +import 'errors/not_found_error.dart'; +import 'errors/unauthorized_error.dart'; +import 'interceptors/bad_network_interceptor.dart'; +import 'interceptors/bad_request_interceptor.dart'; +import 'interceptors/connection_timeout_interceptor.dart'; +import 'interceptors/internal_server_interceptor.dart'; +import 'interceptors/not_found_interceptor.dart'; +import 'interceptors/unauthorized_interceptor.dart'; + +@lazySingleton +class ApiClient { + final Dio _dio; + final Env _env; + + ApiClient(this._dio, this._env) { + _dio.options.baseUrl = _env.baseUrl; + _dio.options.connectTimeout = const Duration(seconds: 20); + _dio.options.validateStatus = (status) => true; + _dio.interceptors.add(BadNetworkErrorInterceptor()); + _dio.interceptors.add(BadRequestErrorInterceptor()); + _dio.interceptors.add(InternalServerErrorInterceptor()); + _dio.interceptors.add(NotFoundErrorInterceptor()); + _dio.interceptors.add(UnauthorizedInterceptor()); + _dio.interceptors.add(ConnectionTimeoutErrorInterceptor()); + + if (kDebugMode) { + _dio.interceptors.add( + AwesomeDioInterceptor( + logResponseHeaders: false, + logRequestTimeout: false, + logRequestHeaders: true, + ), + ); + } + } + + Future post( + String path, { + dynamic data, + Map? headers, + Map? params, + bool followRedirects = true, + bool Function(int?)? validateStatus, + String? contentType, + }) async { + try { + return await _dio.post( + path, + data: data, + options: Options( + headers: headers, + followRedirects: followRedirects, + validateStatus: validateStatus, + contentType: contentType, + ), + queryParameters: params, + ); + } on UnauthorizedError catch (e) { + throw ApiFailure.unauthorized(e.messageError); + } on InternalServerError { + throw const ApiFailure.internalServerError(); + } on BadNetworkError { + throw const ApiFailure.connectionError(); + } on BadRequestError catch (e) { + throw ApiFailure.badRequest(e.messageError); + } on NotFoundError catch (e) { + throw ApiFailure.notFound(e.messageError); + } on ConnectionTimeoutError { + throw const ApiFailure.connectionTimeout(); + } on DioException catch (e) { + var errorMessage = + e.response?.data['message'] ?? e.response?.statusMessage ?? e.error; + + if (errorMessage.toString().contains('Connection reset')) { + errorMessage = 'Connection reset'; + } + + throw ApiFailure.serverError( + statusCode: e.response?.statusCode ?? 0, + errorMessage: errorMessage.toString(), + ); + } catch (e, s) { + throw ApiFailure.unexpectedError(errorMessage: e, stackTrace: s); + } + } + + Future get( + String path, { + Map? headers, + Map? params, + bool followRedirects = true, + bool Function(int?)? validateStatus, + String? contentType, + }) async { + try { + return await _dio.get( + path, + options: Options( + headers: headers, + followRedirects: followRedirects, + validateStatus: validateStatus, + contentType: contentType, + ), + queryParameters: params, + ); + } on UnauthorizedError catch (e) { + throw ApiFailure.unauthorized(e.messageError); + } on InternalServerError { + throw const ApiFailure.internalServerError(); + } on BadNetworkError { + throw const ApiFailure.connectionError(); + } on BadRequestError catch (e) { + throw ApiFailure.badRequest(e.messageError); + } on NotFoundError catch (e) { + throw ApiFailure.notFound(e.messageError); + } on ConnectionTimeoutError { + throw const ApiFailure.connectionTimeout(); + } on DioException catch (e) { + var errorMessage = + e.response?.data['message'] ?? e.response?.statusMessage ?? e.error; + + if (errorMessage.toString().contains('Connection reset')) { + errorMessage = 'Connection reset'; + } + + throw ApiFailure.serverError( + statusCode: e.response?.statusCode ?? 0, + errorMessage: errorMessage.toString(), + ); + } catch (e, s) { + throw ApiFailure.unexpectedError(errorMessage: e, stackTrace: s); + } + } + + Future put( + String path, { + dynamic data, + Map? headers, + Map? params, + bool followRedirects = true, + bool Function(int?)? validateStatus, + String? contentType, + void Function(int count, int total)? onSendProgress, + }) async { + try { + return await _dio.put( + path, + data: data, + options: Options( + headers: headers, + followRedirects: followRedirects, + validateStatus: validateStatus, + contentType: contentType, + ), + queryParameters: params, + onSendProgress: onSendProgress, + ); + } on UnauthorizedError catch (e) { + throw ApiFailure.unauthorized(e.messageError); + } on InternalServerError { + throw const ApiFailure.internalServerError(); + } on BadNetworkError { + throw const ApiFailure.connectionError(); + } on BadRequestError catch (e) { + throw ApiFailure.badRequest(e.messageError); + } on NotFoundError catch (e) { + throw ApiFailure.notFound(e.messageError); + } on ConnectionTimeoutError { + throw const ApiFailure.connectionTimeout(); + } on DioException catch (e) { + var errorMessage = + e.response?.data['message'] ?? e.response?.statusMessage ?? e.error; + + if (errorMessage.toString().contains('Connection reset')) { + errorMessage = 'Connection reset'; + } + + throw ApiFailure.serverError( + statusCode: e.response?.statusCode ?? 0, + errorMessage: errorMessage.toString(), + ); + } catch (e, s) { + throw ApiFailure.unexpectedError(errorMessage: e, stackTrace: s); + } + } + + Future delete( + String path, { + dynamic data, + Map? headers, + Map? params, + bool followRedirects = true, + bool Function(int?)? validateStatus, + String? contentType, + }) async { + try { + return await _dio.delete( + path, + data: data, + options: Options( + headers: headers, + followRedirects: followRedirects, + validateStatus: validateStatus, + contentType: contentType, + ), + queryParameters: params, + ); + } on UnauthorizedError catch (e) { + throw ApiFailure.unauthorized(e.messageError); + } on InternalServerError { + throw const ApiFailure.internalServerError(); + } on BadNetworkError { + throw const ApiFailure.connectionError(); + } on BadRequestError catch (e) { + throw ApiFailure.badRequest(e.messageError); + } on NotFoundError catch (e) { + throw ApiFailure.notFound(e.messageError); + } on DioException catch (e) { + throw ApiFailure.serverError( + statusCode: e.response?.statusCode ?? 0, + errorMessage: + e.response?.data['message'] ?? e.response?.statusMessage ?? e.error, + ); + } catch (e, s) { + throw ApiFailure.unexpectedError(errorMessage: e, stackTrace: s); + } + } +} diff --git a/lib/common/api/api_failure.dart b/lib/common/api/api_failure.dart new file mode 100644 index 0000000..bcfbd28 --- /dev/null +++ b/lib/common/api/api_failure.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'api_failure.freezed.dart'; + +@freezed +sealed class ApiFailure with _$ApiFailure { + const ApiFailure._(); + + const factory ApiFailure.serverError({ + required int statusCode, + required Object errorMessage, + }) = _ServerError; + + const factory ApiFailure.unexpectedError({ + required Object errorMessage, + required StackTrace stackTrace, + }) = _UnexpectedError; + + const factory ApiFailure.connectionError() = _ConnectionError; + + const factory ApiFailure.internalServerError() = _InternalServerError; + + const factory ApiFailure.unauthorized(String? message) = _Unauthorized; + + const factory ApiFailure.badRequest(String? message) = _BadRequest; + + const factory ApiFailure.notFound(String? message) = _NotFound; + + const factory ApiFailure.connectionTimeout() = _ConnectionTimeout; + + String toStringFormatted( + BuildContext context, { + String? unauthorizedMessage, + }) { + return switch (this) { + _ServerError(:final statusCode, :final errorMessage) => + 'There is a problem with the server. Status code: $statusCode Error: $errorMessage', + + _UnexpectedError() => 'An error occurred. Please try again later.', + + _ConnectionError() => 'No Internet', + + _InternalServerError() => + 'The server is experiencing problems. Please try again later.', + + _Unauthorized(:final message) => + message ?? unauthorizedMessage ?? 'Session has expired.', + + _BadRequest(:final message) => + message ?? 'There is an incorrect entry. Please check again', + + _NotFound(:final message) => message ?? 'Not Found', + + _ConnectionTimeout() => 'Connection Timeout', + }; + } +} diff --git a/lib/common/api/api_failure.freezed.dart b/lib/common/api/api_failure.freezed.dart new file mode 100644 index 0000000..aac5bd3 --- /dev/null +++ b/lib/common/api/api_failure.freezed.dart @@ -0,0 +1,1506 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'api_failure.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ApiFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApiFailureCopyWith<$Res> { + factory $ApiFailureCopyWith( + ApiFailure value, + $Res Function(ApiFailure) then, + ) = _$ApiFailureCopyWithImpl<$Res, ApiFailure>; +} + +/// @nodoc +class _$ApiFailureCopyWithImpl<$Res, $Val extends ApiFailure> + implements $ApiFailureCopyWith<$Res> { + _$ApiFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({int statusCode, Object errorMessage}); +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? statusCode = null, Object? errorMessage = null}) { + return _then( + _$ServerErrorImpl( + statusCode: null == statusCode + ? _value.statusCode + : statusCode // ignore: cast_nullable_to_non_nullable + as int, + errorMessage: null == errorMessage ? _value.errorMessage : errorMessage, + ), + ); + } +} + +/// @nodoc + +class _$ServerErrorImpl extends _ServerError { + const _$ServerErrorImpl({ + required this.statusCode, + required this.errorMessage, + }) : super._(); + + @override + final int statusCode; + @override + final Object errorMessage; + + @override + String toString() { + return 'ApiFailure.serverError(statusCode: $statusCode, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.statusCode, statusCode) || + other.statusCode == statusCode) && + const DeepCollectionEquality().equals( + other.errorMessage, + errorMessage, + )); + } + + @override + int get hashCode => Object.hash( + runtimeType, + statusCode, + const DeepCollectionEquality().hash(errorMessage), + ); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return serverError(statusCode, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return serverError?.call(statusCode, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(statusCode, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError extends ApiFailure { + const factory _ServerError({ + required final int statusCode, + required final Object errorMessage, + }) = _$ServerErrorImpl; + const _ServerError._() : super._(); + + int get statusCode; + Object get errorMessage; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({Object errorMessage, StackTrace stackTrace}); +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? errorMessage = null, Object? stackTrace = null}) { + return _then( + _$UnexpectedErrorImpl( + errorMessage: null == errorMessage ? _value.errorMessage : errorMessage, + stackTrace: null == stackTrace + ? _value.stackTrace + : stackTrace // ignore: cast_nullable_to_non_nullable + as StackTrace, + ), + ); + } +} + +/// @nodoc + +class _$UnexpectedErrorImpl extends _UnexpectedError { + const _$UnexpectedErrorImpl({ + required this.errorMessage, + required this.stackTrace, + }) : super._(); + + @override + final Object errorMessage; + @override + final StackTrace stackTrace; + + @override + String toString() { + return 'ApiFailure.unexpectedError(errorMessage: $errorMessage, stackTrace: $stackTrace)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UnexpectedErrorImpl && + const DeepCollectionEquality().equals( + other.errorMessage, + errorMessage, + ) && + (identical(other.stackTrace, stackTrace) || + other.stackTrace == stackTrace)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(errorMessage), + stackTrace, + ); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UnexpectedErrorImplCopyWith<_$UnexpectedErrorImpl> get copyWith => + __$$UnexpectedErrorImplCopyWithImpl<_$UnexpectedErrorImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return unexpectedError(errorMessage, stackTrace); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return unexpectedError?.call(errorMessage, stackTrace); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(errorMessage, stackTrace); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError extends ApiFailure { + const factory _UnexpectedError({ + required final Object errorMessage, + required final StackTrace stackTrace, + }) = _$UnexpectedErrorImpl; + const _UnexpectedError._() : super._(); + + Object get errorMessage; + StackTrace get stackTrace; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UnexpectedErrorImplCopyWith<_$UnexpectedErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ConnectionErrorImplCopyWith<$Res> { + factory _$$ConnectionErrorImplCopyWith( + _$ConnectionErrorImpl value, + $Res Function(_$ConnectionErrorImpl) then, + ) = __$$ConnectionErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ConnectionErrorImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$ConnectionErrorImpl> + implements _$$ConnectionErrorImplCopyWith<$Res> { + __$$ConnectionErrorImplCopyWithImpl( + _$ConnectionErrorImpl _value, + $Res Function(_$ConnectionErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ConnectionErrorImpl extends _ConnectionError { + const _$ConnectionErrorImpl() : super._(); + + @override + String toString() { + return 'ApiFailure.connectionError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ConnectionErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return connectionError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return connectionError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (connectionError != null) { + return connectionError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return connectionError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return connectionError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (connectionError != null) { + return connectionError(this); + } + return orElse(); + } +} + +abstract class _ConnectionError extends ApiFailure { + const factory _ConnectionError() = _$ConnectionErrorImpl; + const _ConnectionError._() : super._(); +} + +/// @nodoc +abstract class _$$InternalServerErrorImplCopyWith<$Res> { + factory _$$InternalServerErrorImplCopyWith( + _$InternalServerErrorImpl value, + $Res Function(_$InternalServerErrorImpl) then, + ) = __$$InternalServerErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InternalServerErrorImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$InternalServerErrorImpl> + implements _$$InternalServerErrorImplCopyWith<$Res> { + __$$InternalServerErrorImplCopyWithImpl( + _$InternalServerErrorImpl _value, + $Res Function(_$InternalServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InternalServerErrorImpl extends _InternalServerError { + const _$InternalServerErrorImpl() : super._(); + + @override + String toString() { + return 'ApiFailure.internalServerError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InternalServerErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return internalServerError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return internalServerError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (internalServerError != null) { + return internalServerError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return internalServerError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return internalServerError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (internalServerError != null) { + return internalServerError(this); + } + return orElse(); + } +} + +abstract class _InternalServerError extends ApiFailure { + const factory _InternalServerError() = _$InternalServerErrorImpl; + const _InternalServerError._() : super._(); +} + +/// @nodoc +abstract class _$$UnauthorizedImplCopyWith<$Res> { + factory _$$UnauthorizedImplCopyWith( + _$UnauthorizedImpl value, + $Res Function(_$UnauthorizedImpl) then, + ) = __$$UnauthorizedImplCopyWithImpl<$Res>; + @useResult + $Res call({String? message}); +} + +/// @nodoc +class __$$UnauthorizedImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$UnauthorizedImpl> + implements _$$UnauthorizedImplCopyWith<$Res> { + __$$UnauthorizedImplCopyWithImpl( + _$UnauthorizedImpl _value, + $Res Function(_$UnauthorizedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? message = freezed}) { + return _then( + _$UnauthorizedImpl( + freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$UnauthorizedImpl extends _Unauthorized { + const _$UnauthorizedImpl(this.message) : super._(); + + @override + final String? message; + + @override + String toString() { + return 'ApiFailure.unauthorized(message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UnauthorizedImpl && + (identical(other.message, message) || other.message == message)); + } + + @override + int get hashCode => Object.hash(runtimeType, message); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UnauthorizedImplCopyWith<_$UnauthorizedImpl> get copyWith => + __$$UnauthorizedImplCopyWithImpl<_$UnauthorizedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return unauthorized(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return unauthorized?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (unauthorized != null) { + return unauthorized(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return unauthorized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return unauthorized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (unauthorized != null) { + return unauthorized(this); + } + return orElse(); + } +} + +abstract class _Unauthorized extends ApiFailure { + const factory _Unauthorized(final String? message) = _$UnauthorizedImpl; + const _Unauthorized._() : super._(); + + String? get message; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UnauthorizedImplCopyWith<_$UnauthorizedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$BadRequestImplCopyWith<$Res> { + factory _$$BadRequestImplCopyWith( + _$BadRequestImpl value, + $Res Function(_$BadRequestImpl) then, + ) = __$$BadRequestImplCopyWithImpl<$Res>; + @useResult + $Res call({String? message}); +} + +/// @nodoc +class __$$BadRequestImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$BadRequestImpl> + implements _$$BadRequestImplCopyWith<$Res> { + __$$BadRequestImplCopyWithImpl( + _$BadRequestImpl _value, + $Res Function(_$BadRequestImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? message = freezed}) { + return _then( + _$BadRequestImpl( + freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$BadRequestImpl extends _BadRequest { + const _$BadRequestImpl(this.message) : super._(); + + @override + final String? message; + + @override + String toString() { + return 'ApiFailure.badRequest(message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BadRequestImpl && + (identical(other.message, message) || other.message == message)); + } + + @override + int get hashCode => Object.hash(runtimeType, message); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BadRequestImplCopyWith<_$BadRequestImpl> get copyWith => + __$$BadRequestImplCopyWithImpl<_$BadRequestImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return badRequest(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return badRequest?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (badRequest != null) { + return badRequest(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return badRequest(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return badRequest?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (badRequest != null) { + return badRequest(this); + } + return orElse(); + } +} + +abstract class _BadRequest extends ApiFailure { + const factory _BadRequest(final String? message) = _$BadRequestImpl; + const _BadRequest._() : super._(); + + String? get message; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BadRequestImplCopyWith<_$BadRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$NotFoundImplCopyWith<$Res> { + factory _$$NotFoundImplCopyWith( + _$NotFoundImpl value, + $Res Function(_$NotFoundImpl) then, + ) = __$$NotFoundImplCopyWithImpl<$Res>; + @useResult + $Res call({String? message}); +} + +/// @nodoc +class __$$NotFoundImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$NotFoundImpl> + implements _$$NotFoundImplCopyWith<$Res> { + __$$NotFoundImplCopyWithImpl( + _$NotFoundImpl _value, + $Res Function(_$NotFoundImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? message = freezed}) { + return _then( + _$NotFoundImpl( + freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$NotFoundImpl extends _NotFound { + const _$NotFoundImpl(this.message) : super._(); + + @override + final String? message; + + @override + String toString() { + return 'ApiFailure.notFound(message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotFoundImpl && + (identical(other.message, message) || other.message == message)); + } + + @override + int get hashCode => Object.hash(runtimeType, message); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NotFoundImplCopyWith<_$NotFoundImpl> get copyWith => + __$$NotFoundImplCopyWithImpl<_$NotFoundImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return notFound(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return notFound?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (notFound != null) { + return notFound(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return notFound(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return notFound?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (notFound != null) { + return notFound(this); + } + return orElse(); + } +} + +abstract class _NotFound extends ApiFailure { + const factory _NotFound(final String? message) = _$NotFoundImpl; + const _NotFound._() : super._(); + + String? get message; + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NotFoundImplCopyWith<_$NotFoundImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ConnectionTimeoutImplCopyWith<$Res> { + factory _$$ConnectionTimeoutImplCopyWith( + _$ConnectionTimeoutImpl value, + $Res Function(_$ConnectionTimeoutImpl) then, + ) = __$$ConnectionTimeoutImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ConnectionTimeoutImplCopyWithImpl<$Res> + extends _$ApiFailureCopyWithImpl<$Res, _$ConnectionTimeoutImpl> + implements _$$ConnectionTimeoutImplCopyWith<$Res> { + __$$ConnectionTimeoutImplCopyWithImpl( + _$ConnectionTimeoutImpl _value, + $Res Function(_$ConnectionTimeoutImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ApiFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ConnectionTimeoutImpl extends _ConnectionTimeout { + const _$ConnectionTimeoutImpl() : super._(); + + @override + String toString() { + return 'ApiFailure.connectionTimeout()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ConnectionTimeoutImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int statusCode, Object errorMessage) serverError, + required TResult Function(Object errorMessage, StackTrace stackTrace) + unexpectedError, + required TResult Function() connectionError, + required TResult Function() internalServerError, + required TResult Function(String? message) unauthorized, + required TResult Function(String? message) badRequest, + required TResult Function(String? message) notFound, + required TResult Function() connectionTimeout, + }) { + return connectionTimeout(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int statusCode, Object errorMessage)? serverError, + TResult? Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult? Function()? connectionError, + TResult? Function()? internalServerError, + TResult? Function(String? message)? unauthorized, + TResult? Function(String? message)? badRequest, + TResult? Function(String? message)? notFound, + TResult? Function()? connectionTimeout, + }) { + return connectionTimeout?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int statusCode, Object errorMessage)? serverError, + TResult Function(Object errorMessage, StackTrace stackTrace)? + unexpectedError, + TResult Function()? connectionError, + TResult Function()? internalServerError, + TResult Function(String? message)? unauthorized, + TResult Function(String? message)? badRequest, + TResult Function(String? message)? notFound, + TResult Function()? connectionTimeout, + required TResult orElse(), + }) { + if (connectionTimeout != null) { + return connectionTimeout(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_ConnectionError value) connectionError, + required TResult Function(_InternalServerError value) internalServerError, + required TResult Function(_Unauthorized value) unauthorized, + required TResult Function(_BadRequest value) badRequest, + required TResult Function(_NotFound value) notFound, + required TResult Function(_ConnectionTimeout value) connectionTimeout, + }) { + return connectionTimeout(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_ConnectionError value)? connectionError, + TResult? Function(_InternalServerError value)? internalServerError, + TResult? Function(_Unauthorized value)? unauthorized, + TResult? Function(_BadRequest value)? badRequest, + TResult? Function(_NotFound value)? notFound, + TResult? Function(_ConnectionTimeout value)? connectionTimeout, + }) { + return connectionTimeout?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_ConnectionError value)? connectionError, + TResult Function(_InternalServerError value)? internalServerError, + TResult Function(_Unauthorized value)? unauthorized, + TResult Function(_BadRequest value)? badRequest, + TResult Function(_NotFound value)? notFound, + TResult Function(_ConnectionTimeout value)? connectionTimeout, + required TResult orElse(), + }) { + if (connectionTimeout != null) { + return connectionTimeout(this); + } + return orElse(); + } +} + +abstract class _ConnectionTimeout extends ApiFailure { + const factory _ConnectionTimeout() = _$ConnectionTimeoutImpl; + const _ConnectionTimeout._() : super._(); +} diff --git a/lib/common/api/errors/bad_network_error.dart b/lib/common/api/errors/bad_network_error.dart new file mode 100644 index 0000000..ef9bb82 --- /dev/null +++ b/lib/common/api/errors/bad_network_error.dart @@ -0,0 +1,15 @@ +import 'package:dio/dio.dart'; + +class BadNetworkError extends DioException { + final DioException dioError; + + BadNetworkError(this.dioError) + : super( + requestOptions: dioError.requestOptions, + error: dioError.error, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/errors/bad_request_error.dart b/lib/common/api/errors/bad_request_error.dart new file mode 100644 index 0000000..70660de --- /dev/null +++ b/lib/common/api/errors/bad_request_error.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; + +class BadRequestError extends DioException { + final DioException dioError; + final String? messageError; + + BadRequestError(this.dioError, this.messageError) + : super( + error: dioError.error, + requestOptions: dioError.requestOptions, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/errors/connection_timeout_error.dart b/lib/common/api/errors/connection_timeout_error.dart new file mode 100644 index 0000000..d7d1b12 --- /dev/null +++ b/lib/common/api/errors/connection_timeout_error.dart @@ -0,0 +1,15 @@ +import 'package:dio/dio.dart'; + +class ConnectionTimeoutError extends DioException { + final DioException dioError; + + ConnectionTimeoutError(this.dioError) + : super( + error: dioError.error, + requestOptions: dioError.requestOptions, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/errors/internal_server_error.dart b/lib/common/api/errors/internal_server_error.dart new file mode 100644 index 0000000..e94bcbf --- /dev/null +++ b/lib/common/api/errors/internal_server_error.dart @@ -0,0 +1,15 @@ + import 'package:dio/dio.dart'; + +class InternalServerError extends DioException { + final DioException dioError; + + InternalServerError(this.dioError) + : super( + requestOptions: dioError.requestOptions, + error: dioError.error, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/errors/not_found_error.dart b/lib/common/api/errors/not_found_error.dart new file mode 100644 index 0000000..957a4ef --- /dev/null +++ b/lib/common/api/errors/not_found_error.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; + +class NotFoundError extends DioException { + final DioException dioError; + final String? messageError; + + NotFoundError(this.dioError, this.messageError) + : super( + error: dioError.error, + requestOptions: dioError.requestOptions, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/errors/unauthorized_error.dart b/lib/common/api/errors/unauthorized_error.dart new file mode 100644 index 0000000..b1c967e --- /dev/null +++ b/lib/common/api/errors/unauthorized_error.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; + +class UnauthorizedError extends DioException { + final DioException dioError; + final String? messageError; + + UnauthorizedError(this.dioError, this.messageError) + : super( + requestOptions: dioError.requestOptions, + error: dioError.error, + response: dioError.response, + type: dioError.type, + message: dioError.message, + stackTrace: dioError.stackTrace, + ); +} diff --git a/lib/common/api/interceptors/bad_network_interceptor.dart b/lib/common/api/interceptors/bad_network_interceptor.dart new file mode 100644 index 0000000..3e82fd1 --- /dev/null +++ b/lib/common/api/interceptors/bad_network_interceptor.dart @@ -0,0 +1,22 @@ +import 'package:dio/dio.dart'; + +import '../../../injection.dart'; +import '../../network/network_client.dart'; +import '../errors/bad_network_error.dart'; + +class BadNetworkErrorInterceptor extends Interceptor { + final _networkClient = getIt(); + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + final isConnected = await _networkClient.isConnected; + + if (err.type == DioExceptionType.connectionTimeout || + !isConnected || + err.type == DioExceptionType.receiveTimeout || + err.type == DioExceptionType.connectionError) { + return super.onError(BadNetworkError(err), handler); + } + super.onError(err, handler); + } +} diff --git a/lib/common/api/interceptors/bad_request_interceptor.dart b/lib/common/api/interceptors/bad_request_interceptor.dart new file mode 100644 index 0000000..efac3fc --- /dev/null +++ b/lib/common/api/interceptors/bad_request_interceptor.dart @@ -0,0 +1,15 @@ +import 'package:dio/dio.dart'; + +import '../errors/bad_request_error.dart'; + +class BadRequestErrorInterceptor extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.response?.statusCode == 422 || + err.response?.statusCode == 400 || + err.response?.statusCode == 405) { + return super.onError(BadRequestError(err, null), handler); + } + super.onError(err, handler); + } +} diff --git a/lib/common/api/interceptors/connection_timeout_interceptor.dart b/lib/common/api/interceptors/connection_timeout_interceptor.dart new file mode 100644 index 0000000..c5abe94 --- /dev/null +++ b/lib/common/api/interceptors/connection_timeout_interceptor.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; + +import '../errors/connection_timeout_error.dart'; + +class ConnectionTimeoutErrorInterceptor extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.type == DioExceptionType.connectionTimeout) { + return super.onError(ConnectionTimeoutError(err), handler); + } + super.onError(err, handler); + } +} diff --git a/lib/common/api/interceptors/internal_server_interceptor.dart b/lib/common/api/interceptors/internal_server_interceptor.dart new file mode 100644 index 0000000..8eddc5c --- /dev/null +++ b/lib/common/api/interceptors/internal_server_interceptor.dart @@ -0,0 +1,18 @@ +import 'package:dio/dio.dart'; + +import '../errors/internal_server_error.dart'; + +class InternalServerErrorInterceptor extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.response != null) { + if (err.response?.statusCode != null && + err.response!.statusCode! >= 500 && + err.response!.statusCode! < 600) { + return super.onError(InternalServerError(err), handler); + } + } + + super.onError(err, handler); + } +} diff --git a/lib/common/api/interceptors/not_found_interceptor.dart b/lib/common/api/interceptors/not_found_interceptor.dart new file mode 100644 index 0000000..e625b92 --- /dev/null +++ b/lib/common/api/interceptors/not_found_interceptor.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; + +import '../errors/not_found_error.dart'; + +class NotFoundErrorInterceptor extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.response?.statusCode == 404) { + return super.onError(NotFoundError(err, null), handler); + } + super.onError(err, handler); + } +} diff --git a/lib/common/api/interceptors/unauthorized_interceptor.dart b/lib/common/api/interceptors/unauthorized_interceptor.dart new file mode 100644 index 0000000..646ef40 --- /dev/null +++ b/lib/common/api/interceptors/unauthorized_interceptor.dart @@ -0,0 +1,40 @@ +import 'dart:developer'; + +import 'package:auto_route/auto_route.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../presentation/router/app_router.gr.dart'; +import '../../constant/local_storage_key.dart'; +import '../errors/unauthorized_error.dart'; + +class UnauthorizedInterceptor extends Interceptor { + static final GlobalKey navigatorKey = + GlobalKey(); + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + if (err.response?.statusCode == 401 || + err.response?.statusCode == 403 || + err.response?.statusCode == 419) { + await _handleTokenExpired(); + return super.onError(UnauthorizedError(err, null), handler); + } + super.onError(err, handler); + } + + Future _handleTokenExpired() async { + // Clear stored token + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(LocalStorageKey.token); + await prefs.remove(LocalStorageKey.user); + await prefs.clear(); // Optional: clear all user data + log('handleTokenExpired'); + // Navigate to login page + final context = navigatorKey.currentContext; + if (context != null) { + // Option 1: Navigate and remove all previous routes + context.router.replaceAll([LoginRoute()]); + } + } +} diff --git a/lib/common/components/component.dart b/lib/common/components/component.dart new file mode 100644 index 0000000..7199fdd --- /dev/null +++ b/lib/common/components/component.dart @@ -0,0 +1 @@ + // TODO: define your code diff --git a/lib/common/constant/app_constant.dart b/lib/common/constant/app_constant.dart new file mode 100644 index 0000000..b0dbc06 --- /dev/null +++ b/lib/common/constant/app_constant.dart @@ -0,0 +1,3 @@ +class AppConstant { + static const String appName = ""; +} diff --git a/lib/common/constant/local_storage_key.dart b/lib/common/constant/local_storage_key.dart new file mode 100644 index 0000000..b23712d --- /dev/null +++ b/lib/common/constant/local_storage_key.dart @@ -0,0 +1,4 @@ +class LocalStorageKey { + static const token = 'token'; + static const user = 'user'; +} diff --git a/lib/common/di/di_auto_route.dart b/lib/common/di/di_auto_route.dart new file mode 100644 index 0000000..f5277bd --- /dev/null +++ b/lib/common/di/di_auto_route.dart @@ -0,0 +1,9 @@ +import 'package:injectable/injectable.dart'; + +import '../../presentation/router/app_router.dart'; + +@module +abstract class AutoRouteDi { + @lazySingleton + AppRouter get appRouter => AppRouter(); +} diff --git a/lib/common/di/di_connectivity.dart b/lib/common/di/di_connectivity.dart new file mode 100644 index 0000000..2bc7026 --- /dev/null +++ b/lib/common/di/di_connectivity.dart @@ -0,0 +1,8 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class ConnectivityDi { + @lazySingleton + Connectivity get connectivity => Connectivity(); +} diff --git a/lib/common/di/di_dio.dart b/lib/common/di/di_dio.dart new file mode 100644 index 0000000..56f622e --- /dev/null +++ b/lib/common/di/di_dio.dart @@ -0,0 +1,8 @@ +import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class DioDi { + @lazySingleton + Dio get dio => Dio(); +} diff --git a/lib/common/di/di_shared_preferences.dart b/lib/common/di/di_shared_preferences.dart new file mode 100644 index 0000000..3e4b581 --- /dev/null +++ b/lib/common/di/di_shared_preferences.dart @@ -0,0 +1,8 @@ +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +@module +abstract class SharedPreferencesDi { + @preResolve + Future get prefs => SharedPreferences.getInstance(); +} diff --git a/lib/common/extension/date_extension.dart b/lib/common/extension/date_extension.dart new file mode 100644 index 0000000..9486843 --- /dev/null +++ b/lib/common/extension/date_extension.dart @@ -0,0 +1,33 @@ +part of 'extension.dart'; + +extension DateTimeIndonesia on DateTime { + /// Format: 13 Agustus 2025 + String get toDate { + return DateFormat('d MMMM yyyy', 'id_ID').format(this); + } + + /// Format: 13 Agustus 2025 20:00 + String get toDatetime { + return DateFormat('d MMMM yyyy HH:mm', 'id_ID').format(this); + } + + /// Format: Rabu, 13 Agustus 2025 + String get toDayDate { + return DateFormat('EEEE, d MMMM yyyy', 'id_ID').format(this); + } + + /// Format: 13/08/2025 + String get toShortDate { + return DateFormat('dd/MM/yyyy', 'id_ID').format(this); + } + + /// Format: 13-08-2025 + String get toServerDate { + return DateFormat('yyyy-MM-dd', 'id_ID').format(this); + } + + /// Format jam: 14:30 + String get toHourMinute { + return DateFormat('HH:mm', 'id_ID').format(this); + } +} diff --git a/lib/common/extension/extension.dart b/lib/common/extension/extension.dart new file mode 100644 index 0000000..a950fef --- /dev/null +++ b/lib/common/extension/extension.dart @@ -0,0 +1,29 @@ +import 'package:intl/intl.dart'; + +import '../../domain/auth/auth.dart'; + +part 'date_extension.dart'; + +extension StringExt on String { + CheckPhoneStatus toCheckPhoneStatus() { + switch (this) { + case 'NOT_REGISTERED': + return CheckPhoneStatus.notRegistered; + case 'PASSWORD_REQUIRED': + return CheckPhoneStatus.passwordRequired; + default: + return CheckPhoneStatus.unknown; + } + } + + ResendStatus toResendStatus() { + switch (this) { + case 'RESEND_NOT_ALLOWED': + return ResendStatus.resendNotAllowed; + case 'SUCCESS': + return ResendStatus.success; + default: + return ResendStatus.unknown; + } + } +} diff --git a/lib/common/function/app_function.dart b/lib/common/function/app_function.dart new file mode 100644 index 0000000..d140a91 --- /dev/null +++ b/lib/common/function/app_function.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../injection.dart'; +import '../constant/local_storage_key.dart'; + +void dismissKeyboard(BuildContext context) { + final currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) { + FocusManager.instance.primaryFocus?.unfocus(); + } +} + +String getNormalizePhone(String phoneNumber) { + final normalizedPhone = phoneNumber.startsWith('08') + ? phoneNumber.replaceFirst('0', '') + : phoneNumber; + return '62$normalizedPhone'; +} + +Map getAuthorizationHeader() { + return { + 'Authorization': + 'Bearer ${getIt().getString(LocalStorageKey.token)}', + }; +} diff --git a/lib/common/network/network_client.dart b/lib/common/network/network_client.dart new file mode 100644 index 0000000..081a6b5 --- /dev/null +++ b/lib/common/network/network_client.dart @@ -0,0 +1,19 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:injectable/injectable.dart'; + +@lazySingleton +class NetworkClient extends NetworkInfoBase { + final Connectivity connectivity; + + NetworkClient(this.connectivity); + + @override + Future get isConnected async { + final result = await connectivity.checkConnectivity(); + return result.first != ConnectivityResult.none; + } +} + +abstract class NetworkInfoBase { + Future get isConnected; +} diff --git a/lib/common/painter/wheel_painter.dart b/lib/common/painter/wheel_painter.dart new file mode 100644 index 0000000..2415de4 --- /dev/null +++ b/lib/common/painter/wheel_painter.dart @@ -0,0 +1,148 @@ +// wheel_painter.dart - Fixed implementation with consistent positioning +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import '../../domain/game/game.dart'; + +class WheelPainter extends CustomPainter { + final List gamePrizes; + final Color Function(GamePrize prize, int index) getPrizeColor; + + WheelPainter({required this.gamePrizes, required this.getPrizeColor}); + + @override + void paint(Canvas canvas, Size size) { + if (gamePrizes.isEmpty) return; + + final center = Offset(size.width / 2, size.height / 2); + final radius = size.width / 2; + final sectionAngle = 2 * math.pi / gamePrizes.length; + + // Draw outer white border + final outerBorderPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + canvas.drawCircle(center, radius, outerBorderPaint); + + // Draw blue border ring + final blueBorderPaint = Paint() + ..color = const Color(0xFF3B82F6) + ..style = PaintingStyle.fill; + canvas.drawCircle(center, radius - 8, blueBorderPaint); + + // Draw inner white circle + final innerWhitePaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + canvas.drawCircle(center, radius - 20, innerWhitePaint); + + // Draw sections - KONSISTEN dengan logic spin + for (int i = 0; i < gamePrizes.length; i++) { + final prize = gamePrizes[i]; + // Section 0 di top (-π/2), section 1 di kanan atas, dst (clockwise) + final startAngle = (-math.pi / 2) + (i * sectionAngle); + + // Section background + final sectionPaint = Paint() + ..color = getPrizeColor(prize, i) + ..style = PaintingStyle.fill; + + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius - 22), + startAngle, + sectionAngle, + true, + sectionPaint, + ); + + // Draw prize text + final textAngle = startAngle + sectionAngle / 2; + final textPosition = Offset( + center.dx + (radius - 80) * math.cos(textAngle), + center.dy + (radius - 80) * math.sin(textAngle), + ); + + canvas.save(); + canvas.translate(textPosition.dx, textPosition.dy); + canvas.rotate(textAngle + math.pi / 2); + + final textPainter = TextPainter( + text: TextSpan( + text: prize.name, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(maxWidth: 100); + textPainter.paint( + canvas, + Offset(-textPainter.width / 2, -textPainter.height / 2), + ); + canvas.restore(); + + // DEBUG: Draw section number + final numberPosition = Offset( + center.dx + (radius - 50) * math.cos(textAngle), + center.dy + (radius - 50) * math.sin(textAngle), + ); + canvas.drawCircle(numberPosition, 15, Paint()..color = Colors.white); + final numberPainter = TextPainter( + text: TextSpan( + text: i.toString(), + style: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + textDirection: TextDirection.ltr, + ); + numberPainter.layout(); + numberPainter.paint( + canvas, + Offset( + numberPosition.dx - numberPainter.width / 2, + numberPosition.dy - numberPainter.height / 2, + ), + ); + } + + // Draw white dots + final dotPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + for (int i = 0; i < 24; i++) { + final dotAngle = (2 * math.pi / 24) * i; + final dotPosition = Offset( + center.dx + (radius - 14) * math.cos(dotAngle), + center.dy + (radius - 14) * math.sin(dotAngle), + ); + canvas.drawCircle(dotPosition, 3, dotPaint); + } + + // Draw section dividers + final dividerPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + for (int i = 0; i < gamePrizes.length; i++) { + final angle = (-math.pi / 2) + (i * sectionAngle); + final lineStart = Offset( + center.dx + (radius - 110) * math.cos(angle), + center.dy + (radius - 110) * math.sin(angle), + ); + final lineEnd = Offset( + center.dx + (radius - 22) * math.cos(angle), + center.dy + (radius - 22) * math.sin(angle), + ); + canvas.drawLine(lineStart, lineEnd, dividerPaint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/common/theme/app_color.dart b/lib/common/theme/app_color.dart new file mode 100644 index 0000000..1187b9d --- /dev/null +++ b/lib/common/theme/app_color.dart @@ -0,0 +1,66 @@ +part of 'theme.dart'; + +class AppColor { + // Primary Colors (Merah) + static const Color primary = Color.fromARGB(255, 196, 2, 2); // #d90000 + static const Color primaryLight = Color(0xFFFF4D4D); // merah terang + static const Color primaryDark = Color(0xFF990000); // merah gelap + + // Secondary Colors (biar tetap harmonis → hijau dipertahankan) + static const Color secondary = Color(0xFF4CAF50); + static const Color secondaryLight = Color(0xFF81C784); + static const Color secondaryDark = Color(0xFF388E3C); + + // Background Colors + static const Color background = Color(0xFFF8F9FA); + static const Color backgroundLight = Color(0xFFFFFFFF); + static const Color backgroundDark = Color(0xFF1A1A1A); + static const Color surface = Color(0xFFFFFFFF); + static const Color surfaceDark = Color(0xFF2D2D2D); + + // Text Colors + static const Color textPrimary = Color(0xFF212121); + static const Color textSecondary = Color(0xFF757575); + static const Color textLight = Color(0xFFBDBDBD); + static const Color textWhite = Color(0xFFFFFFFF); + + // Status Colors + static const Color success = Color(0xFF4CAF50); + static const Color error = Color(0xFFE53E3E); + static const Color warning = Color(0xFFFF9800); + static const Color info = Color(0xFF2196F3); + + // Border Colors + static const Color border = Color(0xFFE0E0E0); + static const Color borderLight = Color(0xFFF0F0F0); + static const Color borderDark = Color(0xFFBDBDBD); + + // Basic Color + static const Color white = Color(0xFFFFFFFF); + static const Color black = Color(0xFF000000); + + // Gradient Colors + static const List primaryGradient = [ + Color(0xFFD90000), // primary + Color(0xFF990000), // dark red + ]; + + static const List successGradient = [ + Color(0xFF4CAF50), + Color(0xFF81C784), + ]; + + static const List backgroundGradient = [ + Color(0xFFF5F5F5), + Color(0xFFE8E8E8), + ]; + + // Opacity Variations + static Color primaryWithOpacity(double opacity) => + primary.withOpacity(opacity); + static Color successWithOpacity(double opacity) => + success.withOpacity(opacity); + static Color errorWithOpacity(double opacity) => error.withOpacity(opacity); + static Color warningWithOpacity(double opacity) => + warning.withOpacity(opacity); +} diff --git a/lib/common/theme/app_style.dart b/lib/common/theme/app_style.dart new file mode 100644 index 0000000..246e087 --- /dev/null +++ b/lib/common/theme/app_style.dart @@ -0,0 +1,27 @@ +part of 'theme.dart'; + +class AppStyle { + static TextStyle xs = TextStyle(color: AppColor.textPrimary, fontSize: 11); + + static TextStyle sm = TextStyle(color: AppColor.textPrimary, fontSize: 12); + + static TextStyle md = TextStyle(color: AppColor.textPrimary, fontSize: 14); + + static TextStyle lg = TextStyle(color: AppColor.textPrimary, fontSize: 16); + + static TextStyle xl = TextStyle(color: AppColor.textPrimary, fontSize: 18); + + static TextStyle xxl = TextStyle(color: AppColor.textPrimary, fontSize: 20); + + static TextStyle h6 = TextStyle(color: AppColor.textPrimary, fontSize: 22); + + static TextStyle h5 = TextStyle(color: AppColor.textPrimary, fontSize: 24); + + static TextStyle h4 = TextStyle(color: AppColor.textPrimary, fontSize: 26); + + static TextStyle h3 = TextStyle(color: AppColor.textPrimary, fontSize: 28); + + static TextStyle h2 = TextStyle(color: AppColor.textPrimary, fontSize: 30); + + static TextStyle h1 = TextStyle(color: AppColor.textPrimary, fontSize: 32); +} diff --git a/lib/common/theme/app_value.dart b/lib/common/theme/app_value.dart new file mode 100644 index 0000000..8a1c7a3 --- /dev/null +++ b/lib/common/theme/app_value.dart @@ -0,0 +1,3 @@ +part of 'theme.dart'; + +class AppValue {} diff --git a/lib/common/theme/theme.dart b/lib/common/theme/theme.dart new file mode 100644 index 0000000..9f00497 --- /dev/null +++ b/lib/common/theme/theme.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +import '../../presentation/components/assets/fonts.gen.dart'; + +part 'app_color.dart'; +part 'app_style.dart'; +part 'app_value.dart'; + +UnderlineInputBorder _inputBorder = UnderlineInputBorder( + borderSide: BorderSide(color: AppColor.borderDark, width: 1), +); + +class ThemeApp { + static ThemeData get theme => ThemeData( + useMaterial3: true, + fontFamily: FontFamily.quicksand, + primaryColor: AppColor.primary, + scaffoldBackgroundColor: AppColor.white, + datePickerTheme: DatePickerThemeData( + backgroundColor: AppColor.white, + todayBackgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.selected)) { + return AppColor.primary; // warna background tanggal terpilih + } + return null; // default + }), + todayBorder: BorderSide(color: AppColor.primary, width: 1), + dayBackgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.selected)) { + return AppColor.primary; // warna background tanggal terpilih + } + return null; // default + }), + dayForegroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.selected)) { + return AppColor.white; // warna text tanggal terpilih + } + return null; // default + }), + ), + appBarTheme: AppBarTheme( + backgroundColor: AppColor.white, + foregroundColor: AppColor.textPrimary, + elevation: 0, + titleTextStyle: AppStyle.xl.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + centerTitle: true, + iconTheme: IconThemeData(color: AppColor.primary), + scrolledUnderElevation: 0.0, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + ), + ), + inputDecorationTheme: InputDecorationTheme( + border: _inputBorder, + focusedBorder: _inputBorder.copyWith( + borderSide: BorderSide(color: AppColor.primary, width: 2), + ), + enabledBorder: _inputBorder, + disabledBorder: _inputBorder.copyWith( + borderSide: BorderSide(color: AppColor.border), + ), + errorBorder: _inputBorder.copyWith( + borderSide: BorderSide(color: AppColor.error), + ), + hintStyle: AppStyle.md.copyWith( + color: AppColor.textLight, + fontWeight: FontWeight.w500, + ), + contentPadding: const EdgeInsets.symmetric(vertical: 12), + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + type: BottomNavigationBarType.fixed, + selectedItemColor: AppColor.primary, + unselectedItemColor: AppColor.textSecondary, + backgroundColor: AppColor.white, + elevation: 4, + ), + tabBarTheme: TabBarThemeData( + indicatorColor: AppColor.primary, + labelColor: AppColor.primary, + unselectedLabelColor: AppColor.textSecondary, + ), + ); +} diff --git a/lib/common/ui/clipper/voucher_clipper.dart b/lib/common/ui/clipper/voucher_clipper.dart new file mode 100644 index 0000000..5336e7d --- /dev/null +++ b/lib/common/ui/clipper/voucher_clipper.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class VoucherClipper extends CustomClipper { + @override + Path getClip(Size size) { + final path = Path(); + double notchRadius = 10.0; + double notchPosition = size.height * 0.6; // Position of notch + + // Start from top-left + path.moveTo(0, 12); + + // Top edge with rounded corners + path.quadraticBezierTo(0, 0, 12, 0); + path.lineTo(size.width - 12, 0); + path.quadraticBezierTo(size.width, 0, size.width, 12); + + // Right edge until notch + path.lineTo(size.width, notchPosition - notchRadius); + + // Right notch (semicircle going inward) + path.arcToPoint( + Offset(size.width, notchPosition + notchRadius), + radius: Radius.circular(notchRadius), + clockwise: false, + ); + + // Continue right edge + path.lineTo(size.width, size.height - 12); + + // Bottom edge + path.quadraticBezierTo( + size.width, + size.height, + size.width - 12, + size.height, + ); + path.lineTo(12, size.height); + path.quadraticBezierTo(0, size.height, 0, size.height - 12); + + // Left edge until notch + path.lineTo(0, notchPosition + notchRadius); + + // Left notch (semicircle going inward) + path.arcToPoint( + Offset(0, notchPosition - notchRadius), + radius: Radius.circular(notchRadius), + clockwise: false, + ); + + // Close path + path.close(); + + return path; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} diff --git a/lib/common/ui/painter/dashed_line_painter.dart b/lib/common/ui/painter/dashed_line_painter.dart new file mode 100644 index 0000000..5799a9d --- /dev/null +++ b/lib/common/ui/painter/dashed_line_painter.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class DashedLinePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = Colors.grey[300]! + ..strokeWidth = 1 + ..style = PaintingStyle.stroke; + + double dashWidth = 5; + double dashSpace = 3; + double startX = 0; + + while (startX < size.width) { + canvas.drawLine( + Offset(startX, size.height / 2), + Offset(startX + dashWidth, size.height / 2), + paint, + ); + startX += dashWidth + dashSpace; + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart new file mode 100644 index 0000000..cf51d59 --- /dev/null +++ b/lib/common/url/api_path.dart @@ -0,0 +1,13 @@ +class ApiPath { + static String checkPhone = '/api/v1/customer-auth/check-phone'; + static String register = '/api/v1/customer-auth/register/start'; + static String verify = '/api/v1/customer-auth/register/verify-otp'; + static String setPassword = '/api/v1/customer-auth/register/set-password'; + static String login = '/api/v1/customer-auth/login'; + static String resend = '/api/v1/customer-auth/resend-otp'; + + // Marketing + static String ferrisWheel = '/api/v1/customer/ferris-wheel'; + // Customer + static String customerPoint = '/api/v1/customer/points'; +} diff --git a/lib/domain/auth/auth.dart b/lib/domain/auth/auth.dart new file mode 100644 index 0000000..50cc7bd --- /dev/null +++ b/lib/domain/auth/auth.dart @@ -0,0 +1,40 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:dartz/dartz.dart'; + +import '../../common/api/api_failure.dart'; + +part 'auth.freezed.dart'; + +part 'entities/check_phone_entity.dart'; +part 'entities/register_entity.dart'; +part 'entities/verify_entity.dart'; +part 'entities/login_entity.dart'; +part 'entities/resend_entity.dart'; +part 'failures/auth_failure.dart'; +part 'repositories/i_auth_repository.dart'; + +enum CheckPhoneStatus { notRegistered, passwordRequired, unknown } + +extension CheckPhoneStatusX on CheckPhoneStatus { + String toStringType() => switch (this) { + CheckPhoneStatus.notRegistered => 'NOT_REGISTERED', + CheckPhoneStatus.passwordRequired => 'PASSWORD_REQUIRED', + CheckPhoneStatus.unknown => '', + }; + + bool get isNotRegistered => this == CheckPhoneStatus.notRegistered; + bool get isPasswordRequired => this == CheckPhoneStatus.passwordRequired; +} + +enum ResendStatus { resendNotAllowed, success, unknown } + +extension ResendStatusX on ResendStatus { + String toStringType() => switch (this) { + ResendStatus.resendNotAllowed => 'RESEND_NOT_ALLOWED', + ResendStatus.success => 'SUCCESS', + ResendStatus.unknown => '', + }; + + bool get isResendNotAllowed => this == ResendStatus.resendNotAllowed; + bool get isSuccess => this == ResendStatus.success; +} diff --git a/lib/domain/auth/auth.freezed.dart b/lib/domain/auth/auth.freezed.dart new file mode 100644 index 0000000..a9aa594 --- /dev/null +++ b/lib/domain/auth/auth.freezed.dart @@ -0,0 +1,1754 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$CheckPhone { + CheckPhoneStatus get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String get phoneNumber => throw _privateConstructorUsedError; + + /// Create a copy of CheckPhone + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CheckPhoneCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CheckPhoneCopyWith<$Res> { + factory $CheckPhoneCopyWith( + CheckPhone value, + $Res Function(CheckPhone) then, + ) = _$CheckPhoneCopyWithImpl<$Res, CheckPhone>; + @useResult + $Res call({CheckPhoneStatus status, String message, String phoneNumber}); +} + +/// @nodoc +class _$CheckPhoneCopyWithImpl<$Res, $Val extends CheckPhone> + implements $CheckPhoneCopyWith<$Res> { + _$CheckPhoneCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CheckPhone + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? phoneNumber = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as CheckPhoneStatus, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CheckPhoneImplCopyWith<$Res> + implements $CheckPhoneCopyWith<$Res> { + factory _$$CheckPhoneImplCopyWith( + _$CheckPhoneImpl value, + $Res Function(_$CheckPhoneImpl) then, + ) = __$$CheckPhoneImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({CheckPhoneStatus status, String message, String phoneNumber}); +} + +/// @nodoc +class __$$CheckPhoneImplCopyWithImpl<$Res> + extends _$CheckPhoneCopyWithImpl<$Res, _$CheckPhoneImpl> + implements _$$CheckPhoneImplCopyWith<$Res> { + __$$CheckPhoneImplCopyWithImpl( + _$CheckPhoneImpl _value, + $Res Function(_$CheckPhoneImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhone + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? phoneNumber = null, + }) { + return _then( + _$CheckPhoneImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as CheckPhoneStatus, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$CheckPhoneImpl implements _CheckPhone { + const _$CheckPhoneImpl({ + required this.status, + required this.message, + required this.phoneNumber, + }); + + @override + final CheckPhoneStatus status; + @override + final String message; + @override + final String phoneNumber; + + @override + String toString() { + return 'CheckPhone(status: $status, message: $message, phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CheckPhoneImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, message, phoneNumber); + + /// Create a copy of CheckPhone + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CheckPhoneImplCopyWith<_$CheckPhoneImpl> get copyWith => + __$$CheckPhoneImplCopyWithImpl<_$CheckPhoneImpl>(this, _$identity); +} + +abstract class _CheckPhone implements CheckPhone { + const factory _CheckPhone({ + required final CheckPhoneStatus status, + required final String message, + required final String phoneNumber, + }) = _$CheckPhoneImpl; + + @override + CheckPhoneStatus get status; + @override + String get message; + @override + String get phoneNumber; + + /// Create a copy of CheckPhone + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CheckPhoneImplCopyWith<_$CheckPhoneImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Register { + String get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String get registrationToken => throw _privateConstructorUsedError; + String get otpToken => throw _privateConstructorUsedError; + int get expiresIn => throw _privateConstructorUsedError; + + /// Create a copy of Register + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RegisterCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RegisterCopyWith<$Res> { + factory $RegisterCopyWith(Register value, $Res Function(Register) then) = + _$RegisterCopyWithImpl<$Res, Register>; + @useResult + $Res call({ + String status, + String message, + String registrationToken, + String otpToken, + int expiresIn, + }); +} + +/// @nodoc +class _$RegisterCopyWithImpl<$Res, $Val extends Register> + implements $RegisterCopyWith<$Res> { + _$RegisterCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Register + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? registrationToken = null, + Object? otpToken = null, + Object? expiresIn = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + otpToken: null == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$RegisterImplCopyWith<$Res> + implements $RegisterCopyWith<$Res> { + factory _$$RegisterImplCopyWith( + _$RegisterImpl value, + $Res Function(_$RegisterImpl) then, + ) = __$$RegisterImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String status, + String message, + String registrationToken, + String otpToken, + int expiresIn, + }); +} + +/// @nodoc +class __$$RegisterImplCopyWithImpl<$Res> + extends _$RegisterCopyWithImpl<$Res, _$RegisterImpl> + implements _$$RegisterImplCopyWith<$Res> { + __$$RegisterImplCopyWithImpl( + _$RegisterImpl _value, + $Res Function(_$RegisterImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Register + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? registrationToken = null, + Object? otpToken = null, + Object? expiresIn = null, + }) { + return _then( + _$RegisterImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + otpToken: null == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$RegisterImpl implements _Register { + const _$RegisterImpl({ + required this.status, + required this.message, + required this.registrationToken, + required this.otpToken, + required this.expiresIn, + }); + + @override + final String status; + @override + final String message; + @override + final String registrationToken; + @override + final String otpToken; + @override + final int expiresIn; + + @override + String toString() { + return 'Register(status: $status, message: $message, registrationToken: $registrationToken, otpToken: $otpToken, expiresIn: $expiresIn)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegisterImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken) && + (identical(other.otpToken, otpToken) || + other.otpToken == otpToken) && + (identical(other.expiresIn, expiresIn) || + other.expiresIn == expiresIn)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + status, + message, + registrationToken, + otpToken, + expiresIn, + ); + + /// Create a copy of Register + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegisterImplCopyWith<_$RegisterImpl> get copyWith => + __$$RegisterImplCopyWithImpl<_$RegisterImpl>(this, _$identity); +} + +abstract class _Register implements Register { + const factory _Register({ + required final String status, + required final String message, + required final String registrationToken, + required final String otpToken, + required final int expiresIn, + }) = _$RegisterImpl; + + @override + String get status; + @override + String get message; + @override + String get registrationToken; + @override + String get otpToken; + @override + int get expiresIn; + + /// Create a copy of Register + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegisterImplCopyWith<_$RegisterImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Verify { + String get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String get registrationToken => throw _privateConstructorUsedError; + + /// Create a copy of Verify + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VerifyCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyCopyWith<$Res> { + factory $VerifyCopyWith(Verify value, $Res Function(Verify) then) = + _$VerifyCopyWithImpl<$Res, Verify>; + @useResult + $Res call({String status, String message, String registrationToken}); +} + +/// @nodoc +class _$VerifyCopyWithImpl<$Res, $Val extends Verify> + implements $VerifyCopyWith<$Res> { + _$VerifyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Verify + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? registrationToken = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$VerifyImplCopyWith<$Res> implements $VerifyCopyWith<$Res> { + factory _$$VerifyImplCopyWith( + _$VerifyImpl value, + $Res Function(_$VerifyImpl) then, + ) = __$$VerifyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String status, String message, String registrationToken}); +} + +/// @nodoc +class __$$VerifyImplCopyWithImpl<$Res> + extends _$VerifyCopyWithImpl<$Res, _$VerifyImpl> + implements _$$VerifyImplCopyWith<$Res> { + __$$VerifyImplCopyWithImpl( + _$VerifyImpl _value, + $Res Function(_$VerifyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Verify + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? registrationToken = null, + }) { + return _then( + _$VerifyImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + registrationToken: null == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$VerifyImpl implements _Verify { + const _$VerifyImpl({ + required this.status, + required this.message, + required this.registrationToken, + }); + + @override + final String status; + @override + final String message; + @override + final String registrationToken; + + @override + String toString() { + return 'Verify(status: $status, message: $message, registrationToken: $registrationToken)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VerifyImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, message, registrationToken); + + /// Create a copy of Verify + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VerifyImplCopyWith<_$VerifyImpl> get copyWith => + __$$VerifyImplCopyWithImpl<_$VerifyImpl>(this, _$identity); +} + +abstract class _Verify implements Verify { + const factory _Verify({ + required final String status, + required final String message, + required final String registrationToken, + }) = _$VerifyImpl; + + @override + String get status; + @override + String get message; + @override + String get registrationToken; + + /// Create a copy of Verify + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VerifyImplCopyWith<_$VerifyImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Login { + String get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String get accessToken => throw _privateConstructorUsedError; + String get refreshToken => throw _privateConstructorUsedError; + User get user => throw _privateConstructorUsedError; + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginCopyWith<$Res> { + factory $LoginCopyWith(Login value, $Res Function(Login) then) = + _$LoginCopyWithImpl<$Res, Login>; + @useResult + $Res call({ + String status, + String message, + String accessToken, + String refreshToken, + User user, + }); + + $UserCopyWith<$Res> get user; +} + +/// @nodoc +class _$LoginCopyWithImpl<$Res, $Val extends Login> + implements $LoginCopyWith<$Res> { + _$LoginCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? accessToken = null, + Object? refreshToken = null, + Object? user = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + accessToken: null == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String, + refreshToken: null == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + ) + as $Val, + ); + } + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserCopyWith<$Res> get user { + return $UserCopyWith<$Res>(_value.user, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$LoginImplCopyWith<$Res> implements $LoginCopyWith<$Res> { + factory _$$LoginImplCopyWith( + _$LoginImpl value, + $Res Function(_$LoginImpl) then, + ) = __$$LoginImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String status, + String message, + String accessToken, + String refreshToken, + User user, + }); + + @override + $UserCopyWith<$Res> get user; +} + +/// @nodoc +class __$$LoginImplCopyWithImpl<$Res> + extends _$LoginCopyWithImpl<$Res, _$LoginImpl> + implements _$$LoginImplCopyWith<$Res> { + __$$LoginImplCopyWithImpl( + _$LoginImpl _value, + $Res Function(_$LoginImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? accessToken = null, + Object? refreshToken = null, + Object? user = null, + }) { + return _then( + _$LoginImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + accessToken: null == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String, + refreshToken: null == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String, + user: null == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as User, + ), + ); + } +} + +/// @nodoc + +class _$LoginImpl implements _Login { + const _$LoginImpl({ + required this.status, + required this.message, + required this.accessToken, + required this.refreshToken, + required this.user, + }); + + @override + final String status; + @override + final String message; + @override + final String accessToken; + @override + final String refreshToken; + @override + final User user; + + @override + String toString() { + return 'Login(status: $status, message: $message, accessToken: $accessToken, refreshToken: $refreshToken, user: $user)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.accessToken, accessToken) || + other.accessToken == accessToken) && + (identical(other.refreshToken, refreshToken) || + other.refreshToken == refreshToken) && + (identical(other.user, user) || other.user == user)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + status, + message, + accessToken, + refreshToken, + user, + ); + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginImplCopyWith<_$LoginImpl> get copyWith => + __$$LoginImplCopyWithImpl<_$LoginImpl>(this, _$identity); +} + +abstract class _Login implements Login { + const factory _Login({ + required final String status, + required final String message, + required final String accessToken, + required final String refreshToken, + required final User user, + }) = _$LoginImpl; + + @override + String get status; + @override + String get message; + @override + String get accessToken; + @override + String get refreshToken; + @override + User get user; + + /// Create a copy of Login + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginImplCopyWith<_$LoginImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$User { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get phoneNumber => throw _privateConstructorUsedError; + String get birthDate => throw _privateConstructorUsedError; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserCopyWith<$Res> { + factory $UserCopyWith(User value, $Res Function(User) then) = + _$UserCopyWithImpl<$Res, User>; + @useResult + $Res call({String id, String name, String phoneNumber, String birthDate}); +} + +/// @nodoc +class _$UserCopyWithImpl<$Res, $Val extends User> + implements $UserCopyWith<$Res> { + _$UserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? phoneNumber = null, + Object? birthDate = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + birthDate: null == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> { + factory _$$UserImplCopyWith( + _$UserImpl value, + $Res Function(_$UserImpl) then, + ) = __$$UserImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String phoneNumber, String birthDate}); +} + +/// @nodoc +class __$$UserImplCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$UserImpl> + implements _$$UserImplCopyWith<$Res> { + __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then) + : super(_value, _then); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? phoneNumber = null, + Object? birthDate = null, + }) { + return _then( + _$UserImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + birthDate: null == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$UserImpl implements _User { + const _$UserImpl({ + required this.id, + required this.name, + required this.phoneNumber, + required this.birthDate, + }); + + @override + final String id; + @override + final String name; + @override + final String phoneNumber; + @override + final String birthDate; + + @override + String toString() { + return 'User(id: $id, name: $name, phoneNumber: $phoneNumber, birthDate: $birthDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.birthDate, birthDate) || + other.birthDate == birthDate)); + } + + @override + int get hashCode => + Object.hash(runtimeType, id, name, phoneNumber, birthDate); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserImplCopyWith<_$UserImpl> get copyWith => + __$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity); +} + +abstract class _User implements User { + const factory _User({ + required final String id, + required final String name, + required final String phoneNumber, + required final String birthDate, + }) = _$UserImpl; + + @override + String get id; + @override + String get name; + @override + String get phoneNumber; + @override + String get birthDate; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserImplCopyWith<_$UserImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Resend { + ResendStatus get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + String get otpToken => throw _privateConstructorUsedError; + int get expiresIn => throw _privateConstructorUsedError; + int get nextResendIn => throw _privateConstructorUsedError; + + /// Create a copy of Resend + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ResendCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResendCopyWith<$Res> { + factory $ResendCopyWith(Resend value, $Res Function(Resend) then) = + _$ResendCopyWithImpl<$Res, Resend>; + @useResult + $Res call({ + ResendStatus status, + String message, + String otpToken, + int expiresIn, + int nextResendIn, + }); +} + +/// @nodoc +class _$ResendCopyWithImpl<$Res, $Val extends Resend> + implements $ResendCopyWith<$Res> { + _$ResendCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Resend + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? otpToken = null, + Object? expiresIn = null, + Object? nextResendIn = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as ResendStatus, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + otpToken: null == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int, + nextResendIn: null == nextResendIn + ? _value.nextResendIn + : nextResendIn // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ResendImplCopyWith<$Res> implements $ResendCopyWith<$Res> { + factory _$$ResendImplCopyWith( + _$ResendImpl value, + $Res Function(_$ResendImpl) then, + ) = __$$ResendImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + ResendStatus status, + String message, + String otpToken, + int expiresIn, + int nextResendIn, + }); +} + +/// @nodoc +class __$$ResendImplCopyWithImpl<$Res> + extends _$ResendCopyWithImpl<$Res, _$ResendImpl> + implements _$$ResendImplCopyWith<$Res> { + __$$ResendImplCopyWithImpl( + _$ResendImpl _value, + $Res Function(_$ResendImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Resend + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? otpToken = null, + Object? expiresIn = null, + Object? nextResendIn = null, + }) { + return _then( + _$ResendImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as ResendStatus, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + otpToken: null == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String, + expiresIn: null == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int, + nextResendIn: null == nextResendIn + ? _value.nextResendIn + : nextResendIn // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$ResendImpl implements _Resend { + const _$ResendImpl({ + required this.status, + required this.message, + required this.otpToken, + required this.expiresIn, + required this.nextResendIn, + }); + + @override + final ResendStatus status; + @override + final String message; + @override + final String otpToken; + @override + final int expiresIn; + @override + final int nextResendIn; + + @override + String toString() { + return 'Resend(status: $status, message: $message, otpToken: $otpToken, expiresIn: $expiresIn, nextResendIn: $nextResendIn)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ResendImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.otpToken, otpToken) || + other.otpToken == otpToken) && + (identical(other.expiresIn, expiresIn) || + other.expiresIn == expiresIn) && + (identical(other.nextResendIn, nextResendIn) || + other.nextResendIn == nextResendIn)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + status, + message, + otpToken, + expiresIn, + nextResendIn, + ); + + /// Create a copy of Resend + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ResendImplCopyWith<_$ResendImpl> get copyWith => + __$$ResendImplCopyWithImpl<_$ResendImpl>(this, _$identity); +} + +abstract class _Resend implements Resend { + const factory _Resend({ + required final ResendStatus status, + required final String message, + required final String otpToken, + required final int expiresIn, + required final int nextResendIn, + }) = _$ResendImpl; + + @override + ResendStatus get status; + @override + String get message; + @override + String get otpToken; + @override + int get expiresIn; + @override + int get nextResendIn; + + /// Create a copy of Resend + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ResendImplCopyWith<_$ResendImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$AuthFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthFailureCopyWith<$Res> { + factory $AuthFailureCopyWith( + AuthFailure value, + $Res Function(AuthFailure) then, + ) = _$AuthFailureCopyWithImpl<$Res, AuthFailure>; +} + +/// @nodoc +class _$AuthFailureCopyWithImpl<$Res, $Val extends AuthFailure> + implements $AuthFailureCopyWith<$Res> { + _$AuthFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$AuthFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'AuthFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements AuthFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$AuthFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'AuthFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements AuthFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$AuthFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'AuthFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements AuthFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of AuthFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/auth/entities/check_phone_entity.dart b/lib/domain/auth/entities/check_phone_entity.dart new file mode 100644 index 0000000..cfb6509 --- /dev/null +++ b/lib/domain/auth/entities/check_phone_entity.dart @@ -0,0 +1,16 @@ +part of '../auth.dart'; + +@freezed +class CheckPhone with _$CheckPhone { + const factory CheckPhone({ + required CheckPhoneStatus status, + required String message, + required String phoneNumber, + }) = _CheckPhone; + + factory CheckPhone.empty() => CheckPhone( + status: CheckPhoneStatus.unknown, + message: '', + phoneNumber: '', + ); +} diff --git a/lib/domain/auth/entities/login_entity.dart b/lib/domain/auth/entities/login_entity.dart new file mode 100644 index 0000000..0d869b1 --- /dev/null +++ b/lib/domain/auth/entities/login_entity.dart @@ -0,0 +1,33 @@ +part of '../auth.dart'; + +@freezed +class Login with _$Login { + const factory Login({ + required String status, + required String message, + required String accessToken, + required String refreshToken, + required User user, + }) = _Login; + + factory Login.empty() => Login( + status: '', + message: '', + accessToken: '', + refreshToken: '', + user: User.empty(), + ); +} + +@freezed +class User with _$User { + const factory User({ + required String id, + required String name, + required String phoneNumber, + required String birthDate, + }) = _User; + + factory User.empty() => + const User(id: '', name: '', phoneNumber: '', birthDate: ''); +} diff --git a/lib/domain/auth/entities/register_entity.dart b/lib/domain/auth/entities/register_entity.dart new file mode 100644 index 0000000..d441c20 --- /dev/null +++ b/lib/domain/auth/entities/register_entity.dart @@ -0,0 +1,20 @@ +part of '../auth.dart'; + +@freezed +class Register with _$Register { + const factory Register({ + required String status, + required String message, + required String registrationToken, + required String otpToken, + required int expiresIn, + }) = _Register; + + factory Register.empty() => const Register( + status: '', + message: '', + registrationToken: '', + otpToken: '', + expiresIn: 0, + ); +} diff --git a/lib/domain/auth/entities/resend_entity.dart b/lib/domain/auth/entities/resend_entity.dart new file mode 100644 index 0000000..cd77206 --- /dev/null +++ b/lib/domain/auth/entities/resend_entity.dart @@ -0,0 +1,20 @@ +part of '../auth.dart'; + +@freezed +class Resend with _$Resend { + const factory Resend({ + required ResendStatus status, + required String message, + required String otpToken, + required int expiresIn, + required int nextResendIn, + }) = _Resend; + + factory Resend.empty() => Resend( + status: ResendStatus.unknown, + message: '', + otpToken: '', + expiresIn: 0, + nextResendIn: 0, + ); +} diff --git a/lib/domain/auth/entities/verify_entity.dart b/lib/domain/auth/entities/verify_entity.dart new file mode 100644 index 0000000..cef23e8 --- /dev/null +++ b/lib/domain/auth/entities/verify_entity.dart @@ -0,0 +1,13 @@ +part of '../auth.dart'; + +@freezed +class Verify with _$Verify { + const factory Verify({ + required String status, + required String message, + required String registrationToken, + }) = _Verify; + + factory Verify.empty() => + const Verify(status: '', message: '', registrationToken: ''); +} diff --git a/lib/domain/auth/failures/auth_failure.dart b/lib/domain/auth/failures/auth_failure.dart new file mode 100644 index 0000000..bf304fd --- /dev/null +++ b/lib/domain/auth/failures/auth_failure.dart @@ -0,0 +1,9 @@ +part of '../auth.dart'; + +@freezed +sealed class AuthFailure with _$AuthFailure { + const factory AuthFailure.serverError(ApiFailure failure) = _ServerError; + const factory AuthFailure.unexpectedError() = _UnexpectedError; + const factory AuthFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/auth/repositories/i_auth_repository.dart b/lib/domain/auth/repositories/i_auth_repository.dart new file mode 100644 index 0000000..5d50e11 --- /dev/null +++ b/lib/domain/auth/repositories/i_auth_repository.dart @@ -0,0 +1,40 @@ +part of '../auth.dart'; + +abstract class IAuthRepository { + Future> checkPhone({ + required String phoneNumber, + }); + + Future> register({ + required String phoneNumber, + required String name, + required DateTime birthDate, + }); + + Future> verify({ + required String registrationToken, + required String otpCode, + }); + + Future> setPassword({ + required String registrationToken, + required String password, + required String confirmPassword, + }); + + Future> login({ + required String phoneNumber, + required String password, + }); + + Future> resend({ + required String phoneNumber, + required String purpose, + }); + + Future hasToken(); + + Future> currentUser(); + + Future> logout(); +} diff --git a/lib/domain/customer/customer.dart b/lib/domain/customer/customer.dart new file mode 100644 index 0000000..571a2d6 --- /dev/null +++ b/lib/domain/customer/customer.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'customer.freezed.dart'; + +part 'entities/customer_point_entity.dart'; +part 'failures/customer_failures.dart'; +part 'repositories/i_customer_repository.dart'; diff --git a/lib/domain/customer/customer.freezed.dart b/lib/domain/customer/customer.freezed.dart new file mode 100644 index 0000000..6ff1ab3 --- /dev/null +++ b/lib/domain/customer/customer.freezed.dart @@ -0,0 +1,713 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'customer.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$CustomerPoint { + String get status => throw _privateConstructorUsedError; + String get message => throw _privateConstructorUsedError; + int get totalPoints => throw _privateConstructorUsedError; + String get lastUpdated => throw _privateConstructorUsedError; + + /// Create a copy of CustomerPoint + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerPointCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerPointCopyWith<$Res> { + factory $CustomerPointCopyWith( + CustomerPoint value, + $Res Function(CustomerPoint) then, + ) = _$CustomerPointCopyWithImpl<$Res, CustomerPoint>; + @useResult + $Res call({ + String status, + String message, + int totalPoints, + String lastUpdated, + }); +} + +/// @nodoc +class _$CustomerPointCopyWithImpl<$Res, $Val extends CustomerPoint> + implements $CustomerPointCopyWith<$Res> { + _$CustomerPointCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerPoint + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? totalPoints = null, + Object? lastUpdated = null, + }) { + return _then( + _value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + totalPoints: null == totalPoints + ? _value.totalPoints + : totalPoints // ignore: cast_nullable_to_non_nullable + as int, + lastUpdated: null == lastUpdated + ? _value.lastUpdated + : lastUpdated // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CustomerPointImplCopyWith<$Res> + implements $CustomerPointCopyWith<$Res> { + factory _$$CustomerPointImplCopyWith( + _$CustomerPointImpl value, + $Res Function(_$CustomerPointImpl) then, + ) = __$$CustomerPointImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String status, + String message, + int totalPoints, + String lastUpdated, + }); +} + +/// @nodoc +class __$$CustomerPointImplCopyWithImpl<$Res> + extends _$CustomerPointCopyWithImpl<$Res, _$CustomerPointImpl> + implements _$$CustomerPointImplCopyWith<$Res> { + __$$CustomerPointImplCopyWithImpl( + _$CustomerPointImpl _value, + $Res Function(_$CustomerPointImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerPoint + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? message = null, + Object? totalPoints = null, + Object? lastUpdated = null, + }) { + return _then( + _$CustomerPointImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + totalPoints: null == totalPoints + ? _value.totalPoints + : totalPoints // ignore: cast_nullable_to_non_nullable + as int, + lastUpdated: null == lastUpdated + ? _value.lastUpdated + : lastUpdated // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$CustomerPointImpl implements _CustomerPoint { + const _$CustomerPointImpl({ + required this.status, + required this.message, + required this.totalPoints, + required this.lastUpdated, + }); + + @override + final String status; + @override + final String message; + @override + final int totalPoints; + @override + final String lastUpdated; + + @override + String toString() { + return 'CustomerPoint(status: $status, message: $message, totalPoints: $totalPoints, lastUpdated: $lastUpdated)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CustomerPointImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.totalPoints, totalPoints) || + other.totalPoints == totalPoints) && + (identical(other.lastUpdated, lastUpdated) || + other.lastUpdated == lastUpdated)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, message, totalPoints, lastUpdated); + + /// Create a copy of CustomerPoint + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CustomerPointImplCopyWith<_$CustomerPointImpl> get copyWith => + __$$CustomerPointImplCopyWithImpl<_$CustomerPointImpl>(this, _$identity); +} + +abstract class _CustomerPoint implements CustomerPoint { + const factory _CustomerPoint({ + required final String status, + required final String message, + required final int totalPoints, + required final String lastUpdated, + }) = _$CustomerPointImpl; + + @override + String get status; + @override + String get message; + @override + int get totalPoints; + @override + String get lastUpdated; + + /// Create a copy of CustomerPoint + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CustomerPointImplCopyWith<_$CustomerPointImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$CustomerFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerFailureCopyWith<$Res> { + factory $CustomerFailureCopyWith( + CustomerFailure value, + $Res Function(CustomerFailure) then, + ) = _$CustomerFailureCopyWithImpl<$Res, CustomerFailure>; +} + +/// @nodoc +class _$CustomerFailureCopyWithImpl<$Res, $Val extends CustomerFailure> + implements $CustomerFailureCopyWith<$Res> { + _$CustomerFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$CustomerFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'CustomerFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements CustomerFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$CustomerFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'CustomerFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements CustomerFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$CustomerFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'CustomerFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements CustomerFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of CustomerFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/customer/entities/customer_point_entity.dart b/lib/domain/customer/entities/customer_point_entity.dart new file mode 100644 index 0000000..fc1750e --- /dev/null +++ b/lib/domain/customer/entities/customer_point_entity.dart @@ -0,0 +1,18 @@ +part of '../customer.dart'; + +@freezed +class CustomerPoint with _$CustomerPoint { + const factory CustomerPoint({ + required String status, + required String message, + required int totalPoints, + required String lastUpdated, + }) = _CustomerPoint; + + factory CustomerPoint.empty() => const CustomerPoint( + status: '', + message: '', + totalPoints: 0, + lastUpdated: '', + ); +} diff --git a/lib/domain/customer/failures/customer_failures.dart b/lib/domain/customer/failures/customer_failures.dart new file mode 100644 index 0000000..e4459ba --- /dev/null +++ b/lib/domain/customer/failures/customer_failures.dart @@ -0,0 +1,9 @@ +part of '../customer.dart'; + +@freezed +sealed class CustomerFailure with _$CustomerFailure { + const factory CustomerFailure.serverError(ApiFailure failure) = _ServerError; + const factory CustomerFailure.unexpectedError() = _UnexpectedError; + const factory CustomerFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/customer/repositories/i_customer_repository.dart b/lib/domain/customer/repositories/i_customer_repository.dart new file mode 100644 index 0000000..618a473 --- /dev/null +++ b/lib/domain/customer/repositories/i_customer_repository.dart @@ -0,0 +1,5 @@ +part of '../customer.dart'; + +abstract class ICustomerRepository { + Future> getPoints(); +} diff --git a/lib/domain/game/entity/game_entity.dart b/lib/domain/game/entity/game_entity.dart new file mode 100644 index 0000000..5f56b9d --- /dev/null +++ b/lib/domain/game/entity/game_entity.dart @@ -0,0 +1,26 @@ +part of '../game.dart'; + +@freezed +class Game with _$Game { + const factory Game({ + required String id, + required String name, + required String type, + required bool isActive, + required Map metadata, + required List prizes, + required String createdAt, + required String updatedAt, + }) = _Game; + + factory Game.empty() => const Game( + id: '', + name: '', + type: '', + isActive: false, + metadata: {}, + prizes: [], + createdAt: '', + updatedAt: '', + ); +} diff --git a/lib/domain/game/entity/game_prize_entity.dart b/lib/domain/game/entity/game_prize_entity.dart new file mode 100644 index 0000000..1d32834 --- /dev/null +++ b/lib/domain/game/entity/game_prize_entity.dart @@ -0,0 +1,22 @@ +part of '../game.dart'; + +@freezed +class GamePrize with _$GamePrize { + const factory GamePrize({ + required String id, + required String gameId, + required String name, + required Map metadata, + required String createdAt, + required String updatedAt, + }) = _GamePrize; + + factory GamePrize.empty() => const GamePrize( + id: '', + gameId: '', + name: '', + metadata: {}, + createdAt: '', + updatedAt: '', + ); +} diff --git a/lib/domain/game/failures/game_failure.dart b/lib/domain/game/failures/game_failure.dart new file mode 100644 index 0000000..ea61756 --- /dev/null +++ b/lib/domain/game/failures/game_failure.dart @@ -0,0 +1,9 @@ +part of '../game.dart'; + +@freezed +sealed class GameFailure with _$GameFailure { + const factory GameFailure.serverError(ApiFailure failure) = _ServerError; + const factory GameFailure.unexpectedError() = _UnexpectedError; + const factory GameFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/game/game.dart b/lib/domain/game/game.dart new file mode 100644 index 0000000..af0911b --- /dev/null +++ b/lib/domain/game/game.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'game.freezed.dart'; + +part 'entity/game_entity.dart'; +part 'entity/game_prize_entity.dart'; +part 'failures/game_failure.dart'; +part 'repositories/i_game_repository.dart'; diff --git a/lib/domain/game/game.freezed.dart b/lib/domain/game/game.freezed.dart new file mode 100644 index 0000000..18c5557 --- /dev/null +++ b/lib/domain/game/game.freezed.dart @@ -0,0 +1,1068 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'game.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$Game { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + List get prizes => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GameCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameCopyWith<$Res> { + factory $GameCopyWith(Game value, $Res Function(Game) then) = + _$GameCopyWithImpl<$Res, Game>; + @useResult + $Res call({ + String id, + String name, + String type, + bool isActive, + Map metadata, + List prizes, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class _$GameCopyWithImpl<$Res, $Val extends Game> + implements $GameCopyWith<$Res> { + _$GameCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? isActive = null, + Object? metadata = null, + Object? prizes = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + prizes: null == prizes + ? _value.prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GameImplCopyWith<$Res> implements $GameCopyWith<$Res> { + factory _$$GameImplCopyWith( + _$GameImpl value, + $Res Function(_$GameImpl) then, + ) = __$$GameImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String name, + String type, + bool isActive, + Map metadata, + List prizes, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class __$$GameImplCopyWithImpl<$Res> + extends _$GameCopyWithImpl<$Res, _$GameImpl> + implements _$$GameImplCopyWith<$Res> { + __$$GameImplCopyWithImpl(_$GameImpl _value, $Res Function(_$GameImpl) _then) + : super(_value, _then); + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? type = null, + Object? isActive = null, + Object? metadata = null, + Object? prizes = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$GameImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + prizes: null == prizes + ? _value._prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$GameImpl implements _Game { + const _$GameImpl({ + required this.id, + required this.name, + required this.type, + required this.isActive, + required final Map metadata, + required final List prizes, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata, + _prizes = prizes; + + @override + final String id; + @override + final String name; + @override + final String type; + @override + final bool isActive; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + final List _prizes; + @override + List get prizes { + if (_prizes is EqualUnmodifiableListView) return _prizes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_prizes); + } + + @override + final String createdAt; + @override + final String updatedAt; + + @override + String toString() { + return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GameImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.type, type) || other.type == type) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + const DeepCollectionEquality().equals(other._prizes, _prizes) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + type, + isActive, + const DeepCollectionEquality().hash(_metadata), + const DeepCollectionEquality().hash(_prizes), + createdAt, + updatedAt, + ); + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GameImplCopyWith<_$GameImpl> get copyWith => + __$$GameImplCopyWithImpl<_$GameImpl>(this, _$identity); +} + +abstract class _Game implements Game { + const factory _Game({ + required final String id, + required final String name, + required final String type, + required final bool isActive, + required final Map metadata, + required final List prizes, + required final String createdAt, + required final String updatedAt, + }) = _$GameImpl; + + @override + String get id; + @override + String get name; + @override + String get type; + @override + bool get isActive; + @override + Map get metadata; + @override + List get prizes; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of Game + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GameImplCopyWith<_$GameImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$GamePrize { + String get id => throw _privateConstructorUsedError; + String get gameId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GamePrizeCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GamePrizeCopyWith<$Res> { + factory $GamePrizeCopyWith(GamePrize value, $Res Function(GamePrize) then) = + _$GamePrizeCopyWithImpl<$Res, GamePrize>; + @useResult + $Res call({ + String id, + String gameId, + String name, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class _$GamePrizeCopyWithImpl<$Res, $Val extends GamePrize> + implements $GamePrizeCopyWith<$Res> { + _$GamePrizeCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? gameId = null, + Object? name = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + gameId: null == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GamePrizeImplCopyWith<$Res> + implements $GamePrizeCopyWith<$Res> { + factory _$$GamePrizeImplCopyWith( + _$GamePrizeImpl value, + $Res Function(_$GamePrizeImpl) then, + ) = __$$GamePrizeImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String gameId, + String name, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class __$$GamePrizeImplCopyWithImpl<$Res> + extends _$GamePrizeCopyWithImpl<$Res, _$GamePrizeImpl> + implements _$$GamePrizeImplCopyWith<$Res> { + __$$GamePrizeImplCopyWithImpl( + _$GamePrizeImpl _value, + $Res Function(_$GamePrizeImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? gameId = null, + Object? name = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$GamePrizeImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + gameId: null == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$GamePrizeImpl implements _GamePrize { + const _$GamePrizeImpl({ + required this.id, + required this.gameId, + required this.name, + required final Map metadata, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata; + + @override + final String id; + @override + final String gameId; + @override + final String name; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final String createdAt; + @override + final String updatedAt; + + @override + String toString() { + return 'GamePrize(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GamePrizeImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.gameId, gameId) || other.gameId == gameId) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + gameId, + name, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GamePrizeImplCopyWith<_$GamePrizeImpl> get copyWith => + __$$GamePrizeImplCopyWithImpl<_$GamePrizeImpl>(this, _$identity); +} + +abstract class _GamePrize implements GamePrize { + const factory _GamePrize({ + required final String id, + required final String gameId, + required final String name, + required final Map metadata, + required final String createdAt, + required final String updatedAt, + }) = _$GamePrizeImpl; + + @override + String get id; + @override + String get gameId; + @override + String get name; + @override + Map get metadata; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of GamePrize + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GamePrizeImplCopyWith<_$GamePrizeImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$GameFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameFailureCopyWith<$Res> { + factory $GameFailureCopyWith( + GameFailure value, + $Res Function(GameFailure) then, + ) = _$GameFailureCopyWithImpl<$Res, GameFailure>; +} + +/// @nodoc +class _$GameFailureCopyWithImpl<$Res, $Val extends GameFailure> + implements $GameFailureCopyWith<$Res> { + _$GameFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'GameFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements GameFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'GameFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements GameFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$GameFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'GameFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements GameFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of GameFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/game/repositories/i_game_repository.dart b/lib/domain/game/repositories/i_game_repository.dart new file mode 100644 index 0000000..f297b14 --- /dev/null +++ b/lib/domain/game/repositories/i_game_repository.dart @@ -0,0 +1,5 @@ +part of '../game.dart'; + +abstract class IGameRepository { + Future> ferrisWheel(); +} diff --git a/lib/env.dart b/lib/env.dart new file mode 100644 index 0000000..031e483 --- /dev/null +++ b/lib/env.dart @@ -0,0 +1,20 @@ +import 'package:injectable/injectable.dart'; + +abstract class Env { + String get baseUrl; + // add getter here... +} + +@Injectable(as: Env) +@dev +class DevEnv implements Env { + @override + String get baseUrl => 'http://192.168.1.30:4000'; // example value +} + +@Injectable(as: Env) +@prod +class ProdEnv implements Env { + @override + String get baseUrl => 'https://enaklo-pos-be.altru.id'; +} diff --git a/lib/infrastructure/auth/auth_dtos.dart b/lib/infrastructure/auth/auth_dtos.dart new file mode 100644 index 0000000..42603df --- /dev/null +++ b/lib/infrastructure/auth/auth_dtos.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/extension/extension.dart'; +import '../../domain/auth/auth.dart'; + +part 'auth_dtos.freezed.dart'; +part 'auth_dtos.g.dart'; + +part 'dto/check_phone_dto.dart'; +part 'dto/register_dto.dart'; +part 'dto/verify_dto.dart'; +part 'dto/login_dto.dart'; +part 'dto/resend_dto.dart'; diff --git a/lib/infrastructure/auth/auth_dtos.freezed.dart b/lib/infrastructure/auth/auth_dtos.freezed.dart new file mode 100644 index 0000000..98d1193 --- /dev/null +++ b/lib/infrastructure/auth/auth_dtos.freezed.dart @@ -0,0 +1,2393 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +CheckPhoneDto _$CheckPhoneDtoFromJson(Map json) { + return _CheckPhoneDto.fromJson(json); +} + +/// @nodoc +mixin _$CheckPhoneDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + CheckPhoneDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this CheckPhoneDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CheckPhoneDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CheckPhoneDtoCopyWith<$Res> { + factory $CheckPhoneDtoCopyWith( + CheckPhoneDto value, + $Res Function(CheckPhoneDto) then, + ) = _$CheckPhoneDtoCopyWithImpl<$Res, CheckPhoneDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CheckPhoneDataDto? data, + }); + + $CheckPhoneDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$CheckPhoneDtoCopyWithImpl<$Res, $Val extends CheckPhoneDto> + implements $CheckPhoneDtoCopyWith<$Res> { + _$CheckPhoneDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as CheckPhoneDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $CheckPhoneDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $CheckPhoneDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CheckPhoneDtoImplCopyWith<$Res> + implements $CheckPhoneDtoCopyWith<$Res> { + factory _$$CheckPhoneDtoImplCopyWith( + _$CheckPhoneDtoImpl value, + $Res Function(_$CheckPhoneDtoImpl) then, + ) = __$$CheckPhoneDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CheckPhoneDataDto? data, + }); + + @override + $CheckPhoneDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$CheckPhoneDtoImplCopyWithImpl<$Res> + extends _$CheckPhoneDtoCopyWithImpl<$Res, _$CheckPhoneDtoImpl> + implements _$$CheckPhoneDtoImplCopyWith<$Res> { + __$$CheckPhoneDtoImplCopyWithImpl( + _$CheckPhoneDtoImpl _value, + $Res Function(_$CheckPhoneDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$CheckPhoneDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as CheckPhoneDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$CheckPhoneDtoImpl extends _CheckPhoneDto { + const _$CheckPhoneDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$CheckPhoneDtoImpl.fromJson(Map json) => + _$$CheckPhoneDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final CheckPhoneDataDto? data; + + @override + String toString() { + return 'CheckPhoneDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CheckPhoneDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CheckPhoneDtoImplCopyWith<_$CheckPhoneDtoImpl> get copyWith => + __$$CheckPhoneDtoImplCopyWithImpl<_$CheckPhoneDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CheckPhoneDtoImplToJson(this); + } +} + +abstract class _CheckPhoneDto extends CheckPhoneDto { + const factory _CheckPhoneDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final CheckPhoneDataDto? data, + }) = _$CheckPhoneDtoImpl; + const _CheckPhoneDto._() : super._(); + + factory _CheckPhoneDto.fromJson(Map json) = + _$CheckPhoneDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + CheckPhoneDataDto? get data; + + /// Create a copy of CheckPhoneDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CheckPhoneDtoImplCopyWith<_$CheckPhoneDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CheckPhoneDataDto _$CheckPhoneDataDtoFromJson(Map json) { + return _CheckPhoneDataDto.fromJson(json); +} + +/// @nodoc +mixin _$CheckPhoneDataDto { + @JsonKey(name: 'phone_number') + String? get phoneNumber => throw _privateConstructorUsedError; + + /// Serializes this CheckPhoneDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CheckPhoneDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CheckPhoneDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CheckPhoneDataDtoCopyWith<$Res> { + factory $CheckPhoneDataDtoCopyWith( + CheckPhoneDataDto value, + $Res Function(CheckPhoneDataDto) then, + ) = _$CheckPhoneDataDtoCopyWithImpl<$Res, CheckPhoneDataDto>; + @useResult + $Res call({@JsonKey(name: 'phone_number') String? phoneNumber}); +} + +/// @nodoc +class _$CheckPhoneDataDtoCopyWithImpl<$Res, $Val extends CheckPhoneDataDto> + implements $CheckPhoneDataDtoCopyWith<$Res> { + _$CheckPhoneDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CheckPhoneDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = freezed}) { + return _then( + _value.copyWith( + phoneNumber: freezed == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CheckPhoneDataDtoImplCopyWith<$Res> + implements $CheckPhoneDataDtoCopyWith<$Res> { + factory _$$CheckPhoneDataDtoImplCopyWith( + _$CheckPhoneDataDtoImpl value, + $Res Function(_$CheckPhoneDataDtoImpl) then, + ) = __$$CheckPhoneDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonKey(name: 'phone_number') String? phoneNumber}); +} + +/// @nodoc +class __$$CheckPhoneDataDtoImplCopyWithImpl<$Res> + extends _$CheckPhoneDataDtoCopyWithImpl<$Res, _$CheckPhoneDataDtoImpl> + implements _$$CheckPhoneDataDtoImplCopyWith<$Res> { + __$$CheckPhoneDataDtoImplCopyWithImpl( + _$CheckPhoneDataDtoImpl _value, + $Res Function(_$CheckPhoneDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CheckPhoneDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? phoneNumber = freezed}) { + return _then( + _$CheckPhoneDataDtoImpl( + phoneNumber: freezed == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$CheckPhoneDataDtoImpl implements _CheckPhoneDataDto { + const _$CheckPhoneDataDtoImpl({ + @JsonKey(name: 'phone_number') this.phoneNumber, + }); + + factory _$CheckPhoneDataDtoImpl.fromJson(Map json) => + _$$CheckPhoneDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'phone_number') + final String? phoneNumber; + + @override + String toString() { + return 'CheckPhoneDataDto(phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CheckPhoneDataDtoImpl && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, phoneNumber); + + /// Create a copy of CheckPhoneDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CheckPhoneDataDtoImplCopyWith<_$CheckPhoneDataDtoImpl> get copyWith => + __$$CheckPhoneDataDtoImplCopyWithImpl<_$CheckPhoneDataDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$CheckPhoneDataDtoImplToJson(this); + } +} + +abstract class _CheckPhoneDataDto implements CheckPhoneDataDto { + const factory _CheckPhoneDataDto({ + @JsonKey(name: 'phone_number') final String? phoneNumber, + }) = _$CheckPhoneDataDtoImpl; + + factory _CheckPhoneDataDto.fromJson(Map json) = + _$CheckPhoneDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'phone_number') + String? get phoneNumber; + + /// Create a copy of CheckPhoneDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CheckPhoneDataDtoImplCopyWith<_$CheckPhoneDataDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +RegisterDto _$RegisterDtoFromJson(Map json) { + return _RegisterDto.fromJson(json); +} + +/// @nodoc +mixin _$RegisterDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + RegisterDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this RegisterDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RegisterDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RegisterDtoCopyWith<$Res> { + factory $RegisterDtoCopyWith( + RegisterDto value, + $Res Function(RegisterDto) then, + ) = _$RegisterDtoCopyWithImpl<$Res, RegisterDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') RegisterDataDto? data, + }); + + $RegisterDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$RegisterDtoCopyWithImpl<$Res, $Val extends RegisterDto> + implements $RegisterDtoCopyWith<$Res> { + _$RegisterDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as RegisterDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $RegisterDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $RegisterDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$RegisterDtoImplCopyWith<$Res> + implements $RegisterDtoCopyWith<$Res> { + factory _$$RegisterDtoImplCopyWith( + _$RegisterDtoImpl value, + $Res Function(_$RegisterDtoImpl) then, + ) = __$$RegisterDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') RegisterDataDto? data, + }); + + @override + $RegisterDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$RegisterDtoImplCopyWithImpl<$Res> + extends _$RegisterDtoCopyWithImpl<$Res, _$RegisterDtoImpl> + implements _$$RegisterDtoImplCopyWith<$Res> { + __$$RegisterDtoImplCopyWithImpl( + _$RegisterDtoImpl _value, + $Res Function(_$RegisterDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$RegisterDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as RegisterDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$RegisterDtoImpl extends _RegisterDto { + const _$RegisterDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$RegisterDtoImpl.fromJson(Map json) => + _$$RegisterDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final RegisterDataDto? data; + + @override + String toString() { + return 'RegisterDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegisterDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegisterDtoImplCopyWith<_$RegisterDtoImpl> get copyWith => + __$$RegisterDtoImplCopyWithImpl<_$RegisterDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RegisterDtoImplToJson(this); + } +} + +abstract class _RegisterDto extends RegisterDto { + const factory _RegisterDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final RegisterDataDto? data, + }) = _$RegisterDtoImpl; + const _RegisterDto._() : super._(); + + factory _RegisterDto.fromJson(Map json) = + _$RegisterDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + RegisterDataDto? get data; + + /// Create a copy of RegisterDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegisterDtoImplCopyWith<_$RegisterDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +RegisterDataDto _$RegisterDataDtoFromJson(Map json) { + return _RegisterDataDto.fromJson(json); +} + +/// @nodoc +mixin _$RegisterDataDto { + @JsonKey(name: 'registration_token') + String? get registrationToken => throw _privateConstructorUsedError; + @JsonKey(name: 'otp_token') + String? get otpToken => throw _privateConstructorUsedError; + @JsonKey(name: 'expires_in') + int? get expiresIn => throw _privateConstructorUsedError; + + /// Serializes this RegisterDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of RegisterDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RegisterDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RegisterDataDtoCopyWith<$Res> { + factory $RegisterDataDtoCopyWith( + RegisterDataDto value, + $Res Function(RegisterDataDto) then, + ) = _$RegisterDataDtoCopyWithImpl<$Res, RegisterDataDto>; + @useResult + $Res call({ + @JsonKey(name: 'registration_token') String? registrationToken, + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + }); +} + +/// @nodoc +class _$RegisterDataDtoCopyWithImpl<$Res, $Val extends RegisterDataDto> + implements $RegisterDataDtoCopyWith<$Res> { + _$RegisterDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RegisterDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = freezed, + Object? otpToken = freezed, + Object? expiresIn = freezed, + }) { + return _then( + _value.copyWith( + registrationToken: freezed == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String?, + otpToken: freezed == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String?, + expiresIn: freezed == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$RegisterDataDtoImplCopyWith<$Res> + implements $RegisterDataDtoCopyWith<$Res> { + factory _$$RegisterDataDtoImplCopyWith( + _$RegisterDataDtoImpl value, + $Res Function(_$RegisterDataDtoImpl) then, + ) = __$$RegisterDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'registration_token') String? registrationToken, + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + }); +} + +/// @nodoc +class __$$RegisterDataDtoImplCopyWithImpl<$Res> + extends _$RegisterDataDtoCopyWithImpl<$Res, _$RegisterDataDtoImpl> + implements _$$RegisterDataDtoImplCopyWith<$Res> { + __$$RegisterDataDtoImplCopyWithImpl( + _$RegisterDataDtoImpl _value, + $Res Function(_$RegisterDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of RegisterDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? registrationToken = freezed, + Object? otpToken = freezed, + Object? expiresIn = freezed, + }) { + return _then( + _$RegisterDataDtoImpl( + registrationToken: freezed == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String?, + otpToken: freezed == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String?, + expiresIn: freezed == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$RegisterDataDtoImpl implements _RegisterDataDto { + const _$RegisterDataDtoImpl({ + @JsonKey(name: 'registration_token') this.registrationToken, + @JsonKey(name: 'otp_token') this.otpToken, + @JsonKey(name: 'expires_in') this.expiresIn, + }); + + factory _$RegisterDataDtoImpl.fromJson(Map json) => + _$$RegisterDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'registration_token') + final String? registrationToken; + @override + @JsonKey(name: 'otp_token') + final String? otpToken; + @override + @JsonKey(name: 'expires_in') + final int? expiresIn; + + @override + String toString() { + return 'RegisterDataDto(registrationToken: $registrationToken, otpToken: $otpToken, expiresIn: $expiresIn)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RegisterDataDtoImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken) && + (identical(other.otpToken, otpToken) || + other.otpToken == otpToken) && + (identical(other.expiresIn, expiresIn) || + other.expiresIn == expiresIn)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, registrationToken, otpToken, expiresIn); + + /// Create a copy of RegisterDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RegisterDataDtoImplCopyWith<_$RegisterDataDtoImpl> get copyWith => + __$$RegisterDataDtoImplCopyWithImpl<_$RegisterDataDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$RegisterDataDtoImplToJson(this); + } +} + +abstract class _RegisterDataDto implements RegisterDataDto { + const factory _RegisterDataDto({ + @JsonKey(name: 'registration_token') final String? registrationToken, + @JsonKey(name: 'otp_token') final String? otpToken, + @JsonKey(name: 'expires_in') final int? expiresIn, + }) = _$RegisterDataDtoImpl; + + factory _RegisterDataDto.fromJson(Map json) = + _$RegisterDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'registration_token') + String? get registrationToken; + @override + @JsonKey(name: 'otp_token') + String? get otpToken; + @override + @JsonKey(name: 'expires_in') + int? get expiresIn; + + /// Create a copy of RegisterDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RegisterDataDtoImplCopyWith<_$RegisterDataDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +VerifyDto _$VerifyDtoFromJson(Map json) { + return _VerifyDto.fromJson(json); +} + +/// @nodoc +mixin _$VerifyDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + VerifyDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this VerifyDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VerifyDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyDtoCopyWith<$Res> { + factory $VerifyDtoCopyWith(VerifyDto value, $Res Function(VerifyDto) then) = + _$VerifyDtoCopyWithImpl<$Res, VerifyDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') VerifyDataDto? data, + }); + + $VerifyDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$VerifyDtoCopyWithImpl<$Res, $Val extends VerifyDto> + implements $VerifyDtoCopyWith<$Res> { + _$VerifyDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as VerifyDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $VerifyDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $VerifyDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$VerifyDtoImplCopyWith<$Res> + implements $VerifyDtoCopyWith<$Res> { + factory _$$VerifyDtoImplCopyWith( + _$VerifyDtoImpl value, + $Res Function(_$VerifyDtoImpl) then, + ) = __$$VerifyDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') VerifyDataDto? data, + }); + + @override + $VerifyDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$VerifyDtoImplCopyWithImpl<$Res> + extends _$VerifyDtoCopyWithImpl<$Res, _$VerifyDtoImpl> + implements _$$VerifyDtoImplCopyWith<$Res> { + __$$VerifyDtoImplCopyWithImpl( + _$VerifyDtoImpl _value, + $Res Function(_$VerifyDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$VerifyDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as VerifyDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$VerifyDtoImpl extends _VerifyDto { + const _$VerifyDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$VerifyDtoImpl.fromJson(Map json) => + _$$VerifyDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final VerifyDataDto? data; + + @override + String toString() { + return 'VerifyDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VerifyDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VerifyDtoImplCopyWith<_$VerifyDtoImpl> get copyWith => + __$$VerifyDtoImplCopyWithImpl<_$VerifyDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$VerifyDtoImplToJson(this); + } +} + +abstract class _VerifyDto extends VerifyDto { + const factory _VerifyDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final VerifyDataDto? data, + }) = _$VerifyDtoImpl; + const _VerifyDto._() : super._(); + + factory _VerifyDto.fromJson(Map json) = + _$VerifyDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + VerifyDataDto? get data; + + /// Create a copy of VerifyDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VerifyDtoImplCopyWith<_$VerifyDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +VerifyDataDto _$VerifyDataDtoFromJson(Map json) { + return _VerifyDataDto.fromJson(json); +} + +/// @nodoc +mixin _$VerifyDataDto { + @JsonKey(name: 'registration_token') + String? get registrationToken => throw _privateConstructorUsedError; + + /// Serializes this VerifyDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of VerifyDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VerifyDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VerifyDataDtoCopyWith<$Res> { + factory $VerifyDataDtoCopyWith( + VerifyDataDto value, + $Res Function(VerifyDataDto) then, + ) = _$VerifyDataDtoCopyWithImpl<$Res, VerifyDataDto>; + @useResult + $Res call({@JsonKey(name: 'registration_token') String? registrationToken}); +} + +/// @nodoc +class _$VerifyDataDtoCopyWithImpl<$Res, $Val extends VerifyDataDto> + implements $VerifyDataDtoCopyWith<$Res> { + _$VerifyDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VerifyDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? registrationToken = freezed}) { + return _then( + _value.copyWith( + registrationToken: freezed == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$VerifyDataDtoImplCopyWith<$Res> + implements $VerifyDataDtoCopyWith<$Res> { + factory _$$VerifyDataDtoImplCopyWith( + _$VerifyDataDtoImpl value, + $Res Function(_$VerifyDataDtoImpl) then, + ) = __$$VerifyDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonKey(name: 'registration_token') String? registrationToken}); +} + +/// @nodoc +class __$$VerifyDataDtoImplCopyWithImpl<$Res> + extends _$VerifyDataDtoCopyWithImpl<$Res, _$VerifyDataDtoImpl> + implements _$$VerifyDataDtoImplCopyWith<$Res> { + __$$VerifyDataDtoImplCopyWithImpl( + _$VerifyDataDtoImpl _value, + $Res Function(_$VerifyDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of VerifyDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? registrationToken = freezed}) { + return _then( + _$VerifyDataDtoImpl( + registrationToken: freezed == registrationToken + ? _value.registrationToken + : registrationToken // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$VerifyDataDtoImpl implements _VerifyDataDto { + const _$VerifyDataDtoImpl({ + @JsonKey(name: 'registration_token') this.registrationToken, + }); + + factory _$VerifyDataDtoImpl.fromJson(Map json) => + _$$VerifyDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'registration_token') + final String? registrationToken; + + @override + String toString() { + return 'VerifyDataDto(registrationToken: $registrationToken)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VerifyDataDtoImpl && + (identical(other.registrationToken, registrationToken) || + other.registrationToken == registrationToken)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, registrationToken); + + /// Create a copy of VerifyDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VerifyDataDtoImplCopyWith<_$VerifyDataDtoImpl> get copyWith => + __$$VerifyDataDtoImplCopyWithImpl<_$VerifyDataDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$VerifyDataDtoImplToJson(this); + } +} + +abstract class _VerifyDataDto implements VerifyDataDto { + const factory _VerifyDataDto({ + @JsonKey(name: 'registration_token') final String? registrationToken, + }) = _$VerifyDataDtoImpl; + + factory _VerifyDataDto.fromJson(Map json) = + _$VerifyDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'registration_token') + String? get registrationToken; + + /// Create a copy of VerifyDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VerifyDataDtoImplCopyWith<_$VerifyDataDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +LoginDto _$LoginDtoFromJson(Map json) { + return _LoginDto.fromJson(json); +} + +/// @nodoc +mixin _$LoginDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + LoginDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this LoginDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginDtoCopyWith<$Res> { + factory $LoginDtoCopyWith(LoginDto value, $Res Function(LoginDto) then) = + _$LoginDtoCopyWithImpl<$Res, LoginDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') LoginDataDto? data, + }); + + $LoginDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$LoginDtoCopyWithImpl<$Res, $Val extends LoginDto> + implements $LoginDtoCopyWith<$Res> { + _$LoginDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as LoginDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LoginDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $LoginDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$LoginDtoImplCopyWith<$Res> + implements $LoginDtoCopyWith<$Res> { + factory _$$LoginDtoImplCopyWith( + _$LoginDtoImpl value, + $Res Function(_$LoginDtoImpl) then, + ) = __$$LoginDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') LoginDataDto? data, + }); + + @override + $LoginDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$LoginDtoImplCopyWithImpl<$Res> + extends _$LoginDtoCopyWithImpl<$Res, _$LoginDtoImpl> + implements _$$LoginDtoImplCopyWith<$Res> { + __$$LoginDtoImplCopyWithImpl( + _$LoginDtoImpl _value, + $Res Function(_$LoginDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$LoginDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as LoginDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginDtoImpl extends _LoginDto { + const _$LoginDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$LoginDtoImpl.fromJson(Map json) => + _$$LoginDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final LoginDataDto? data; + + @override + String toString() { + return 'LoginDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginDtoImplCopyWith<_$LoginDtoImpl> get copyWith => + __$$LoginDtoImplCopyWithImpl<_$LoginDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginDtoImplToJson(this); + } +} + +abstract class _LoginDto extends LoginDto { + const factory _LoginDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final LoginDataDto? data, + }) = _$LoginDtoImpl; + const _LoginDto._() : super._(); + + factory _LoginDto.fromJson(Map json) = + _$LoginDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + LoginDataDto? get data; + + /// Create a copy of LoginDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginDtoImplCopyWith<_$LoginDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +LoginDataDto _$LoginDataDtoFromJson(Map json) { + return _LoginDataDto.fromJson(json); +} + +/// @nodoc +mixin _$LoginDataDto { + @JsonKey(name: 'access_token') + String? get accessToken => throw _privateConstructorUsedError; + @JsonKey(name: 'refresh_token') + String? get refreshToken => throw _privateConstructorUsedError; + @JsonKey(name: 'user') + UserDto? get user => throw _privateConstructorUsedError; + + /// Serializes this LoginDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginDataDtoCopyWith<$Res> { + factory $LoginDataDtoCopyWith( + LoginDataDto value, + $Res Function(LoginDataDto) then, + ) = _$LoginDataDtoCopyWithImpl<$Res, LoginDataDto>; + @useResult + $Res call({ + @JsonKey(name: 'access_token') String? accessToken, + @JsonKey(name: 'refresh_token') String? refreshToken, + @JsonKey(name: 'user') UserDto? user, + }); + + $UserDtoCopyWith<$Res>? get user; +} + +/// @nodoc +class _$LoginDataDtoCopyWithImpl<$Res, $Val extends LoginDataDto> + implements $LoginDataDtoCopyWith<$Res> { + _$LoginDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accessToken = freezed, + Object? refreshToken = freezed, + Object? user = freezed, + }) { + return _then( + _value.copyWith( + accessToken: freezed == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String?, + refreshToken: freezed == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String?, + user: freezed == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as UserDto?, + ) + as $Val, + ); + } + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserDtoCopyWith<$Res>? get user { + if (_value.user == null) { + return null; + } + + return $UserDtoCopyWith<$Res>(_value.user!, (value) { + return _then(_value.copyWith(user: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$LoginDataDtoImplCopyWith<$Res> + implements $LoginDataDtoCopyWith<$Res> { + factory _$$LoginDataDtoImplCopyWith( + _$LoginDataDtoImpl value, + $Res Function(_$LoginDataDtoImpl) then, + ) = __$$LoginDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'access_token') String? accessToken, + @JsonKey(name: 'refresh_token') String? refreshToken, + @JsonKey(name: 'user') UserDto? user, + }); + + @override + $UserDtoCopyWith<$Res>? get user; +} + +/// @nodoc +class __$$LoginDataDtoImplCopyWithImpl<$Res> + extends _$LoginDataDtoCopyWithImpl<$Res, _$LoginDataDtoImpl> + implements _$$LoginDataDtoImplCopyWith<$Res> { + __$$LoginDataDtoImplCopyWithImpl( + _$LoginDataDtoImpl _value, + $Res Function(_$LoginDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accessToken = freezed, + Object? refreshToken = freezed, + Object? user = freezed, + }) { + return _then( + _$LoginDataDtoImpl( + accessToken: freezed == accessToken + ? _value.accessToken + : accessToken // ignore: cast_nullable_to_non_nullable + as String?, + refreshToken: freezed == refreshToken + ? _value.refreshToken + : refreshToken // ignore: cast_nullable_to_non_nullable + as String?, + user: freezed == user + ? _value.user + : user // ignore: cast_nullable_to_non_nullable + as UserDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginDataDtoImpl implements _LoginDataDto { + const _$LoginDataDtoImpl({ + @JsonKey(name: 'access_token') this.accessToken, + @JsonKey(name: 'refresh_token') this.refreshToken, + @JsonKey(name: 'user') this.user, + }); + + factory _$LoginDataDtoImpl.fromJson(Map json) => + _$$LoginDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'access_token') + final String? accessToken; + @override + @JsonKey(name: 'refresh_token') + final String? refreshToken; + @override + @JsonKey(name: 'user') + final UserDto? user; + + @override + String toString() { + return 'LoginDataDto(accessToken: $accessToken, refreshToken: $refreshToken, user: $user)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginDataDtoImpl && + (identical(other.accessToken, accessToken) || + other.accessToken == accessToken) && + (identical(other.refreshToken, refreshToken) || + other.refreshToken == refreshToken) && + (identical(other.user, user) || other.user == user)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, accessToken, refreshToken, user); + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginDataDtoImplCopyWith<_$LoginDataDtoImpl> get copyWith => + __$$LoginDataDtoImplCopyWithImpl<_$LoginDataDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginDataDtoImplToJson(this); + } +} + +abstract class _LoginDataDto implements LoginDataDto { + const factory _LoginDataDto({ + @JsonKey(name: 'access_token') final String? accessToken, + @JsonKey(name: 'refresh_token') final String? refreshToken, + @JsonKey(name: 'user') final UserDto? user, + }) = _$LoginDataDtoImpl; + + factory _LoginDataDto.fromJson(Map json) = + _$LoginDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'access_token') + String? get accessToken; + @override + @JsonKey(name: 'refresh_token') + String? get refreshToken; + @override + @JsonKey(name: 'user') + UserDto? get user; + + /// Create a copy of LoginDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginDataDtoImplCopyWith<_$LoginDataDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +UserDto _$UserDtoFromJson(Map json) { + return _UserDto.fromJson(json); +} + +/// @nodoc +mixin _$UserDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'phone_number') + String? get phoneNumber => throw _privateConstructorUsedError; + @JsonKey(name: 'birth_date') + String? get birthDate => throw _privateConstructorUsedError; + + /// Serializes this UserDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of UserDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserDtoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserDtoCopyWith<$Res> { + factory $UserDtoCopyWith(UserDto value, $Res Function(UserDto) then) = + _$UserDtoCopyWithImpl<$Res, UserDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'phone_number') String? phoneNumber, + @JsonKey(name: 'birth_date') String? birthDate, + }); +} + +/// @nodoc +class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto> + implements $UserDtoCopyWith<$Res> { + _$UserDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? phoneNumber = freezed, + Object? birthDate = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + phoneNumber: freezed == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + birthDate: freezed == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UserDtoImplCopyWith<$Res> implements $UserDtoCopyWith<$Res> { + factory _$$UserDtoImplCopyWith( + _$UserDtoImpl value, + $Res Function(_$UserDtoImpl) then, + ) = __$$UserDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'phone_number') String? phoneNumber, + @JsonKey(name: 'birth_date') String? birthDate, + }); +} + +/// @nodoc +class __$$UserDtoImplCopyWithImpl<$Res> + extends _$UserDtoCopyWithImpl<$Res, _$UserDtoImpl> + implements _$$UserDtoImplCopyWith<$Res> { + __$$UserDtoImplCopyWithImpl( + _$UserDtoImpl _value, + $Res Function(_$UserDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UserDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? phoneNumber = freezed, + Object? birthDate = freezed, + }) { + return _then( + _$UserDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + phoneNumber: freezed == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + birthDate: freezed == birthDate + ? _value.birthDate + : birthDate // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserDtoImpl extends _UserDto { + const _$UserDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'phone_number') this.phoneNumber, + @JsonKey(name: 'birth_date') this.birthDate, + }) : super._(); + + factory _$UserDtoImpl.fromJson(Map json) => + _$$UserDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'name') + final String? name; + @override + @JsonKey(name: 'phone_number') + final String? phoneNumber; + @override + @JsonKey(name: 'birth_date') + final String? birthDate; + + @override + String toString() { + return 'UserDto(id: $id, name: $name, phoneNumber: $phoneNumber, birthDate: $birthDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.birthDate, birthDate) || + other.birthDate == birthDate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, id, name, phoneNumber, birthDate); + + /// Create a copy of UserDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserDtoImplCopyWith<_$UserDtoImpl> get copyWith => + __$$UserDtoImplCopyWithImpl<_$UserDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UserDtoImplToJson(this); + } +} + +abstract class _UserDto extends UserDto { + const factory _UserDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'phone_number') final String? phoneNumber, + @JsonKey(name: 'birth_date') final String? birthDate, + }) = _$UserDtoImpl; + const _UserDto._() : super._(); + + factory _UserDto.fromJson(Map json) = _$UserDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'phone_number') + String? get phoneNumber; + @override + @JsonKey(name: 'birth_date') + String? get birthDate; + + /// Create a copy of UserDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserDtoImplCopyWith<_$UserDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ResendDto _$ResendDtoFromJson(Map json) { + return _ResendDto.fromJson(json); +} + +/// @nodoc +mixin _$ResendDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + ResendDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this ResendDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ResendDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResendDtoCopyWith<$Res> { + factory $ResendDtoCopyWith(ResendDto value, $Res Function(ResendDto) then) = + _$ResendDtoCopyWithImpl<$Res, ResendDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') ResendDataDto? data, + }); + + $ResendDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$ResendDtoCopyWithImpl<$Res, $Val extends ResendDto> + implements $ResendDtoCopyWith<$Res> { + _$ResendDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as ResendDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ResendDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $ResendDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ResendDtoImplCopyWith<$Res> + implements $ResendDtoCopyWith<$Res> { + factory _$$ResendDtoImplCopyWith( + _$ResendDtoImpl value, + $Res Function(_$ResendDtoImpl) then, + ) = __$$ResendDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') ResendDataDto? data, + }); + + @override + $ResendDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$ResendDtoImplCopyWithImpl<$Res> + extends _$ResendDtoCopyWithImpl<$Res, _$ResendDtoImpl> + implements _$$ResendDtoImplCopyWith<$Res> { + __$$ResendDtoImplCopyWithImpl( + _$ResendDtoImpl _value, + $Res Function(_$ResendDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$ResendDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as ResendDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ResendDtoImpl extends _ResendDto { + const _$ResendDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$ResendDtoImpl.fromJson(Map json) => + _$$ResendDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final ResendDataDto? data; + + @override + String toString() { + return 'ResendDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ResendDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ResendDtoImplCopyWith<_$ResendDtoImpl> get copyWith => + __$$ResendDtoImplCopyWithImpl<_$ResendDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ResendDtoImplToJson(this); + } +} + +abstract class _ResendDto extends ResendDto { + const factory _ResendDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final ResendDataDto? data, + }) = _$ResendDtoImpl; + const _ResendDto._() : super._(); + + factory _ResendDto.fromJson(Map json) = + _$ResendDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + ResendDataDto? get data; + + /// Create a copy of ResendDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ResendDtoImplCopyWith<_$ResendDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ResendDataDto _$ResendDataDtoFromJson(Map json) { + return _ResendDataDto.fromJson(json); +} + +/// @nodoc +mixin _$ResendDataDto { + @JsonKey(name: 'otp_token') + String? get otpToken => throw _privateConstructorUsedError; + @JsonKey(name: 'expires_in') + int? get expiresIn => throw _privateConstructorUsedError; + @JsonKey(name: 'next_resend_in') + int? get nextResendIn => throw _privateConstructorUsedError; + + /// Serializes this ResendDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ResendDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ResendDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ResendDataDtoCopyWith<$Res> { + factory $ResendDataDtoCopyWith( + ResendDataDto value, + $Res Function(ResendDataDto) then, + ) = _$ResendDataDtoCopyWithImpl<$Res, ResendDataDto>; + @useResult + $Res call({ + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + @JsonKey(name: 'next_resend_in') int? nextResendIn, + }); +} + +/// @nodoc +class _$ResendDataDtoCopyWithImpl<$Res, $Val extends ResendDataDto> + implements $ResendDataDtoCopyWith<$Res> { + _$ResendDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ResendDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpToken = freezed, + Object? expiresIn = freezed, + Object? nextResendIn = freezed, + }) { + return _then( + _value.copyWith( + otpToken: freezed == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String?, + expiresIn: freezed == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int?, + nextResendIn: freezed == nextResendIn + ? _value.nextResendIn + : nextResendIn // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ResendDataDtoImplCopyWith<$Res> + implements $ResendDataDtoCopyWith<$Res> { + factory _$$ResendDataDtoImplCopyWith( + _$ResendDataDtoImpl value, + $Res Function(_$ResendDataDtoImpl) then, + ) = __$$ResendDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + @JsonKey(name: 'next_resend_in') int? nextResendIn, + }); +} + +/// @nodoc +class __$$ResendDataDtoImplCopyWithImpl<$Res> + extends _$ResendDataDtoCopyWithImpl<$Res, _$ResendDataDtoImpl> + implements _$$ResendDataDtoImplCopyWith<$Res> { + __$$ResendDataDtoImplCopyWithImpl( + _$ResendDataDtoImpl _value, + $Res Function(_$ResendDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ResendDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpToken = freezed, + Object? expiresIn = freezed, + Object? nextResendIn = freezed, + }) { + return _then( + _$ResendDataDtoImpl( + otpToken: freezed == otpToken + ? _value.otpToken + : otpToken // ignore: cast_nullable_to_non_nullable + as String?, + expiresIn: freezed == expiresIn + ? _value.expiresIn + : expiresIn // ignore: cast_nullable_to_non_nullable + as int?, + nextResendIn: freezed == nextResendIn + ? _value.nextResendIn + : nextResendIn // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ResendDataDtoImpl implements _ResendDataDto { + const _$ResendDataDtoImpl({ + @JsonKey(name: 'otp_token') this.otpToken, + @JsonKey(name: 'expires_in') this.expiresIn, + @JsonKey(name: 'next_resend_in') this.nextResendIn, + }); + + factory _$ResendDataDtoImpl.fromJson(Map json) => + _$$ResendDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'otp_token') + final String? otpToken; + @override + @JsonKey(name: 'expires_in') + final int? expiresIn; + @override + @JsonKey(name: 'next_resend_in') + final int? nextResendIn; + + @override + String toString() { + return 'ResendDataDto(otpToken: $otpToken, expiresIn: $expiresIn, nextResendIn: $nextResendIn)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ResendDataDtoImpl && + (identical(other.otpToken, otpToken) || + other.otpToken == otpToken) && + (identical(other.expiresIn, expiresIn) || + other.expiresIn == expiresIn) && + (identical(other.nextResendIn, nextResendIn) || + other.nextResendIn == nextResendIn)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, otpToken, expiresIn, nextResendIn); + + /// Create a copy of ResendDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ResendDataDtoImplCopyWith<_$ResendDataDtoImpl> get copyWith => + __$$ResendDataDtoImplCopyWithImpl<_$ResendDataDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ResendDataDtoImplToJson(this); + } +} + +abstract class _ResendDataDto implements ResendDataDto { + const factory _ResendDataDto({ + @JsonKey(name: 'otp_token') final String? otpToken, + @JsonKey(name: 'expires_in') final int? expiresIn, + @JsonKey(name: 'next_resend_in') final int? nextResendIn, + }) = _$ResendDataDtoImpl; + + factory _ResendDataDto.fromJson(Map json) = + _$ResendDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'otp_token') + String? get otpToken; + @override + @JsonKey(name: 'expires_in') + int? get expiresIn; + @override + @JsonKey(name: 'next_resend_in') + int? get nextResendIn; + + /// Create a copy of ResendDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ResendDataDtoImplCopyWith<_$ResendDataDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/auth/auth_dtos.g.dart b/lib/infrastructure/auth/auth_dtos.g.dart new file mode 100644 index 0000000..6569175 --- /dev/null +++ b/lib/infrastructure/auth/auth_dtos.g.dart @@ -0,0 +1,165 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$CheckPhoneDtoImpl _$$CheckPhoneDtoImplFromJson(Map json) => + _$CheckPhoneDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : CheckPhoneDataDto.fromJson(json['data'] as Map), + ); + +Map _$$CheckPhoneDtoImplToJson(_$CheckPhoneDtoImpl instance) => + { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, + }; + +_$CheckPhoneDataDtoImpl _$$CheckPhoneDataDtoImplFromJson( + Map json, +) => _$CheckPhoneDataDtoImpl(phoneNumber: json['phone_number'] as String?); + +Map _$$CheckPhoneDataDtoImplToJson( + _$CheckPhoneDataDtoImpl instance, +) => {'phone_number': instance.phoneNumber}; + +_$RegisterDtoImpl _$$RegisterDtoImplFromJson(Map json) => + _$RegisterDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : RegisterDataDto.fromJson(json['data'] as Map), + ); + +Map _$$RegisterDtoImplToJson(_$RegisterDtoImpl instance) => + { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, + }; + +_$RegisterDataDtoImpl _$$RegisterDataDtoImplFromJson( + Map json, +) => _$RegisterDataDtoImpl( + registrationToken: json['registration_token'] as String?, + otpToken: json['otp_token'] as String?, + expiresIn: (json['expires_in'] as num?)?.toInt(), +); + +Map _$$RegisterDataDtoImplToJson( + _$RegisterDataDtoImpl instance, +) => { + 'registration_token': instance.registrationToken, + 'otp_token': instance.otpToken, + 'expires_in': instance.expiresIn, +}; + +_$VerifyDtoImpl _$$VerifyDtoImplFromJson(Map json) => + _$VerifyDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : VerifyDataDto.fromJson(json['data'] as Map), + ); + +Map _$$VerifyDtoImplToJson(_$VerifyDtoImpl instance) => + { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, + }; + +_$VerifyDataDtoImpl _$$VerifyDataDtoImplFromJson(Map json) => + _$VerifyDataDtoImpl( + registrationToken: json['registration_token'] as String?, + ); + +Map _$$VerifyDataDtoImplToJson(_$VerifyDataDtoImpl instance) => + {'registration_token': instance.registrationToken}; + +_$LoginDtoImpl _$$LoginDtoImplFromJson(Map json) => + _$LoginDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : LoginDataDto.fromJson(json['data'] as Map), + ); + +Map _$$LoginDtoImplToJson(_$LoginDtoImpl instance) => + { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, + }; + +_$LoginDataDtoImpl _$$LoginDataDtoImplFromJson(Map json) => + _$LoginDataDtoImpl( + accessToken: json['access_token'] as String?, + refreshToken: json['refresh_token'] as String?, + user: json['user'] == null + ? null + : UserDto.fromJson(json['user'] as Map), + ); + +Map _$$LoginDataDtoImplToJson(_$LoginDataDtoImpl instance) => + { + 'access_token': instance.accessToken, + 'refresh_token': instance.refreshToken, + 'user': instance.user, + }; + +_$UserDtoImpl _$$UserDtoImplFromJson(Map json) => + _$UserDtoImpl( + id: json['id'] as String?, + name: json['name'] as String?, + phoneNumber: json['phone_number'] as String?, + birthDate: json['birth_date'] as String?, + ); + +Map _$$UserDtoImplToJson(_$UserDtoImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'phone_number': instance.phoneNumber, + 'birth_date': instance.birthDate, + }; + +_$ResendDtoImpl _$$ResendDtoImplFromJson(Map json) => + _$ResendDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : ResendDataDto.fromJson(json['data'] as Map), + ); + +Map _$$ResendDtoImplToJson(_$ResendDtoImpl instance) => + { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, + }; + +_$ResendDataDtoImpl _$$ResendDataDtoImplFromJson(Map json) => + _$ResendDataDtoImpl( + otpToken: json['otp_token'] as String?, + expiresIn: (json['expires_in'] as num?)?.toInt(), + nextResendIn: (json['next_resend_in'] as num?)?.toInt(), + ); + +Map _$$ResendDataDtoImplToJson(_$ResendDataDtoImpl instance) => + { + 'otp_token': instance.otpToken, + 'expires_in': instance.expiresIn, + 'next_resend_in': instance.nextResendIn, + }; diff --git a/lib/infrastructure/auth/datasources/local_data_provider.dart b/lib/infrastructure/auth/datasources/local_data_provider.dart new file mode 100644 index 0000000..26c460c --- /dev/null +++ b/lib/infrastructure/auth/datasources/local_data_provider.dart @@ -0,0 +1,60 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../common/constant/local_storage_key.dart'; +import '../../../domain/auth/auth.dart'; +import '../auth_dtos.dart'; + +@injectable +class AuthLocalDataProvider { + final SharedPreferences _sharedPreferences; + final String _logName = 'AuthLocalDataProvider'; + + AuthLocalDataProvider(this._sharedPreferences); + + Future saveToken(String token) async { + await _sharedPreferences.setString(LocalStorageKey.token, token); + } + + Future getToken() async { + return _sharedPreferences.getString(LocalStorageKey.token); + } + + Future deleteToken() async { + await _sharedPreferences.remove(LocalStorageKey.token); + } + + Future hasToken() async { + return _sharedPreferences.containsKey(LocalStorageKey.token); + } + + Future saveCurrentUser(UserDto user) async { + final userJsonString = jsonEncode(user.toJson()); + await _sharedPreferences.setString(LocalStorageKey.user, userJsonString); + } + + Future currentUser() async { + final userString = _sharedPreferences.getString(LocalStorageKey.user); + if (userString == null) return User.empty(); + + final Map userMap = jsonDecode(userString); + final userDto = UserDto.fromJson(userMap); + return userDto.toDomain(); + } + + Future deleteCurrentUser() async { + await _sharedPreferences.remove(LocalStorageKey.user); + } + + Future deleteAllAuth() async { + try { + await _sharedPreferences.remove(LocalStorageKey.token); + await _sharedPreferences.remove(LocalStorageKey.user); + } catch (e) { + log('deleteAllAuthError', name: _logName, error: e); + } + } +} diff --git a/lib/infrastructure/auth/datasources/remote_data_provider.dart b/lib/infrastructure/auth/datasources/remote_data_provider.dart new file mode 100644 index 0000000..579a3ae --- /dev/null +++ b/lib/infrastructure/auth/datasources/remote_data_provider.dart @@ -0,0 +1,263 @@ +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; +import 'package:data_channel/data_channel.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/extension/extension.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/auth/auth.dart'; +import '../auth_dtos.dart'; + +@injectable +class AuthRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = "AuthRemoteDataProvider"; + + AuthRemoteDataProvider(this._apiClient); + + Future> checkPhone({ + required String phoneNumber, + }) async { + try { + final response = await _apiClient.post( + ApiPath.checkPhone, + data: {'phone_number': phoneNumber}, + ); + + if (response.data['code'] == 401) { + return DC.error( + AuthFailure.serverError( + ApiFailure.unauthorized('Incorrect email or password'), + ), + ); + } + + if (response.data['success'] == false) { + if ((response.data['errors'] as List).isNotEmpty) { + if (response.data['errors'][0]['code'] == 303) { + return DC.error( + AuthFailure.dynamicErrorMessage('No. Telepon Tidak Boleh Kosong'), + ); + } + if (response.data['errors'][0]['code'] == 304) { + return DC.error( + AuthFailure.dynamicErrorMessage( + response.data['errors'][0]['cause'], + ), + ); + } + } + } + + final dto = CheckPhoneDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('checkPhone', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } + + Future> register({ + required String phoneNumber, + required String name, + required DateTime birthDate, + }) async { + try { + final response = await _apiClient.post( + ApiPath.register, + data: { + 'phone_number': phoneNumber, + 'name': name, + 'birth_date': birthDate.toServerDate, + }, + ); + + if (response.data['success'] == false) { + if ((response.data['errors'] as List).isNotEmpty) { + if (response.data['errors'][0]['code'] == "900") { + return DC.error( + AuthFailure.dynamicErrorMessage('No. Telepon Sudah Terdaftar'), + ); + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } + + final dto = RegisterDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('register', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } + + Future> verify({ + required String registrationToken, + required String otpCode, + }) async { + try { + final response = await _apiClient.post( + ApiPath.verify, + data: {'registration_token': registrationToken, 'otp_code': otpCode}, + ); + + if (response.data['success'] == false) { + if ((response.data['errors'] as List).isNotEmpty) { + if (response.data['errors'][0]['code'] == "900") { + return DC.error( + AuthFailure.dynamicErrorMessage('Kode OTP Tidak Sesuai'), + ); + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } + + final dto = VerifyDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('verify', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } + + Future> setPassword({ + required String registrationToken, + required String password, + required String confirmPassword, + }) async { + try { + final response = await _apiClient.post( + ApiPath.setPassword, + data: { + 'registration_token': registrationToken, + 'password': password, + 'confirm_password': confirmPassword, + }, + ); + + if (response.data['success'] == false) { + if ((response.data['errors'] as List).isNotEmpty) { + if (response.data['errors'][0]['code'] == 900) { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Invalid Registration, Lakukan kembali dari awal', + ), + ); + } else if (response.data['errors'][0]['code'] == "304") { + return DC.error( + AuthFailure.dynamicErrorMessage( + response.data['errors'][0]['cause'], + ), + ); + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } + + final dto = LoginDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('setPassword', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } + + Future> login({ + required String phoneNumber, + required String password, + }) async { + try { + final response = await _apiClient.post( + ApiPath.login, + data: {'phone_number': phoneNumber, 'password': password}, + ); + + if (response.data['success'] == false) { + if ((response.data['errors'] as List).isNotEmpty) { + if (response.data['errors'][0]['code'] == "900") { + return DC.error( + AuthFailure.dynamicErrorMessage('Kamu Belum Terdaftar'), + ); + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } else { + return DC.error( + AuthFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + } + + final dto = LoginDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('login', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } + + Future> resend({ + required String phoneNumber, + required String purpose, //login or registration + }) async { + try { + final response = await _apiClient.post( + ApiPath.resend, + data: {'phone_number': phoneNumber, 'purpose': purpose}, + ); + + if (response.data['success'] == false) { + return DC.error( + AuthFailure.dynamicErrorMessage('Terjadi kesalahan coba lagi nanti'), + ); + } + + final dto = ResendDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('resend', name: _logName, error: e, stackTrace: s); + return DC.error(AuthFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/auth/dto/check_phone_dto.dart b/lib/infrastructure/auth/dto/check_phone_dto.dart new file mode 100644 index 0000000..50bfc3d --- /dev/null +++ b/lib/infrastructure/auth/dto/check_phone_dto.dart @@ -0,0 +1,30 @@ +part of '../auth_dtos.dart'; + +@freezed +class CheckPhoneDto with _$CheckPhoneDto { + const CheckPhoneDto._(); + + const factory CheckPhoneDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CheckPhoneDataDto? data, + }) = _CheckPhoneDto; + + factory CheckPhoneDto.fromJson(Map json) => + _$CheckPhoneDtoFromJson(json); + CheckPhone toDomain() => CheckPhone( + status: status?.toCheckPhoneStatus() ?? CheckPhoneStatus.unknown, + message: message ?? '', + phoneNumber: data?.phoneNumber ?? '', + ); +} + +@freezed +class CheckPhoneDataDto with _$CheckPhoneDataDto { + const factory CheckPhoneDataDto({ + @JsonKey(name: 'phone_number') String? phoneNumber, + }) = _CheckPhoneDataDto; + + factory CheckPhoneDataDto.fromJson(Map json) => + _$CheckPhoneDataDtoFromJson(json); +} diff --git a/lib/infrastructure/auth/dto/login_dto.dart b/lib/infrastructure/auth/dto/login_dto.dart new file mode 100644 index 0000000..a3aba23 --- /dev/null +++ b/lib/infrastructure/auth/dto/login_dto.dart @@ -0,0 +1,58 @@ +part of '../auth_dtos.dart'; + +@freezed +class LoginDto with _$LoginDto { + const factory LoginDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') LoginDataDto? data, + }) = _LoginDto; + + factory LoginDto.fromJson(Map json) => + _$LoginDtoFromJson(json); + + const LoginDto._(); + + /// mapping ke domain + Login toDomain() => Login( + status: status ?? '', + message: message ?? '', + accessToken: data?.accessToken ?? '', + refreshToken: data?.refreshToken ?? '', + user: data?.user?.toDomain() ?? User.empty(), + ); +} + +@freezed +class LoginDataDto with _$LoginDataDto { + const factory LoginDataDto({ + @JsonKey(name: 'access_token') String? accessToken, + @JsonKey(name: 'refresh_token') String? refreshToken, + @JsonKey(name: 'user') UserDto? user, + }) = _LoginDataDto; + + factory LoginDataDto.fromJson(Map json) => + _$LoginDataDtoFromJson(json); +} + +@freezed +class UserDto with _$UserDto { + const factory UserDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'phone_number') String? phoneNumber, + @JsonKey(name: 'birth_date') String? birthDate, + }) = _UserDto; + + factory UserDto.fromJson(Map json) => + _$UserDtoFromJson(json); + + const UserDto._(); + + User toDomain() => User( + id: id ?? '', + name: name ?? '', + phoneNumber: phoneNumber ?? '', + birthDate: birthDate ?? '', + ); +} diff --git a/lib/infrastructure/auth/dto/register_dto.dart b/lib/infrastructure/auth/dto/register_dto.dart new file mode 100644 index 0000000..4cadceb --- /dev/null +++ b/lib/infrastructure/auth/dto/register_dto.dart @@ -0,0 +1,36 @@ +part of '../auth_dtos.dart'; + +@freezed +class RegisterDto with _$RegisterDto { + const RegisterDto._(); + + const factory RegisterDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') RegisterDataDto? data, + }) = _RegisterDto; + + factory RegisterDto.fromJson(Map json) => + _$RegisterDtoFromJson(json); + + /// mapping ke domain + Register toDomain() => Register( + status: status ?? '', + message: message ?? '', + registrationToken: data?.registrationToken ?? '', + otpToken: data?.otpToken ?? '', + expiresIn: data?.expiresIn ?? 0, + ); +} + +@freezed +class RegisterDataDto with _$RegisterDataDto { + const factory RegisterDataDto({ + @JsonKey(name: 'registration_token') String? registrationToken, + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + }) = _RegisterDataDto; + + factory RegisterDataDto.fromJson(Map json) => + _$RegisterDataDtoFromJson(json); +} diff --git a/lib/infrastructure/auth/dto/resend_dto.dart b/lib/infrastructure/auth/dto/resend_dto.dart new file mode 100644 index 0000000..863df73 --- /dev/null +++ b/lib/infrastructure/auth/dto/resend_dto.dart @@ -0,0 +1,36 @@ +part of '../auth_dtos.dart'; + +@freezed +class ResendDto with _$ResendDto { + const factory ResendDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') ResendDataDto? data, + }) = _ResendDto; + + factory ResendDto.fromJson(Map json) => + _$ResendDtoFromJson(json); + + const ResendDto._(); + + /// mapping ke domain + Resend toDomain() => Resend( + status: status?.toResendStatus() ?? ResendStatus.unknown, + message: message ?? '', + otpToken: data?.otpToken ?? '', + expiresIn: data?.expiresIn ?? 0, + nextResendIn: data?.nextResendIn ?? 0, + ); +} + +@freezed +class ResendDataDto with _$ResendDataDto { + const factory ResendDataDto({ + @JsonKey(name: 'otp_token') String? otpToken, + @JsonKey(name: 'expires_in') int? expiresIn, + @JsonKey(name: 'next_resend_in') int? nextResendIn, + }) = _ResendDataDto; + + factory ResendDataDto.fromJson(Map json) => + _$ResendDataDtoFromJson(json); +} diff --git a/lib/infrastructure/auth/dto/verify_dto.dart b/lib/infrastructure/auth/dto/verify_dto.dart new file mode 100644 index 0000000..bb9769d --- /dev/null +++ b/lib/infrastructure/auth/dto/verify_dto.dart @@ -0,0 +1,32 @@ +part of '../auth_dtos.dart'; + +@freezed +class VerifyDto with _$VerifyDto { + const factory VerifyDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') VerifyDataDto? data, + }) = _VerifyDto; + + factory VerifyDto.fromJson(Map json) => + _$VerifyDtoFromJson(json); + + const VerifyDto._(); // biar bisa bikin method + + /// mapping ke domain + Verify toDomain() => Verify( + status: status ?? '', + message: message ?? '', + registrationToken: data?.registrationToken ?? '', + ); +} + +@freezed +class VerifyDataDto with _$VerifyDataDto { + const factory VerifyDataDto({ + @JsonKey(name: 'registration_token') String? registrationToken, + }) = _VerifyDataDto; + + factory VerifyDataDto.fromJson(Map json) => + _$VerifyDataDtoFromJson(json); +} diff --git a/lib/infrastructure/auth/repositories/auth_repository.dart b/lib/infrastructure/auth/repositories/auth_repository.dart new file mode 100644 index 0000000..6e0f817 --- /dev/null +++ b/lib/infrastructure/auth/repositories/auth_repository.dart @@ -0,0 +1,197 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/auth/auth.dart'; +import '../datasources/local_data_provider.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: IAuthRepository) +class AuthRepository implements IAuthRepository { + final AuthLocalDataProvider _localDataProvider; + final AuthRemoteDataProvider _remoteDataProvider; + + final String _logName = 'AuthRepository'; + + AuthRepository(this._remoteDataProvider, this._localDataProvider); + + @override + Future> checkPhone({ + required String phoneNumber, + }) async { + try { + final result = await _remoteDataProvider.checkPhone( + phoneNumber: phoneNumber, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + return right(auth); + } catch (e, s) { + log('checkPhoneError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> register({ + required String phoneNumber, + required String name, + required DateTime birthDate, + }) async { + try { + final result = await _remoteDataProvider.register( + phoneNumber: phoneNumber, + name: name, + birthDate: birthDate, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + return right(auth); + } catch (e, s) { + log('registerError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> verify({ + required String registrationToken, + required String otpCode, + }) async { + try { + final result = await _remoteDataProvider.verify( + registrationToken: registrationToken, + otpCode: otpCode, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + return right(auth); + } catch (e, s) { + log('verifyError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> setPassword({ + required String registrationToken, + required String password, + required String confirmPassword, + }) async { + try { + final result = await _remoteDataProvider.setPassword( + registrationToken: registrationToken, + password: password, + confirmPassword: confirmPassword, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + await _localDataProvider.saveToken(auth.accessToken); + await _localDataProvider.saveCurrentUser(result.data!.data!.user!); + + return right(auth); + } catch (e, s) { + log('setPasswordError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> login({ + required String phoneNumber, + required String password, + }) async { + try { + final result = await _remoteDataProvider.login( + phoneNumber: phoneNumber, + password: password, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + await _localDataProvider.saveToken(auth.accessToken); + await _localDataProvider.saveCurrentUser(result.data!.data!.user!); + + return right(auth); + } catch (e, s) { + log('loginError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> resend({ + required String phoneNumber, + required String purpose, + }) async { + try { + final result = await _remoteDataProvider.resend( + phoneNumber: phoneNumber, + purpose: purpose, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.toDomain(); + + return right(auth); + } catch (e, s) { + log('resendError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future> currentUser() async { + try { + User user = await _localDataProvider.currentUser(); + return right(user); + } catch (e, s) { + log('currentUserError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } + + @override + Future hasToken() async { + return await _localDataProvider.hasToken(); + } + + @override + Future> logout() async { + try { + await _localDataProvider.deleteAllAuth(); + return right(unit); + } catch (e, s) { + log('logoutError', name: _logName, error: e, stackTrace: s); + return left(const AuthFailure.unexpectedError()); + } + } +} diff --git a/lib/infrastructure/customer/customer_dtos.dart b/lib/infrastructure/customer/customer_dtos.dart new file mode 100644 index 0000000..c53f3ec --- /dev/null +++ b/lib/infrastructure/customer/customer_dtos.dart @@ -0,0 +1,8 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/customer/customer.dart'; + +part 'customer_dtos.freezed.dart'; +part 'customer_dtos.g.dart'; + +part 'dtos/customer_point_dto.dart'; diff --git a/lib/infrastructure/customer/customer_dtos.freezed.dart b/lib/infrastructure/customer/customer_dtos.freezed.dart new file mode 100644 index 0000000..86a7403 --- /dev/null +++ b/lib/infrastructure/customer/customer_dtos.freezed.dart @@ -0,0 +1,443 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'customer_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +CustomerPointDto _$CustomerPointDtoFromJson(Map json) { + return _CustomerPointDto.fromJson(json); +} + +/// @nodoc +mixin _$CustomerPointDto { + @JsonKey(name: 'status') + String? get status => throw _privateConstructorUsedError; + @JsonKey(name: 'message') + String? get message => throw _privateConstructorUsedError; + @JsonKey(name: 'data') + CustomerPointDataDto? get data => throw _privateConstructorUsedError; + + /// Serializes this CustomerPointDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerPointDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerPointDtoCopyWith<$Res> { + factory $CustomerPointDtoCopyWith( + CustomerPointDto value, + $Res Function(CustomerPointDto) then, + ) = _$CustomerPointDtoCopyWithImpl<$Res, CustomerPointDto>; + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CustomerPointDataDto? data, + }); + + $CustomerPointDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class _$CustomerPointDtoCopyWithImpl<$Res, $Val extends CustomerPointDto> + implements $CustomerPointDtoCopyWith<$Res> { + _$CustomerPointDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as CustomerPointDataDto?, + ) + as $Val, + ); + } + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $CustomerPointDataDtoCopyWith<$Res>? get data { + if (_value.data == null) { + return null; + } + + return $CustomerPointDataDtoCopyWith<$Res>(_value.data!, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$CustomerPointDtoImplCopyWith<$Res> + implements $CustomerPointDtoCopyWith<$Res> { + factory _$$CustomerPointDtoImplCopyWith( + _$CustomerPointDtoImpl value, + $Res Function(_$CustomerPointDtoImpl) then, + ) = __$$CustomerPointDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CustomerPointDataDto? data, + }); + + @override + $CustomerPointDataDtoCopyWith<$Res>? get data; +} + +/// @nodoc +class __$$CustomerPointDtoImplCopyWithImpl<$Res> + extends _$CustomerPointDtoCopyWithImpl<$Res, _$CustomerPointDtoImpl> + implements _$$CustomerPointDtoImplCopyWith<$Res> { + __$$CustomerPointDtoImplCopyWithImpl( + _$CustomerPointDtoImpl _value, + $Res Function(_$CustomerPointDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? message = freezed, + Object? data = freezed, + }) { + return _then( + _$CustomerPointDtoImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + data: freezed == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as CustomerPointDataDto?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$CustomerPointDtoImpl extends _CustomerPointDto { + const _$CustomerPointDtoImpl({ + @JsonKey(name: 'status') this.status, + @JsonKey(name: 'message') this.message, + @JsonKey(name: 'data') this.data, + }) : super._(); + + factory _$CustomerPointDtoImpl.fromJson(Map json) => + _$$CustomerPointDtoImplFromJson(json); + + @override + @JsonKey(name: 'status') + final String? status; + @override + @JsonKey(name: 'message') + final String? message; + @override + @JsonKey(name: 'data') + final CustomerPointDataDto? data; + + @override + String toString() { + return 'CustomerPointDto(status: $status, message: $message, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CustomerPointDtoImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.message, message) || other.message == message) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, status, message, data); + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CustomerPointDtoImplCopyWith<_$CustomerPointDtoImpl> get copyWith => + __$$CustomerPointDtoImplCopyWithImpl<_$CustomerPointDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$CustomerPointDtoImplToJson(this); + } +} + +abstract class _CustomerPointDto extends CustomerPointDto { + const factory _CustomerPointDto({ + @JsonKey(name: 'status') final String? status, + @JsonKey(name: 'message') final String? message, + @JsonKey(name: 'data') final CustomerPointDataDto? data, + }) = _$CustomerPointDtoImpl; + const _CustomerPointDto._() : super._(); + + factory _CustomerPointDto.fromJson(Map json) = + _$CustomerPointDtoImpl.fromJson; + + @override + @JsonKey(name: 'status') + String? get status; + @override + @JsonKey(name: 'message') + String? get message; + @override + @JsonKey(name: 'data') + CustomerPointDataDto? get data; + + /// Create a copy of CustomerPointDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CustomerPointDtoImplCopyWith<_$CustomerPointDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CustomerPointDataDto _$CustomerPointDataDtoFromJson(Map json) { + return _CustomerPointDataDto.fromJson(json); +} + +/// @nodoc +mixin _$CustomerPointDataDto { + @JsonKey(name: 'total_points') + int? get totalPoints => throw _privateConstructorUsedError; + @JsonKey(name: 'last_updated') + String? get lastUpdated => throw _privateConstructorUsedError; + + /// Serializes this CustomerPointDataDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CustomerPointDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerPointDataDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerPointDataDtoCopyWith<$Res> { + factory $CustomerPointDataDtoCopyWith( + CustomerPointDataDto value, + $Res Function(CustomerPointDataDto) then, + ) = _$CustomerPointDataDtoCopyWithImpl<$Res, CustomerPointDataDto>; + @useResult + $Res call({ + @JsonKey(name: 'total_points') int? totalPoints, + @JsonKey(name: 'last_updated') String? lastUpdated, + }); +} + +/// @nodoc +class _$CustomerPointDataDtoCopyWithImpl< + $Res, + $Val extends CustomerPointDataDto +> + implements $CustomerPointDataDtoCopyWith<$Res> { + _$CustomerPointDataDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerPointDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? totalPoints = freezed, Object? lastUpdated = freezed}) { + return _then( + _value.copyWith( + totalPoints: freezed == totalPoints + ? _value.totalPoints + : totalPoints // ignore: cast_nullable_to_non_nullable + as int?, + lastUpdated: freezed == lastUpdated + ? _value.lastUpdated + : lastUpdated // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CustomerPointDataDtoImplCopyWith<$Res> + implements $CustomerPointDataDtoCopyWith<$Res> { + factory _$$CustomerPointDataDtoImplCopyWith( + _$CustomerPointDataDtoImpl value, + $Res Function(_$CustomerPointDataDtoImpl) then, + ) = __$$CustomerPointDataDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'total_points') int? totalPoints, + @JsonKey(name: 'last_updated') String? lastUpdated, + }); +} + +/// @nodoc +class __$$CustomerPointDataDtoImplCopyWithImpl<$Res> + extends _$CustomerPointDataDtoCopyWithImpl<$Res, _$CustomerPointDataDtoImpl> + implements _$$CustomerPointDataDtoImplCopyWith<$Res> { + __$$CustomerPointDataDtoImplCopyWithImpl( + _$CustomerPointDataDtoImpl _value, + $Res Function(_$CustomerPointDataDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CustomerPointDataDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? totalPoints = freezed, Object? lastUpdated = freezed}) { + return _then( + _$CustomerPointDataDtoImpl( + totalPoints: freezed == totalPoints + ? _value.totalPoints + : totalPoints // ignore: cast_nullable_to_non_nullable + as int?, + lastUpdated: freezed == lastUpdated + ? _value.lastUpdated + : lastUpdated // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$CustomerPointDataDtoImpl implements _CustomerPointDataDto { + const _$CustomerPointDataDtoImpl({ + @JsonKey(name: 'total_points') this.totalPoints, + @JsonKey(name: 'last_updated') this.lastUpdated, + }); + + factory _$CustomerPointDataDtoImpl.fromJson(Map json) => + _$$CustomerPointDataDtoImplFromJson(json); + + @override + @JsonKey(name: 'total_points') + final int? totalPoints; + @override + @JsonKey(name: 'last_updated') + final String? lastUpdated; + + @override + String toString() { + return 'CustomerPointDataDto(totalPoints: $totalPoints, lastUpdated: $lastUpdated)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CustomerPointDataDtoImpl && + (identical(other.totalPoints, totalPoints) || + other.totalPoints == totalPoints) && + (identical(other.lastUpdated, lastUpdated) || + other.lastUpdated == lastUpdated)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, totalPoints, lastUpdated); + + /// Create a copy of CustomerPointDataDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CustomerPointDataDtoImplCopyWith<_$CustomerPointDataDtoImpl> + get copyWith => + __$$CustomerPointDataDtoImplCopyWithImpl<_$CustomerPointDataDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$CustomerPointDataDtoImplToJson(this); + } +} + +abstract class _CustomerPointDataDto implements CustomerPointDataDto { + const factory _CustomerPointDataDto({ + @JsonKey(name: 'total_points') final int? totalPoints, + @JsonKey(name: 'last_updated') final String? lastUpdated, + }) = _$CustomerPointDataDtoImpl; + + factory _CustomerPointDataDto.fromJson(Map json) = + _$CustomerPointDataDtoImpl.fromJson; + + @override + @JsonKey(name: 'total_points') + int? get totalPoints; + @override + @JsonKey(name: 'last_updated') + String? get lastUpdated; + + /// Create a copy of CustomerPointDataDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CustomerPointDataDtoImplCopyWith<_$CustomerPointDataDtoImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/customer/customer_dtos.g.dart b/lib/infrastructure/customer/customer_dtos.g.dart new file mode 100644 index 0000000..2a573d1 --- /dev/null +++ b/lib/infrastructure/customer/customer_dtos.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'customer_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$CustomerPointDtoImpl _$$CustomerPointDtoImplFromJson( + Map json, +) => _$CustomerPointDtoImpl( + status: json['status'] as String?, + message: json['message'] as String?, + data: json['data'] == null + ? null + : CustomerPointDataDto.fromJson(json['data'] as Map), +); + +Map _$$CustomerPointDtoImplToJson( + _$CustomerPointDtoImpl instance, +) => { + 'status': instance.status, + 'message': instance.message, + 'data': instance.data, +}; + +_$CustomerPointDataDtoImpl _$$CustomerPointDataDtoImplFromJson( + Map json, +) => _$CustomerPointDataDtoImpl( + totalPoints: (json['total_points'] as num?)?.toInt(), + lastUpdated: json['last_updated'] as String?, +); + +Map _$$CustomerPointDataDtoImplToJson( + _$CustomerPointDataDtoImpl instance, +) => { + 'total_points': instance.totalPoints, + 'last_updated': instance.lastUpdated, +}; diff --git a/lib/infrastructure/customer/datasources/remote_data_provider.dart b/lib/infrastructure/customer/datasources/remote_data_provider.dart new file mode 100644 index 0000000..1af9a17 --- /dev/null +++ b/lib/infrastructure/customer/datasources/remote_data_provider.dart @@ -0,0 +1,50 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/function/app_function.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/customer/customer.dart'; +import '../customer_dtos.dart'; + +@injectable +class CustomerRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = "CustomerRemoteDataProvider"; + + CustomerRemoteDataProvider(this._apiClient); + + Future> fetchCustomerPoint() async { + try { + final response = await _apiClient.get( + ApiPath.customerPoint, + headers: getAuthorizationHeader(), + ); + + if (response.data['code'] == 401) { + return DC.error( + CustomerFailure.serverError( + ApiFailure.unauthorized('Session Expired'), + ), + ); + } + + if (response.data['status'] == false) { + return DC.error( + CustomerFailure.dynamicErrorMessage( + 'Terjadi kesalahan coba lagi nanti', + ), + ); + } + + final dto = CustomerPointDto.fromJson(response.data['data']); + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('fetchCustomerPoint', name: _logName, error: e, stackTrace: s); + return DC.error(CustomerFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/customer/dtos/customer_point_dto.dart b/lib/infrastructure/customer/dtos/customer_point_dto.dart new file mode 100644 index 0000000..9d9515e --- /dev/null +++ b/lib/infrastructure/customer/dtos/customer_point_dto.dart @@ -0,0 +1,34 @@ +part of '../customer_dtos.dart'; + +@freezed +class CustomerPointDto with _$CustomerPointDto { + const factory CustomerPointDto({ + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'message') String? message, + @JsonKey(name: 'data') CustomerPointDataDto? data, + }) = _CustomerPointDto; + + factory CustomerPointDto.fromJson(Map json) => + _$CustomerPointDtoFromJson(json); + + const CustomerPointDto._(); + + /// mapping ke domain + CustomerPoint toDomain() => CustomerPoint( + status: status ?? '', + message: message ?? '', + totalPoints: data?.totalPoints ?? 0, + lastUpdated: data?.lastUpdated ?? '', + ); +} + +@freezed +class CustomerPointDataDto with _$CustomerPointDataDto { + const factory CustomerPointDataDto({ + @JsonKey(name: 'total_points') int? totalPoints, + @JsonKey(name: 'last_updated') String? lastUpdated, + }) = _CustomerPointDataDto; + + factory CustomerPointDataDto.fromJson(Map json) => + _$CustomerPointDataDtoFromJson(json); +} diff --git a/lib/infrastructure/customer/repositories/customer_repository.dart b/lib/infrastructure/customer/repositories/customer_repository.dart new file mode 100644 index 0000000..18cebfe --- /dev/null +++ b/lib/infrastructure/customer/repositories/customer_repository.dart @@ -0,0 +1,33 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/customer/customer.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: ICustomerRepository) +class CustomerRepository implements ICustomerRepository { + final CustomerRemoteDataProvider _remoteDataProvider; + + final String _logName = 'CustomerRepository'; + + CustomerRepository(this._remoteDataProvider); + + @override + Future> getPoints() async { + try { + final result = await _remoteDataProvider.fetchCustomerPoint(); + + if (result.hasError) { + return left(result.error!); + } + + final data = result.data!.toDomain(); + return right(data); + } catch (e, s) { + log('getPoints', name: _logName, error: e, stackTrace: s); + return left(const CustomerFailure.unexpectedError()); + } + } +} diff --git a/lib/infrastructure/game/datasources/remote_data_provider.dart b/lib/infrastructure/game/datasources/remote_data_provider.dart new file mode 100644 index 0000000..8df1131 --- /dev/null +++ b/lib/infrastructure/game/datasources/remote_data_provider.dart @@ -0,0 +1,47 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/function/app_function.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/game/game.dart'; +import '../game_dtos.dart'; + +@injectable +class GameRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = "GameRemoteDataProvider"; + + GameRemoteDataProvider(this._apiClient); + + Future> ferrisWheel() async { + try { + final response = await _apiClient.get( + ApiPath.ferrisWheel, + headers: getAuthorizationHeader(), + ); + + if (response.data['code'] == 401) { + return DC.error( + GameFailure.serverError(ApiFailure.unauthorized('Session Expired')), + ); + } + + if (response.data['status'] == false) { + return DC.error( + GameFailure.dynamicErrorMessage('Terjadi kesalahan coba lagi nanti'), + ); + } + + final dto = GameDto.fromJson(response.data['data']['data']['game']); + + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('ferrisWheel', name: _logName, error: e, stackTrace: s); + return DC.error(GameFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/game/dto/game_dto.dart b/lib/infrastructure/game/dto/game_dto.dart new file mode 100644 index 0000000..f366d86 --- /dev/null +++ b/lib/infrastructure/game/dto/game_dto.dart @@ -0,0 +1,31 @@ +part of '../game_dtos.dart'; + +@freezed +class GameDto with _$GameDto { + const factory GameDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }) = _GameDto; + + factory GameDto.fromJson(Map json) => + _$GameDtoFromJson(json); + + const GameDto._(); + + Game toDomain() => Game( + id: id ?? '', + name: name ?? '', + type: type ?? '', + isActive: isActive ?? false, + metadata: metadata ?? {}, + prizes: prizes?.map((e) => e.toDomain()).toList() ?? [], + createdAt: createdAt ?? '', + updatedAt: updatedAt ?? '', + ); +} diff --git a/lib/infrastructure/game/dto/game_prize_dto.dart b/lib/infrastructure/game/dto/game_prize_dto.dart new file mode 100644 index 0000000..6ff0a5d --- /dev/null +++ b/lib/infrastructure/game/dto/game_prize_dto.dart @@ -0,0 +1,27 @@ +part of '../game_dtos.dart'; + +@freezed +class GamePrizeDto with _$GamePrizeDto { + const factory GamePrizeDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'game_id') String? gameId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }) = _GamePrizeDto; + + factory GamePrizeDto.fromJson(Map json) => + _$GamePrizeDtoFromJson(json); + + const GamePrizeDto._(); + + GamePrize toDomain() => GamePrize( + id: id ?? '', + gameId: gameId ?? '', + name: name ?? '', + metadata: metadata ?? {}, + createdAt: createdAt ?? '', + updatedAt: updatedAt ?? '', + ); +} diff --git a/lib/infrastructure/game/game_dtos.dart b/lib/infrastructure/game/game_dtos.dart new file mode 100644 index 0000000..899b9fa --- /dev/null +++ b/lib/infrastructure/game/game_dtos.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/game/game.dart'; + +part 'game_dtos.freezed.dart'; +part 'game_dtos.g.dart'; + +part 'dto/game_dto.dart'; +part 'dto/game_prize_dto.dart'; diff --git a/lib/infrastructure/game/game_dtos.freezed.dart b/lib/infrastructure/game/game_dtos.freezed.dart new file mode 100644 index 0000000..3774c5f --- /dev/null +++ b/lib/infrastructure/game/game_dtos.freezed.dart @@ -0,0 +1,671 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, 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, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'game_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +GameDto _$GameDtoFromJson(Map json) { + return _GameDto.fromJson(json); +} + +/// @nodoc +mixin _$GameDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'type') + String? get type => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool? get isActive => throw _privateConstructorUsedError; + @JsonKey(name: 'metadata') + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'prizes') + List? get prizes => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + String? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + String? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this GameDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GameDtoCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GameDtoCopyWith<$Res> { + factory $GameDtoCopyWith(GameDto value, $Res Function(GameDto) then) = + _$GameDtoCopyWithImpl<$Res, GameDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class _$GameDtoCopyWithImpl<$Res, $Val extends GameDto> + implements $GameDtoCopyWith<$Res> { + _$GameDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? type = freezed, + Object? isActive = freezed, + Object? metadata = freezed, + Object? prizes = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + prizes: freezed == prizes + ? _value.prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GameDtoImplCopyWith<$Res> implements $GameDtoCopyWith<$Res> { + factory _$$GameDtoImplCopyWith( + _$GameDtoImpl value, + $Res Function(_$GameDtoImpl) then, + ) = __$$GameDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'type') String? type, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'prizes') List? prizes, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class __$$GameDtoImplCopyWithImpl<$Res> + extends _$GameDtoCopyWithImpl<$Res, _$GameDtoImpl> + implements _$$GameDtoImplCopyWith<$Res> { + __$$GameDtoImplCopyWithImpl( + _$GameDtoImpl _value, + $Res Function(_$GameDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? name = freezed, + Object? type = freezed, + Object? isActive = freezed, + Object? metadata = freezed, + Object? prizes = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$GameDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + prizes: freezed == prizes + ? _value._prizes + : prizes // ignore: cast_nullable_to_non_nullable + as List?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$GameDtoImpl extends _GameDto { + const _$GameDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'type') this.type, + @JsonKey(name: 'is_active') this.isActive, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'prizes') final List? prizes, + @JsonKey(name: 'created_at') this.createdAt, + @JsonKey(name: 'updated_at') this.updatedAt, + }) : _metadata = metadata, + _prizes = prizes, + super._(); + + factory _$GameDtoImpl.fromJson(Map json) => + _$$GameDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'name') + final String? name; + @override + @JsonKey(name: 'type') + final String? type; + @override + @JsonKey(name: 'is_active') + final bool? isActive; + final Map? _metadata; + @override + @JsonKey(name: 'metadata') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + final List? _prizes; + @override + @JsonKey(name: 'prizes') + List? get prizes { + final value = _prizes; + if (value == null) return null; + if (_prizes is EqualUnmodifiableListView) return _prizes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey(name: 'created_at') + final String? createdAt; + @override + @JsonKey(name: 'updated_at') + final String? updatedAt; + + @override + String toString() { + return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GameDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.type, type) || other.type == type) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + const DeepCollectionEquality().equals(other._prizes, _prizes) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + type, + isActive, + const DeepCollectionEquality().hash(_metadata), + const DeepCollectionEquality().hash(_prizes), + createdAt, + updatedAt, + ); + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GameDtoImplCopyWith<_$GameDtoImpl> get copyWith => + __$$GameDtoImplCopyWithImpl<_$GameDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$GameDtoImplToJson(this); + } +} + +abstract class _GameDto extends GameDto { + const factory _GameDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'type') final String? type, + @JsonKey(name: 'is_active') final bool? isActive, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'prizes') final List? prizes, + @JsonKey(name: 'created_at') final String? createdAt, + @JsonKey(name: 'updated_at') final String? updatedAt, + }) = _$GameDtoImpl; + const _GameDto._() : super._(); + + factory _GameDto.fromJson(Map json) = _$GameDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'type') + String? get type; + @override + @JsonKey(name: 'is_active') + bool? get isActive; + @override + @JsonKey(name: 'metadata') + Map? get metadata; + @override + @JsonKey(name: 'prizes') + List? get prizes; + @override + @JsonKey(name: 'created_at') + String? get createdAt; + @override + @JsonKey(name: 'updated_at') + String? get updatedAt; + + /// Create a copy of GameDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GameDtoImplCopyWith<_$GameDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +GamePrizeDto _$GamePrizeDtoFromJson(Map json) { + return _GamePrizeDto.fromJson(json); +} + +/// @nodoc +mixin _$GamePrizeDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'game_id') + String? get gameId => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'metadata') + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + String? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + String? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this GamePrizeDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $GamePrizeDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GamePrizeDtoCopyWith<$Res> { + factory $GamePrizeDtoCopyWith( + GamePrizeDto value, + $Res Function(GamePrizeDto) then, + ) = _$GamePrizeDtoCopyWithImpl<$Res, GamePrizeDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'game_id') String? gameId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class _$GamePrizeDtoCopyWithImpl<$Res, $Val extends GamePrizeDto> + implements $GamePrizeDtoCopyWith<$Res> { + _$GamePrizeDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? gameId = freezed, + Object? name = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + gameId: freezed == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$GamePrizeDtoImplCopyWith<$Res> + implements $GamePrizeDtoCopyWith<$Res> { + factory _$$GamePrizeDtoImplCopyWith( + _$GamePrizeDtoImpl value, + $Res Function(_$GamePrizeDtoImpl) then, + ) = __$$GamePrizeDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'game_id') String? gameId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'updated_at') String? updatedAt, + }); +} + +/// @nodoc +class __$$GamePrizeDtoImplCopyWithImpl<$Res> + extends _$GamePrizeDtoCopyWithImpl<$Res, _$GamePrizeDtoImpl> + implements _$$GamePrizeDtoImplCopyWith<$Res> { + __$$GamePrizeDtoImplCopyWithImpl( + _$GamePrizeDtoImpl _value, + $Res Function(_$GamePrizeDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? gameId = freezed, + Object? name = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$GamePrizeDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + gameId: freezed == gameId + ? _value.gameId + : gameId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$GamePrizeDtoImpl extends _GamePrizeDto { + const _$GamePrizeDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'game_id') this.gameId, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') this.createdAt, + @JsonKey(name: 'updated_at') this.updatedAt, + }) : _metadata = metadata, + super._(); + + factory _$GamePrizeDtoImpl.fromJson(Map json) => + _$$GamePrizeDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'game_id') + final String? gameId; + @override + @JsonKey(name: 'name') + final String? name; + final Map? _metadata; + @override + @JsonKey(name: 'metadata') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'created_at') + final String? createdAt; + @override + @JsonKey(name: 'updated_at') + final String? updatedAt; + + @override + String toString() { + return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GamePrizeDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.gameId, gameId) || other.gameId == gameId) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + gameId, + name, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GamePrizeDtoImplCopyWith<_$GamePrizeDtoImpl> get copyWith => + __$$GamePrizeDtoImplCopyWithImpl<_$GamePrizeDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$GamePrizeDtoImplToJson(this); + } +} + +abstract class _GamePrizeDto extends GamePrizeDto { + const factory _GamePrizeDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'game_id') final String? gameId, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') final String? createdAt, + @JsonKey(name: 'updated_at') final String? updatedAt, + }) = _$GamePrizeDtoImpl; + const _GamePrizeDto._() : super._(); + + factory _GamePrizeDto.fromJson(Map json) = + _$GamePrizeDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'game_id') + String? get gameId; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'metadata') + Map? get metadata; + @override + @JsonKey(name: 'created_at') + String? get createdAt; + @override + @JsonKey(name: 'updated_at') + String? get updatedAt; + + /// Create a copy of GamePrizeDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GamePrizeDtoImplCopyWith<_$GamePrizeDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/game/game_dtos.g.dart b/lib/infrastructure/game/game_dtos.g.dart new file mode 100644 index 0000000..e159635 --- /dev/null +++ b/lib/infrastructure/game/game_dtos.g.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'game_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$GameDtoImpl _$$GameDtoImplFromJson(Map json) => + _$GameDtoImpl( + id: json['id'] as String?, + name: json['name'] as String?, + type: json['type'] as String?, + isActive: json['is_active'] as bool?, + metadata: json['metadata'] as Map?, + prizes: (json['prizes'] as List?) + ?.map((e) => GamePrizeDto.fromJson(e as Map)) + .toList(), + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, + ); + +Map _$$GameDtoImplToJson(_$GameDtoImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'type': instance.type, + 'is_active': instance.isActive, + 'metadata': instance.metadata, + 'prizes': instance.prizes, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + }; + +_$GamePrizeDtoImpl _$$GamePrizeDtoImplFromJson(Map json) => + _$GamePrizeDtoImpl( + id: json['id'] as String?, + gameId: json['game_id'] as String?, + name: json['name'] as String?, + metadata: json['metadata'] as Map?, + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, + ); + +Map _$$GamePrizeDtoImplToJson(_$GamePrizeDtoImpl instance) => + { + 'id': instance.id, + 'game_id': instance.gameId, + 'name': instance.name, + 'metadata': instance.metadata, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/infrastructure/game/repositories/game_repository.dart b/lib/infrastructure/game/repositories/game_repository.dart new file mode 100644 index 0000000..b038d26 --- /dev/null +++ b/lib/infrastructure/game/repositories/game_repository.dart @@ -0,0 +1,34 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/game/game.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: IGameRepository) +class GameRepository implements IGameRepository { + final GameRemoteDataProvider _remoteDataProvider; + + final String _logName = 'GameRepository'; + + GameRepository(this._remoteDataProvider); + + @override + Future> ferrisWheel() async { + try { + final result = await _remoteDataProvider.ferrisWheel(); + + if (result.hasError) { + return left(result.error!); + } + + final data = result.data!.toDomain(); + + return right(data); + } catch (e, s) { + log('ferrisWheel', name: _logName, error: e, stackTrace: s); + return left(const GameFailure.unexpectedError()); + } + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart new file mode 100644 index 0000000..5bcec36 --- /dev/null +++ b/lib/injection.config.dart @@ -0,0 +1,155 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:connectivity_plus/connectivity_plus.dart' as _i895; +import 'package:dio/dio.dart' as _i361; +import 'package:enaklo/application/auth/auth_bloc.dart' as _i771; +import 'package:enaklo/application/auth/check_phone_form/check_phone_form_bloc.dart' + as _i869; +import 'package:enaklo/application/auth/login_form/login_form_bloc.dart' + as _i510; +import 'package:enaklo/application/auth/logout_form/logout_form_bloc.dart' + as _i216; +import 'package:enaklo/application/auth/register_form/register_form_bloc.dart' + as _i260; +import 'package:enaklo/application/auth/resend_form/resend_form_bloc.dart' + as _i627; +import 'package:enaklo/application/auth/set_password/set_password_form_bloc.dart' + as _i174; +import 'package:enaklo/application/auth/verify_form/verify_form_bloc.dart' + as _i521; +import 'package:enaklo/application/customer/customer_point_loader/customer_point_loader_bloc.dart' + as _i497; +import 'package:enaklo/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart' + as _i1013; +import 'package:enaklo/common/api/api_client.dart' as _i842; +import 'package:enaklo/common/di/di_auto_route.dart' as _i619; +import 'package:enaklo/common/di/di_connectivity.dart' as _i644; +import 'package:enaklo/common/di/di_dio.dart' as _i842; +import 'package:enaklo/common/di/di_shared_preferences.dart' as _i672; +import 'package:enaklo/common/network/network_client.dart' as _i109; +import 'package:enaklo/domain/auth/auth.dart' as _i995; +import 'package:enaklo/domain/customer/customer.dart' as _i898; +import 'package:enaklo/domain/game/game.dart' as _i96; +import 'package:enaklo/env.dart' as _i372; +import 'package:enaklo/infrastructure/auth/datasources/local_data_provider.dart' + as _i1003; +import 'package:enaklo/infrastructure/auth/datasources/remote_data_provider.dart' + as _i818; +import 'package:enaklo/infrastructure/auth/repositories/auth_repository.dart' + as _i879; +import 'package:enaklo/infrastructure/customer/datasources/remote_data_provider.dart' + as _i89; +import 'package:enaklo/infrastructure/customer/repositories/customer_repository.dart' + as _i118; +import 'package:enaklo/infrastructure/game/datasources/remote_data_provider.dart' + as _i143; +import 'package:enaklo/infrastructure/game/repositories/game_repository.dart' + as _i547; +import 'package:enaklo/presentation/router/app_router.dart' as _i698; +import 'package:get_it/get_it.dart' as _i174; +import 'package:injectable/injectable.dart' as _i526; +import 'package:shared_preferences/shared_preferences.dart' as _i460; + +const String _dev = 'dev'; +const String _prod = 'prod'; + +extension GetItInjectableX on _i174.GetIt { + // initializes the registration of main-scope dependencies inside of GetIt + Future<_i174.GetIt> init({ + String? environment, + _i526.EnvironmentFilter? environmentFilter, + }) async { + final gh = _i526.GetItHelper(this, environment, environmentFilter); + final sharedPreferencesDi = _$SharedPreferencesDi(); + final dioDi = _$DioDi(); + final autoRouteDi = _$AutoRouteDi(); + final connectivityDi = _$ConnectivityDi(); + await gh.factoryAsync<_i460.SharedPreferences>( + () => sharedPreferencesDi.prefs, + preResolve: true, + ); + gh.lazySingleton<_i361.Dio>(() => dioDi.dio); + gh.lazySingleton<_i698.AppRouter>(() => autoRouteDi.appRouter); + gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity); + gh.lazySingleton<_i109.NetworkClient>( + () => _i109.NetworkClient(gh<_i895.Connectivity>()), + ); + gh.factory<_i372.Env>(() => _i372.DevEnv(), registerFor: {_dev}); + gh.factory<_i1003.AuthLocalDataProvider>( + () => _i1003.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), + ); + gh.factory<_i372.Env>(() => _i372.ProdEnv(), registerFor: {_prod}); + gh.lazySingleton<_i842.ApiClient>( + () => _i842.ApiClient(gh<_i361.Dio>(), gh<_i372.Env>()), + ); + gh.factory<_i818.AuthRemoteDataProvider>( + () => _i818.AuthRemoteDataProvider(gh<_i842.ApiClient>()), + ); + gh.factory<_i143.GameRemoteDataProvider>( + () => _i143.GameRemoteDataProvider(gh<_i842.ApiClient>()), + ); + gh.factory<_i89.CustomerRemoteDataProvider>( + () => _i89.CustomerRemoteDataProvider(gh<_i842.ApiClient>()), + ); + gh.factory<_i96.IGameRepository>( + () => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()), + ); + gh.factory<_i1013.FerrisWheelLoaderBloc>( + () => _i1013.FerrisWheelLoaderBloc(gh<_i96.IGameRepository>()), + ); + gh.factory<_i995.IAuthRepository>( + () => _i879.AuthRepository( + gh<_i818.AuthRemoteDataProvider>(), + gh<_i1003.AuthLocalDataProvider>(), + ), + ); + gh.factory<_i627.ResendFormBloc>( + () => _i627.ResendFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i174.SetPasswordFormBloc>( + () => _i174.SetPasswordFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i260.RegisterFormBloc>( + () => _i260.RegisterFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i869.CheckPhoneFormBloc>( + () => _i869.CheckPhoneFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i521.VerifyFormBloc>( + () => _i521.VerifyFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i771.AuthBloc>( + () => _i771.AuthBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i216.LogoutFormBloc>( + () => _i216.LogoutFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i898.ICustomerRepository>( + () => _i118.CustomerRepository(gh<_i89.CustomerRemoteDataProvider>()), + ); + gh.factory<_i510.LoginFormBloc>( + () => _i510.LoginFormBloc(gh<_i995.IAuthRepository>()), + ); + gh.factory<_i497.CustomerPointLoaderBloc>( + () => _i497.CustomerPointLoaderBloc(gh<_i898.ICustomerRepository>()), + ); + return this; + } +} + +class _$SharedPreferencesDi extends _i672.SharedPreferencesDi {} + +class _$DioDi extends _i842.DioDi {} + +class _$AutoRouteDi extends _i619.AutoRouteDi {} + +class _$ConnectivityDi extends _i644.ConnectivityDi {} diff --git a/lib/injection.dart b/lib/injection.dart new file mode 100644 index 0000000..85d0036 --- /dev/null +++ b/lib/injection.dart @@ -0,0 +1,9 @@ +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; + +import 'injection.config.dart'; + +final getIt = GetIt.instance; + +@InjectableInit() +Future configureDependencies(String env) => getIt.init(environment: env); diff --git a/lib/main.dart b/lib/main.dart index 7b7f5b6..6c40893 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,31 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:injectable/injectable.dart'; +import 'package:intl/date_symbol_data_local.dart'; -void main() { - runApp(const MyApp()); -} +import 'injection.dart'; +import 'presentation/app_widget.dart'; -class MyApp extends StatelessWidget { - const MyApp({super.key}); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await initializeDateFormatting('id_ID', null); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.white, // background putih + statusBarIconBrightness: Brightness.dark, // ikon/tulisan hitam + statusBarBrightness: Brightness.light, // khusus iOS biar teksnya gelap + ), + ); -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + if (kReleaseMode) { + debugPrint = (message, {wrapWidth}) => ''; } - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } + await configureDependencies( + kReleaseMode ? Environment.prod : Environment.dev, + ); + + runApp(const AppWidget()); } diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart new file mode 100644 index 0000000..049e39a --- /dev/null +++ b/lib/presentation/app_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../application/auth/auth_bloc.dart'; +import '../application/auth/logout_form/logout_form_bloc.dart'; +import '../application/customer/customer_point_loader/customer_point_loader_bloc.dart'; +import '../common/theme/theme.dart'; +import '../common/constant/app_constant.dart'; +import '../injection.dart'; +import 'router/app_router.dart'; +import 'router/app_router_observer.dart'; + +class AppWidget extends StatefulWidget { + const AppWidget({super.key}); + + @override + State createState() => _AppWidgetState(); +} + +class _AppWidgetState extends State { + final _appRouter = getIt(); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), + ], + child: MaterialApp.router( + debugShowCheckedModeBanner: false, + title: AppConstant.appName, + theme: ThemeApp.theme, + routerConfig: _appRouter.config( + navigatorObservers: () => [AppRouteObserver()], + ), + ), + ); + } +} diff --git a/lib/presentation/components/assets/assets.gen.dart b/lib/presentation/components/assets/assets.gen.dart new file mode 100644 index 0000000..4ca2ee5 --- /dev/null +++ b/lib/presentation/components/assets/assets.gen.dart @@ -0,0 +1,156 @@ +// dart format width=80 + +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import + +import 'package:flutter/widgets.dart'; + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/bakso343.jpeg + AssetGenImage get bakso343 => + const AssetGenImage('assets/images/bakso343.jpeg'); + + /// File path: assets/images/launcher.png + AssetGenImage get launcher => + const AssetGenImage('assets/images/launcher.png'); + + /// File path: assets/images/logo.png + AssetGenImage get logo => const AssetGenImage('assets/images/logo.png'); + + /// File path: assets/images/onboarding1.png + AssetGenImage get onboarding1 => + const AssetGenImage('assets/images/onboarding1.png'); + + /// File path: assets/images/onboarding2.png + AssetGenImage get onboarding2 => + const AssetGenImage('assets/images/onboarding2.png'); + + /// File path: assets/images/onboarding3.png + AssetGenImage get onboarding3 => + const AssetGenImage('assets/images/onboarding3.png'); + + /// File path: assets/images/ramenmulu.jpeg + AssetGenImage get ramenmulu => + const AssetGenImage('assets/images/ramenmulu.jpeg'); + + /// File path: assets/images/tumulu.jpeg + AssetGenImage get tumulu => const AssetGenImage('assets/images/tumulu.jpeg'); + + /// File path: assets/images/woku.jpeg + AssetGenImage get woku => const AssetGenImage('assets/images/woku.jpeg'); + + /// List of all assets + List get values => [ + bakso343, + launcher, + logo, + onboarding1, + onboarding2, + onboarding3, + ramenmulu, + tumulu, + woku, + ]; +} + +class Assets { + const Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage { + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + this.animation, + }); + + final String _assetName; + + final Size? size; + final Set flavors; + final AssetGenImageAnimation? animation; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = true, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.medium, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({AssetBundle? bundle, String? package}) { + return AssetImage(_assetName, bundle: bundle, package: package); + } + + String get path => _assetName; + + String get keyName => _assetName; +} + +class AssetGenImageAnimation { + const AssetGenImageAnimation({ + required this.isAnimation, + required this.duration, + required this.frames, + }); + + final bool isAnimation; + final Duration duration; + final int frames; +} diff --git a/lib/presentation/components/assets/fonts.gen.dart b/lib/presentation/components/assets/fonts.gen.dart new file mode 100644 index 0000000..6b60f37 --- /dev/null +++ b/lib/presentation/components/assets/fonts.gen.dart @@ -0,0 +1,16 @@ +// dart format width=80 +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import + +class FontFamily { + FontFamily._(); + + /// Font family: Quicksand + static const String quicksand = 'Quicksand'; +} diff --git a/lib/presentation/components/button/button.dart b/lib/presentation/components/button/button.dart new file mode 100644 index 0000000..2cdbda2 --- /dev/null +++ b/lib/presentation/components/button/button.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../../common/theme/theme.dart'; + +part 'elevated_button.dart'; diff --git a/lib/presentation/components/button/elevated_button.dart b/lib/presentation/components/button/elevated_button.dart new file mode 100644 index 0000000..917fed1 --- /dev/null +++ b/lib/presentation/components/button/elevated_button.dart @@ -0,0 +1,52 @@ +part of 'button.dart'; + +class AppElevatedButton extends StatelessWidget { + const AppElevatedButton({ + super.key, + required this.onPressed, + required this.title, + this.width = double.infinity, + this.height = 48.0, + this.isLoading = false, + }); + + final Function()? onPressed; + final String title; + final double width; + final double height; + final bool isLoading; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: width, + height: height, + child: ElevatedButton( + onPressed: onPressed, + child: isLoading + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SpinKitFadingCircle(color: AppColor.white, size: 24), + SizedBox(width: 8), + Text( + 'Loading', + style: AppStyle.lg.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ) + : Text( + title, + style: AppStyle.lg.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} diff --git a/lib/presentation/components/field/date_text_form_field.dart b/lib/presentation/components/field/date_text_form_field.dart new file mode 100644 index 0000000..f244751 --- /dev/null +++ b/lib/presentation/components/field/date_text_form_field.dart @@ -0,0 +1,59 @@ +part of 'field.dart'; + +class DatePickerField extends StatefulWidget { + final String label; + final DateTime? selectedDate; + final Function(DateTime) onDateSelected; + + const DatePickerField({ + super.key, + required this.label, + required this.onDateSelected, + this.selectedDate, + }); + + @override + State createState() => _DatePickerFieldState(); +} + +class _DatePickerFieldState extends State { + DateTime? _selectedDate; + + @override + void initState() { + super.initState(); + _selectedDate = widget.selectedDate; + } + + Future _selectDate() async { + final picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2050), + ); + + if (picked != null && picked != _selectedDate) { + setState(() { + _selectedDate = picked; + }); + widget.onDateSelected(picked); + } + } + + @override + Widget build(BuildContext context) { + return AppTextFormField( + title: widget.label, + hintText: 'Pilih tanggal', + readOnly: true, + controller: TextEditingController(text: _selectedDate?.toServerDate), + onChanged: (value) { + widget.onDateSelected(_selectedDate!); + }, + onTap: () { + _selectDate(); + }, + ); + } +} diff --git a/lib/presentation/components/field/field.dart b/lib/presentation/components/field/field.dart new file mode 100644 index 0000000..6a1c77b --- /dev/null +++ b/lib/presentation/components/field/field.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +import '../../../common/extension/extension.dart'; +import '../../../common/theme/theme.dart'; + +part 'text_form_field.dart'; +part 'search_text_form_field.dart'; +part 'password_text_form_page.dart'; +part 'date_text_form_field.dart'; diff --git a/lib/presentation/components/field/password_text_form_page.dart b/lib/presentation/components/field/password_text_form_page.dart new file mode 100644 index 0000000..01c36ac --- /dev/null +++ b/lib/presentation/components/field/password_text_form_page.dart @@ -0,0 +1,100 @@ +part of 'field.dart'; + +class AppPasswordTextFormField extends StatefulWidget { + const AppPasswordTextFormField({ + super.key, + this.hintText, + required this.title, + this.controller, + this.focusNode, + this.prefixIcon, + this.keyboardType, + this.onChanged, + this.validator, + }); + + final String? hintText; + final String title; + final TextEditingController? controller; + final FocusNode? focusNode; + final Widget? prefixIcon; + final TextInputType? keyboardType; + final ValueChanged? onChanged; + final String? Function(String?)? validator; + + @override + State createState() => + _AppPasswordTextFormFieldState(); +} + +class _AppPasswordTextFormFieldState extends State { + bool isPasswordVisible = true; + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 8), + TextFormField( + controller: widget.controller, + focusNode: widget.focusNode, + keyboardType: widget.keyboardType, + onChanged: widget.onChanged, + cursorColor: AppColor.primary, + obscureText: isPasswordVisible, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + validator: widget.validator, + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: widget.prefixIcon, + suffixIcon: isPasswordVisible + ? IconButton( + icon: Icon( + Icons.visibility, + color: AppColor.primary, + size: 20, + ), + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(8), + onPressed: () { + setState(() { + isPasswordVisible = !isPasswordVisible; + }); + }, + ) + : IconButton( + icon: Icon( + Icons.visibility_off, + color: AppColor.primary, + size: 20, + ), + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(8), + onPressed: () { + setState(() { + isPasswordVisible = !isPasswordVisible; + }); + }, + ), + + prefixIconConstraints: const BoxConstraints( + minWidth: 0, + minHeight: 0, + ), + suffixIconConstraints: const BoxConstraints( + minWidth: 0, + minHeight: 0, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/components/field/search_text_form_field.dart b/lib/presentation/components/field/search_text_form_field.dart new file mode 100644 index 0000000..91d1d1b --- /dev/null +++ b/lib/presentation/components/field/search_text_form_field.dart @@ -0,0 +1,58 @@ +part of 'field.dart'; + +class SearchTextField extends StatelessWidget { + final String hintText; + final IconData prefixIcon; + final TextEditingController? controller; + final Function()? onClear; + + const SearchTextField({ + super.key, + required this.hintText, + required this.prefixIcon, + this.controller, + this.onClear, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.fromLTRB(16, 0, 16, 16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(25), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: TextField( + cursorColor: AppColor.primary, + controller: controller, + decoration: InputDecoration( + hintText: hintText, + hintStyle: TextStyle(color: AppColor.textLight, fontSize: 14), + disabledBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedBorder: InputBorder.none, + prefixIcon: Container( + margin: EdgeInsets.all(12), + width: 24, + height: 24, + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(6), + ), + child: Icon(prefixIcon, color: AppColor.white, size: 14), + ), + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + ), + ), + ); + } +} diff --git a/lib/presentation/components/field/text_form_field.dart b/lib/presentation/components/field/text_form_field.dart new file mode 100644 index 0000000..ce5cab3 --- /dev/null +++ b/lib/presentation/components/field/text_form_field.dart @@ -0,0 +1,68 @@ +part of 'field.dart'; + +class AppTextFormField extends StatelessWidget { + const AppTextFormField({ + super.key, + this.hintText, + required this.title, + this.controller, + this.focusNode, + this.prefixIcon, + this.suffixIcon, + this.keyboardType, + this.onChanged, + this.validator, + this.readOnly = false, + this.onTap, + }); + + final String? hintText; + final String title; + final TextEditingController? controller; + final FocusNode? focusNode; + final Widget? prefixIcon; + final Widget? suffixIcon; + final TextInputType? keyboardType; + final ValueChanged? onChanged; + final String? Function(String?)? validator; + final bool readOnly; + final Function()? onTap; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + TextFormField( + controller: controller, + focusNode: focusNode, + keyboardType: keyboardType, + onChanged: onChanged, + cursorColor: AppColor.primary, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + validator: validator, + onTap: onTap, + readOnly: readOnly, + decoration: InputDecoration( + hintText: hintText, + prefixIcon: prefixIcon, + suffixIcon: suffixIcon, + prefixIconConstraints: const BoxConstraints( + minWidth: 0, + minHeight: 0, + ), + suffixIconConstraints: const BoxConstraints( + minWidth: 0, + minHeight: 0, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/components/image/image.dart b/lib/presentation/components/image/image.dart new file mode 100644 index 0000000..c61ef77 --- /dev/null +++ b/lib/presentation/components/image/image.dart @@ -0,0 +1,10 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'dart:math' as math; + +import '../../../common/theme/theme.dart'; +import '../assets/assets.gen.dart'; + +part 'image_placeholder.dart'; +part 'network_image.dart'; diff --git a/lib/presentation/components/image/image_placeholder.dart b/lib/presentation/components/image/image_placeholder.dart new file mode 100644 index 0000000..ff567ec --- /dev/null +++ b/lib/presentation/components/image/image_placeholder.dart @@ -0,0 +1,170 @@ +part of 'image.dart'; + +class ImagePlaceholder extends StatelessWidget { + const ImagePlaceholder({ + super.key, + this.width, + this.height, + this.showBorderRadius = true, + this.backgroundColor, + }); + + final double? width; + final double? height; + final bool showBorderRadius; + final Color? backgroundColor; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + // Determine the size based on available space or provided dimensions + final containerWidth = width ?? constraints.maxWidth; + final containerHeight = height ?? constraints.maxHeight; + + // Calculate the minimum dimension to determine if we should show simple or detailed version + final minDimension = math.min( + containerWidth == double.infinity ? containerHeight : containerWidth, + containerHeight == double.infinity ? containerWidth : containerHeight, + ); + + return Container( + width: containerWidth == double.infinity + ? double.infinity + : containerWidth, + height: containerHeight == double.infinity ? null : containerHeight, + decoration: BoxDecoration( + color: backgroundColor ?? const Color(0x4DD9D9D9), + borderRadius: showBorderRadius + ? const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ) + : null, + ), + child: Center( + child: minDimension < 150 + ? _buildSimpleVersion(minDimension) + : _buildDetailedVersion(minDimension), + ), + ); + }, + ); + } + + // Simple version for small sizes (< 100px) + Widget _buildSimpleVersion(double size) { + final iconSize = (size * 0.4).clamp(16.0, 32.0); + final fontSize = (size * 0.12).clamp(8.0, 12.0); + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: iconSize * 1.5, + height: iconSize * 1.5, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(iconSize * 0.75), + ), + child: Center( + child: Assets.images.logo.image( + width: iconSize, + height: iconSize, + fit: BoxFit.contain, + ), + ), + ), + if (size > 50) ...[ + SizedBox(height: size * 0.05), + Text( + 'Enaklo', + style: TextStyle( + color: AppColor.primary, + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + ), + ], + ], + ); + } + + // Detailed version for larger sizes (>= 100px) + Widget _buildDetailedVersion(double minDimension) { + final scaleFactor = minDimension / 200; // Base scale factor + + // Proportional sizes + final illustrationSize = (120 * scaleFactor).clamp(80.0, 120.0); + final illustrationHeight = (160 * scaleFactor).clamp(100.0, 160.0); + final handWidth = (60 * scaleFactor).clamp(30.0, 60.0); + final handHeight = (80 * scaleFactor).clamp(40.0, 80.0); + final cupWidth = (70 * scaleFactor).clamp(35.0, 70.0); + final cupHeight = (90 * scaleFactor).clamp(45.0, 90.0); + final logoSize = (40 * scaleFactor).clamp(20.0, 40.0); + final fontSize = (12 * scaleFactor).clamp(8.0, 12.0); + + return Container( + width: illustrationSize, + height: illustrationHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(illustrationSize / 2), + ), + child: Stack( + children: [ + // Hand + Positioned( + bottom: illustrationHeight * 0.125, // 20/160 ratio + left: illustrationSize * 0.25, // 30/120 ratio + child: Container( + width: handWidth, + height: handHeight, + decoration: BoxDecoration( + color: const Color(0xFFFFDBB3), + borderRadius: BorderRadius.circular(handWidth / 2), + ), + ), + ), + // Coffee cup + Positioned( + top: illustrationHeight * 0.1875, // 30/160 ratio + left: illustrationSize * 0.208, // 25/120 ratio + child: Container( + width: cupWidth, + height: cupHeight, + decoration: BoxDecoration( + color: const Color(0xFFF4E4BC), + borderRadius: BorderRadius.circular( + math.max(8.0, 10 * scaleFactor), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo + Assets.images.logo.image( + width: logoSize, + height: logoSize, + fit: BoxFit.contain, + ), + SizedBox(height: math.max(4.0, 8 * scaleFactor)), + if (cupHeight > 50) // Only show text if cup is big enough + Text( + 'Enaklo', + style: TextStyle( + color: AppColor.primary, + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/components/image/network_image.dart b/lib/presentation/components/image/network_image.dart new file mode 100644 index 0000000..344ddde --- /dev/null +++ b/lib/presentation/components/image/network_image.dart @@ -0,0 +1,61 @@ +part of 'image.dart'; + +class AppNetworkImage extends StatelessWidget { + final String? url; + final double? height; + final double? width; + final double? borderRadius; + final BoxFit? fit; + final bool? isCanZoom; + final VoidCallback? onTap; + + const AppNetworkImage({ + super.key, + this.url, + this.height, + this.width, + this.borderRadius = 0, + this.fit = BoxFit.cover, + this.isCanZoom = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + Widget customPhoto( + double? heightx, + double? widthx, + BoxFit? fitx, + double? radius, + ) { + return CachedNetworkImage( + imageUrl: url.toString(), + placeholder: (context, url) => Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: height, + width: width, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(radius ?? 0), + ), + ), + ), + errorWidget: (context, url, error) => + ImagePlaceholder(height: height, width: width), + height: heightx, + width: widthx, + fit: fitx, + ); + } + + return GestureDetector( + onTap: onTap, + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius!), + child: customPhoto(height, width, BoxFit.fill, borderRadius), + ), + ); + } +} diff --git a/lib/presentation/components/toast/flushbar.dart b/lib/presentation/components/toast/flushbar.dart new file mode 100644 index 0000000..0529649 --- /dev/null +++ b/lib/presentation/components/toast/flushbar.dart @@ -0,0 +1,54 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import '../../../domain/auth/auth.dart'; + +class AppFlushbar { + static void showSuccess(BuildContext context, String message) { + Flushbar( + messageText: Text( + message, + style: AppStyle.lg.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + icon: const Icon(Icons.check_circle, color: Colors.white), + duration: const Duration(seconds: 2), + flushbarPosition: FlushbarPosition.TOP, + backgroundColor: AppColor.secondary, + borderRadius: BorderRadius.circular(12), + margin: const EdgeInsets.all(12), + ).show(context); + } + + static void showError(BuildContext context, String message) { + Flushbar( + messageText: Text( + message, + style: AppStyle.lg.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + icon: const Icon(Icons.error, color: Colors.white), + duration: const Duration(seconds: 3), + flushbarPosition: FlushbarPosition.TOP, + backgroundColor: AppColor.error, + borderRadius: BorderRadius.circular(12), + margin: const EdgeInsets.all(12), + ).show(context); + } + + static void showAuthFailureToast(BuildContext context, AuthFailure failure) => + showError( + context, + failure.map( + serverError: (value) => value.failure.toStringFormatted(context), + dynamicErrorMessage: (value) => value.erroMessage, + unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi', + ), + ); +} diff --git a/lib/presentation/pages/account/account_my/account_my_page.dart b/lib/presentation/pages/account/account_my/account_my_page.dart new file mode 100644 index 0000000..07e48ab --- /dev/null +++ b/lib/presentation/pages/account/account_my/account_my_page.dart @@ -0,0 +1,27 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../components/field/field.dart'; + +@RoutePage() +class AccountMyPage extends StatelessWidget { + const AccountMyPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Akun Saya')), + body: ListView( + padding: EdgeInsets.all(16), + children: [ + AppTextFormField(title: 'Nama', hintText: 'Masukkan Name'), + SizedBox(height: 16), + AppTextFormField(title: 'Email', hintText: 'Masukkan Email'), + SizedBox(height: 16), + AppTextFormField(title: 'No. Hp', hintText: 'Masukkan No. Hp'), + SizedBox(height: 16), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/account/address/address_page.dart b/lib/presentation/pages/account/address/address_page.dart new file mode 100644 index 0000000..ffefd87 --- /dev/null +++ b/lib/presentation/pages/account/address/address_page.dart @@ -0,0 +1,398 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +@RoutePage() +class AddressPage extends StatefulWidget { + const AddressPage({super.key}); + + @override + State createState() => _AddressPageState(); +} + +class _AddressPageState extends State { + // Sample saved addresses - replace with your actual data source + final List> savedAddresses = [ + { + 'id': '1', + 'label': 'Rumah', + 'name': 'John Doe', + 'phone': '081234567890', + 'fullAddress': 'Jl. Merdeka No. 123, RT 01/RW 02, Menteng', + 'city': 'Jakarta Pusat', + 'province': 'DKI Jakarta', + 'postalCode': '10110', + 'isDefault': true, + }, + { + 'id': '2', + 'label': 'Kantor', + 'name': 'John Doe', + 'phone': '081234567890', + 'fullAddress': 'Jl. Sudirman No. 456, Lantai 10, SCBD', + 'city': 'Jakarta Selatan', + 'province': 'DKI Jakarta', + 'postalCode': '12190', + 'isDefault': false, + }, + { + 'id': '3', + 'label': 'Apartemen', + 'name': 'Jane Doe', + 'phone': '087654321098', + 'fullAddress': 'Jl. Casablanca Raya No. 789, Tower A Unit 15B', + 'city': 'Jakarta Selatan', + 'province': 'DKI Jakarta', + 'postalCode': '12870', + 'isDefault': false, + }, + ]; + + void _setDefaultAddress(String addressId) { + setState(() { + for (var address in savedAddresses) { + address['isDefault'] = address['id'] == addressId; + } + }); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Alamat utama berhasil diubah', + style: AppStyle.md.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.success, + behavior: SnackBarBehavior.floating, + ), + ); + } + + void _deleteAddress(String addressId) { + setState(() { + savedAddresses.removeWhere((address) => address['id'] == addressId); + }); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Alamat berhasil dihapus', + style: AppStyle.md.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.success, + behavior: SnackBarBehavior.floating, + ), + ); + } + + void _showDeleteDialog(String addressId, String label) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: AppColor.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: Text( + 'Hapus Alamat', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + content: Text( + 'Apakah Anda yakin ingin menghapus alamat "$label"?', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Batal', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + _deleteAddress(addressId); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.error, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + child: Text( + 'Hapus', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + }, + ); + } + + Widget _buildAddressCard(Map address) { + return Container( + decoration: BoxDecoration( + color: AppColor.white, + border: Border(bottom: BorderSide(color: AppColor.border, width: 1)), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: AppColor.border), + ), + child: Text( + address['label'], + style: AppStyle.xs.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + if (address['isDefault']) ...[ + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + 'Utama', + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + const Spacer(), + PopupMenuButton( + color: AppColor.white, + + icon: const Icon( + Icons.more_vert, + color: AppColor.textSecondary, + size: 20, + ), + onSelected: (value) { + switch (value) { + case 'edit': + // Navigate to edit form + break; + case 'default': + _setDefaultAddress(address['id']); + break; + case 'delete': + _showDeleteDialog(address['id'], address['label']); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'edit', + child: Text( + 'Edit Alamat', + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + ), + ), + ), + if (!address['isDefault']) + PopupMenuItem( + value: 'default', + child: Text( + 'Jadikan Utama', + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + ), + ), + ), + PopupMenuItem( + value: 'delete', + child: Text( + 'Hapus Alamat', + style: AppStyle.sm.copyWith(color: AppColor.error), + ), + ), + ], + ), + ], + ), + const SizedBox(height: 12), + Text( + address['name'], + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + address['phone'], + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 8), + Text( + address['fullAddress'], + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + ), + const SizedBox(height: 4), + Text( + '${address['city']}, ${address['province']} ${address['postalCode']}', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: Text('Alamat Tersimpan')), + body: savedAddresses.isEmpty + ? _buildEmptyState() + : Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: savedAddresses.length, + itemBuilder: (context, index) { + return _buildAddressCard(savedAddresses[index]); + }, + ), + ), + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: AppColor.white, + border: Border(top: BorderSide(color: AppColor.border)), + ), + child: SafeArea( + child: SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: () { + // Navigate to add new address form + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.add, + color: AppColor.white, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Tambah Alamat Baru', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.location_off, size: 80, color: AppColor.textLight), + const SizedBox(height: 16), + Text( + 'Belum Ada Alamat Tersimpan', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textSecondary, + ), + ), + const SizedBox(height: 8), + Text( + 'Tambahkan alamat untuk memudahkan\nproses pengiriman', + textAlign: TextAlign.center, + style: AppStyle.md.copyWith(color: AppColor.textLight, height: 1.4), + ), + const SizedBox(height: 32), + SizedBox( + width: 200, + height: 48, + child: ElevatedButton( + onPressed: () { + // Navigate to add address form + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.add, color: AppColor.white, size: 20), + const SizedBox(width: 8), + Text( + 'Tambah Alamat', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/account/payment/payment_page.dart b/lib/presentation/pages/account/payment/payment_page.dart new file mode 100644 index 0000000..8a42d5d --- /dev/null +++ b/lib/presentation/pages/account/payment/payment_page.dart @@ -0,0 +1,345 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +@RoutePage() +class PaymentPage extends StatefulWidget { + const PaymentPage({super.key}); + + @override + State createState() => _PaymentPageState(); +} + +class _PaymentPageState extends State { + // Featured payment methods + final List> featuredMethods = [ + { + 'id': 'qris', + 'name': 'QRIS', + 'subtitle': null, + 'icon': 'assets/images/qris.png', + 'iconColor': AppColor.black, + 'badge': 'Baru', + 'badgeColor': const Color(0xFFE57373), + }, + ]; + + // Main payment categories + final List> paymentCategories = [ + { + 'id': 'credit_card', + 'name': 'Kartu Kredit', + 'subtitle': + 'Minimal pembayaran Rp 10.000 dan mendukung Kartu Berlogo Visa, Mastercard dan JCB', + 'icon': Icons.credit_card, + 'iconColor': const Color(0xFF2E7D32), + 'hasArrow': false, + }, + ]; + + // Other payment methods + final List> otherMethods = [ + { + 'id': 'gopay', + 'name': 'Gopay', + 'subtitle': 'Aktifkan Sekarang', + 'logo': 'assets/images/gopay.png', + 'iconColor': const Color(0xFF00AA5B), + 'hasArrow': true, + }, + { + 'id': 'dana', + 'name': 'Dana', + 'subtitle': 'Aktifkan Sekarang', + 'logo': 'assets/images/dana.png', + 'iconColor': const Color(0xFF118EEA), + 'hasArrow': true, + }, + { + 'id': 'blu', + 'name': 'blu', + 'subtitle': 'Aktifkan Sekarang', + 'logo': 'assets/images/blu.png', + 'iconColor': const Color(0xFF00D4FF), + 'hasArrow': true, + }, + { + 'id': 'shopeepay', + 'name': 'ShopeePay', + 'subtitle': 'Aktifkan Sekarang', + 'logo': 'assets/images/shopeepay.png', + 'iconColor': const Color(0xFFEE4D2D), + 'hasArrow': true, + }, + { + 'id': 'ovo', + 'name': 'OVO', + 'subtitle': 'Aktifkan Sekarang', + 'logo': 'assets/images/ovo.png', + 'iconColor': const Color(0xFF4C3EC9), + 'hasArrow': true, + }, + ]; + + Widget _buildFeaturedCard(Map method) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border.withOpacity(0.3)), + ), + child: Row( + children: [ + // Logo placeholder + Container( + width: 40, + height: 24, + decoration: BoxDecoration( + color: AppColor.black, + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: Text( + method['name'], + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + fontSize: 10, + ), + ), + ), + ), + const SizedBox(width: 16), + Text( + method['name'], + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const Spacer(), + if (method['badge'] != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: method['badgeColor'], + borderRadius: BorderRadius.circular(12), + ), + child: Text( + method['badge'], + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } + + Widget _buildCategoryCard(Map category) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border.withOpacity(0.3)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: category['iconColor'].withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + category['icon'], + color: category['iconColor'], + size: 20, + ), + ), + const SizedBox(width: 16), + Text( + category['name'], + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + if (category['subtitle'] != null) ...[ + const SizedBox(height: 12), + Text( + category['subtitle'], + style: AppStyle.md.copyWith( + color: const Color(0xFF4CAF50), + height: 1.4, + ), + ), + ], + ], + ), + ); + } + + Widget _buildPaymentMethodCard(Map method) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + // Handle method selection + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: const BoxDecoration( + color: AppColor.white, + border: Border( + bottom: BorderSide(color: Color(0xFFF5F5F5), width: 1), + ), + ), + child: Row( + children: [ + // Method logo/icon + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: method['logo'] != null + ? ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + method['logo'], + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildFallbackIcon(method); + }, + ), + ) + : _buildFallbackIcon(method), + ), + const SizedBox(width: 16), + // Method info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + method['name'], + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + if (method['subtitle'] != null) ...[ + const SizedBox(height: 2), + Text( + method['subtitle'], + style: AppStyle.md.copyWith(color: AppColor.textLight), + ), + ], + ], + ), + ), + // Arrow icon + if (method['hasArrow'] == true) + const Icon( + Icons.chevron_right, + color: AppColor.textLight, + size: 20, + ), + ], + ), + ), + ), + ); + } + + Widget _buildFallbackIcon(Map method) { + String initial = method['name'][0].toUpperCase(); + Color backgroundColor = method['iconColor'] ?? AppColor.primary; + + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: backgroundColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + initial, + style: AppStyle.md.copyWith( + color: backgroundColor, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 24, 20, 8), + child: Text( + title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFFAFAFA), + appBar: AppBar(title: Text('Metode Pembayaran')), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + // Featured methods + ...featuredMethods.map((method) => _buildFeaturedCard(method)), + + const SizedBox(height: 16), + + // Payment categories + ...paymentCategories.map( + (category) => _buildCategoryCard(category), + ), + + // Section header for other methods + _buildSectionHeader('Metode Pembayaran Lainnya'), + + // Other payment methods + Container( + decoration: const BoxDecoration(color: AppColor.white), + child: Column( + children: otherMethods + .map((method) => _buildPaymentMethodCard(method)) + .toList(), + ), + ), + + const SizedBox(height: 32), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/auth/create_password/create_password_page.dart b/lib/presentation/pages/auth/create_password/create_password_page.dart new file mode 100644 index 0000000..33afd3e --- /dev/null +++ b/lib/presentation/pages/auth/create_password/create_password_page.dart @@ -0,0 +1,136 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/auth/auth_bloc.dart'; +import '../../../../application/auth/set_password/set_password_form_bloc.dart'; +import '../../../../injection.dart'; +import '../../../components/button/button.dart'; +import '../../../components/field/field.dart'; +import '../../../components/toast/flushbar.dart'; +import '../../../router/app_router.gr.dart'; + +@RoutePage() +class CreatePasswordPage extends StatelessWidget implements AutoRouteWrapper { + final String registrationToken; + const CreatePasswordPage({super.key, required this.registrationToken}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => + p.failureOrSetPasswordOption != c.failureOrSetPasswordOption, + listener: (context, state) { + state.failureOrSetPasswordOption.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + context.read().add(AuthEvent.fetchCurrentUser()); + context.router.replaceAll([MainRoute()]); + }); + }, + ), + ); + }, + child: Scaffold( + appBar: AppBar(title: const Text('Buat Kata Sandi')), + body: BlocBuilder( + builder: (context, state) { + return Form( + autovalidateMode: state.showErrorMessages + ? AutovalidateMode.always + : AutovalidateMode.disabled, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 40), + + // Title + AppPasswordTextFormField( + title: 'Masukkan kata sandi', + hintText: '********', + onChanged: (value) => context + .read() + .add(SetPasswordFormEvent.passwordChanged(value)), + validator: (value) { + if (context + .read() + .state + .password + .isEmpty) { + return 'Masukkan kata sandi'; + } + + return null; + }, + ), + SizedBox(height: 24), + AppPasswordTextFormField( + title: 'Ulangi kata sandi', + hintText: '********', + onChanged: (value) => + context.read().add( + SetPasswordFormEvent.confirmPasswordChanged(value), + ), + validator: (value) { + if (context + .read() + .state + .password + .isEmpty) { + return 'Masukkan kata sandi'; + } + + if (context + .read() + .state + .password != + context + .read() + .state + .confirmPassword) { + return 'Kata sandi tidak cocok'; + } + + return null; + }, + ), + + const SizedBox(height: 50), + + Spacer(), + + // Continue Button + AppElevatedButton( + onPressed: state.isSubmitting + ? null + : () => context.read().add( + SetPasswordFormEvent.submitted(), + ), + title: 'Konfirmasi', + isLoading: state.isSubmitting, + ), + + const SizedBox(height: 24), + ], + ), + ), + ); + }, + ), + ), + ); + } + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => getIt() + ..add(SetPasswordFormEvent.registrationTokenChanged(registrationToken)), + child: this, + ); +} diff --git a/lib/presentation/pages/auth/login/login_page.dart b/lib/presentation/pages/auth/login/login_page.dart new file mode 100644 index 0000000..5d2aabe --- /dev/null +++ b/lib/presentation/pages/auth/login/login_page.dart @@ -0,0 +1,136 @@ +import 'dart:developer'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/auth/check_phone_form/check_phone_form_bloc.dart'; +import '../../../../common/function/app_function.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/auth/auth.dart'; +import '../../../../injection.dart'; +import '../../../components/button/button.dart'; +import '../../../components/toast/flushbar.dart'; +import '../../../router/app_router.gr.dart'; +import 'widgets/phone_field.dart'; + +@RoutePage() +class LoginPage extends StatelessWidget implements AutoRouteWrapper { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => + p.failureOrCheckPhoneOption != c.failureOrCheckPhoneOption, + listener: (context, state) { + state.failureOrCheckPhoneOption.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + log(data.toString()); + if (data.status.isNotRegistered) { + context.router.push( + RegisterRoute(phoneNumber: data.phoneNumber), + ); + } else if (data.status.isPasswordRequired) { + context.router.push( + PasswordRoute( + phoneNumber: getNormalizePhone(state.phoneNumber), + ), + ); + } + }); + }, + ), + ); + }, + child: Scaffold( + appBar: AppBar(title: const Text('Masuk')), + body: BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Form( + autovalidateMode: state.showErrorMessages + ? AutovalidateMode.always + : AutovalidateMode.disabled, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 40), + + // Title + LoginPhoneField(), + + const SizedBox(height: 50), + + // Continue Button + AppElevatedButton( + onPressed: state.isSubmitting + ? null + : () { + context.read().add( + CheckPhoneFormEvent.submitted(), + ); + }, + isLoading: state.isSubmitting, + title: 'Lanjutkan', + ), + + const SizedBox(height: 24), + + // Terms and Conditions + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + children: [ + const TextSpan( + text: + 'Dengan masuk Enaklo, kamu telah\nmenyetujui ', + ), + TextSpan( + text: 'Syarat & Ketentuan', + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + const TextSpan(text: ' dan\n'), + TextSpan( + text: 'Kebijakan Privasi', + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 40), + ], + ), + ), + ); + }, + ), + ), + ); + } + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => getIt(), + child: this, + ); +} diff --git a/lib/presentation/pages/auth/login/widgets/phone_field.dart b/lib/presentation/pages/auth/login/widgets/phone_field.dart new file mode 100644 index 0000000..7caa40b --- /dev/null +++ b/lib/presentation/pages/auth/login/widgets/phone_field.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/auth/check_phone_form/check_phone_form_bloc.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../components/field/field.dart'; + +class LoginPhoneField extends StatefulWidget { + const LoginPhoneField({super.key}); + + @override + State createState() => _LoginPhoneFieldState(); +} + +class _LoginPhoneFieldState extends State { + final TextEditingController _controller = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + bool _hasFocus = false; + + @override + void initState() { + super.initState(); + _focusNode.addListener(() { + setState(() { + _hasFocus = _focusNode.hasFocus; + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + void _clearText() { + _controller.clear(); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return AppTextFormField( + title: 'Masukkan no telepon', + hintText: '8712671212', + controller: _controller, + focusNode: _focusNode, + keyboardType: TextInputType.phone, + validator: (value) { + if (context.read().state.phoneNumber.isEmpty) { + return 'Masukkan no telepon'; + } + + return null; + }, + prefixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + '+62', + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + suffixIcon: (_hasFocus && _controller.text.isNotEmpty) + ? IconButton( + onPressed: _clearText, + icon: Icon(Icons.close, color: AppColor.primary, size: 20), + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(8), + ) + : null, + onChanged: (value) { + context.read().add( + CheckPhoneFormEvent.phoneNumberChanged(value), + ); + }, + ); + } +} diff --git a/lib/presentation/pages/auth/otp/otp_page.dart b/lib/presentation/pages/auth/otp/otp_page.dart new file mode 100644 index 0000000..44a887f --- /dev/null +++ b/lib/presentation/pages/auth/otp/otp_page.dart @@ -0,0 +1,322 @@ +import 'dart:async'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/auth/resend_form/resend_form_bloc.dart'; +import '../../../../application/auth/verify_form/verify_form_bloc.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/auth/auth.dart'; +import '../../../../injection.dart'; +import '../../../components/toast/flushbar.dart'; +import '../../../router/app_router.gr.dart'; + +@RoutePage() +class OtpPage extends StatefulWidget implements AutoRouteWrapper { + final String registrationToken; + final String phoneNumber; + + const OtpPage({ + super.key, + required this.registrationToken, + required this.phoneNumber, + }); + + @override + State createState() => _OtpPageState(); + + @override + Widget wrappedRoute(BuildContext context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => getIt() + ..add(VerifyFormEvent.registrationTokenChanged(registrationToken)), + ), + BlocProvider( + create: (context) => getIt() + ..add(ResendFormEvent.phoneNumberChanged(phoneNumber)) + ..add(ResendFormEvent.purposeChanged('registration')), + ), + ], + child: this, + ); +} + +class _OtpPageState extends State { + final List _controllers = List.generate( + 6, + (index) => TextEditingController(), + ); + final List _focusNodes = List.generate(6, (index) => FocusNode()); + + Timer? _timer; + int _secondsRemaining = 18; + bool _canResend = false; + + @override + void initState() { + super.initState(); + _startTimer(); + } + + void _startTimer() { + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_secondsRemaining > 0) { + setState(() { + _secondsRemaining--; + }); + } else { + setState(() { + _canResend = true; + }); + _timer?.cancel(); + } + }); + } + + void _resendCode() { + setState(() { + _secondsRemaining = 18; + _canResend = false; + }); + _startTimer(); + // Add your resend logic here + context.read().add(ResendFormEvent.submitted()); + } + + String _formatTime(int seconds) { + int minutes = seconds ~/ 60; + int remainingSeconds = seconds % 60; + return '${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}'; + } + + void _onCodeChanged(String value, int index) { + if (value.isNotEmpty) { + // Move to next field + if (index < 5) { + _focusNodes[index + 1].requestFocus(); + } else { + // Last field, unfocus + _focusNodes[index].unfocus(); + _verifyCode(); + } + } else { + // Handle backspace - move to previous field + if (index > 0) { + _focusNodes[index - 1].requestFocus(); + } + } + } + + void _verifyCode() { + String code = _controllers.map((controller) => controller.text).join(); + if (code.length == 6) { + // context.router.push(CreatePasswordRoute()); + context.read().add(VerifyFormEvent.otpCodeChanged(code)); + context.read().add(VerifyFormEvent.submitted()); + } + } + + @override + void dispose() { + _timer?.cancel(); + for (var controller in _controllers) { + controller.dispose(); + } + for (var focusNode in _focusNodes) { + focusNode.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MultiBlocListener( + listeners: [ + BlocListener( + listenWhen: (p, c) => + p.failureOrVerifyOption != c.failureOrVerifyOption, + listener: (context, state) { + state.failureOrVerifyOption.fold( + () {}, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + if (data.status == "FAILED") { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + context.router.replaceAll([LoginRoute()]); + }); + } else { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + context.router.push( + CreatePasswordRoute( + registrationToken: widget.registrationToken, + ), + ); + }); + } + }, + ), + ); + }, + ), + BlocListener( + listenWhen: (p, c) => + p.failureOrResendOption != c.failureOrResendOption, + listener: (context, state) { + state.failureOrResendOption.fold( + () {}, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + if (data.status.isSuccess) { + AppFlushbar.showSuccess(context, data.message); + } else { + AppFlushbar.showError(context, data.message); + } + }, + ), + ); + }, + ), + ], + child: Scaffold( + appBar: AppBar(title: Text('Verifikasi')), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + + // Title + Text( + 'Masukan Kode Verifikasi', + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + + const SizedBox(height: 12), + + // Description + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Kami telah mengirimkan kode verifikasi melalui ', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + ), + TextSpan( + text: 'Whatsapp', + style: AppStyle.sm.copyWith( + color: AppColor.success, + fontWeight: FontWeight.w500, + height: 1.4, + ), + ), + TextSpan( + text: ' ke ', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + ), + TextSpan( + text: '+${widget.phoneNumber}', + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + height: 1.4, + ), + ), + ], + ), + ), + + const SizedBox(height: 6), + + // Hidden text fields for input + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(6, (index) { + return Expanded( + child: Padding( + padding: EdgeInsets.only(right: index == 5 ? 0 : 8.0), + child: TextFormField( + controller: _controllers[index], + focusNode: _focusNodes[index], + keyboardType: TextInputType.number, + maxLength: 1, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + decoration: InputDecoration(counterText: ''), + textAlign: TextAlign.center, + style: AppStyle.lg.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + cursorColor: AppColor.primary, + onChanged: (value) { + setState(() {}); + _onCodeChanged(value, index); + }, + ), + ), + ); + }), + ), + + const SizedBox(height: 40), + + // Timer and Resend Section + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Mohon tunggu untuk kirim ulang kode ', + style: AppStyle.xs.copyWith(color: AppColor.textSecondary), + ), + Text( + _formatTime(_secondsRemaining), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + + if (_canResend) ...[ + const SizedBox(height: 12), + Center( + child: TextButton( + onPressed: _resendCode, + child: Text( + 'Kirim Ulang Kode', + style: AppStyle.sm.copyWith( + color: AppColor.success, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + + const SizedBox(height: 24), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/auth/password/password_page.dart b/lib/presentation/pages/auth/password/password_page.dart new file mode 100644 index 0000000..147789a --- /dev/null +++ b/lib/presentation/pages/auth/password/password_page.dart @@ -0,0 +1,105 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/auth/auth_bloc.dart'; +import '../../../../application/auth/login_form/login_form_bloc.dart'; +import '../../../../injection.dart'; +import '../../../components/button/button.dart'; +import '../../../components/field/field.dart'; +import '../../../components/toast/flushbar.dart'; +import '../../../router/app_router.gr.dart'; + +@RoutePage() +class PasswordPage extends StatelessWidget implements AutoRouteWrapper { + final String phoneNumber; + const PasswordPage({super.key, required this.phoneNumber}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => p.failureOrLoginOption != c.failureOrLoginOption, + listener: (context, state) { + state.failureOrLoginOption.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + context.read().add(AuthEvent.fetchCurrentUser()); + context.router.replaceAll([MainRoute()]); + }); + }, + ), + ); + }, + child: Scaffold( + appBar: AppBar(title: const Text('Kata Sandi')), + body: BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Form( + autovalidateMode: state.showErrorMessages + ? AutovalidateMode.always + : AutovalidateMode.disabled, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 40), + + // Title + AppPasswordTextFormField( + title: 'Masukkan kata sandi', + hintText: '********', + onChanged: (value) => context.read().add( + LoginFormEvent.passwordChanged(value), + ), + validator: (value) { + if (context + .read() + .state + .password + .isEmpty) { + return 'Masukkan kata sandi'; + } + + return null; + }, + ), + + const SizedBox(height: 50), + + Spacer(), + + // Continue Button + AppElevatedButton( + onPressed: state.isSubmitting + ? null + : () => context.read().add( + LoginFormEvent.submitted(), + ), + title: 'Konfirmasi', + isLoading: state.isSubmitting, + ), + + const SizedBox(height: 24), + ], + ), + ), + ); + }, + ), + ), + ); + } + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => + getIt() + ..add(LoginFormEvent.phoneNumberChanged(phoneNumber)), + child: this, + ); +} diff --git a/lib/presentation/pages/auth/pin/pin_page.dart b/lib/presentation/pages/auth/pin/pin_page.dart new file mode 100644 index 0000000..c759315 --- /dev/null +++ b/lib/presentation/pages/auth/pin/pin_page.dart @@ -0,0 +1,331 @@ +import 'dart:async'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../components/button/button.dart'; +import '../../../router/app_router.gr.dart'; + +@RoutePage() +class PinPage extends StatefulWidget { + final bool isCreatePin; // true for creating PIN, false for entering PIN + final String? title; // Optional custom title + + const PinPage({super.key, this.isCreatePin = true, this.title}); + + @override + State createState() => _PinPageState(); +} + +class _PinPageState extends State { + final List _controllers = List.generate( + 6, + (index) => TextEditingController(), + ); + final List _focusNodes = List.generate(6, (index) => FocusNode()); + + String _firstPin = ''; + bool _isConfirmingPin = false; + bool _isPinMismatch = false; + + @override + void initState() { + super.initState(); + } + + void _onPinChanged(String value, int index) { + if (value.isNotEmpty) { + // Move to next field + if (index < 5) { + _focusNodes[index + 1].requestFocus(); + } else { + // Last field, unfocus and process PIN + _focusNodes[index].unfocus(); + _processPinInput(); + } + } else { + // Handle backspace - move to previous field + if (index > 0) { + _focusNodes[index - 1].requestFocus(); + } + } + } + + void _processPinInput() { + String currentPin = _controllers + .map((controller) => controller.text) + .join(); + + if (currentPin.length == 6) { + if (widget.isCreatePin) { + if (!_isConfirmingPin) { + // First PIN entry - store and ask for confirmation + _firstPin = currentPin; + setState(() { + _isConfirmingPin = true; + _isPinMismatch = false; + }); + _clearPinFields(); + } else { + // Confirming PIN + if (currentPin == _firstPin) { + // PINs match - create PIN + _createPin(currentPin); + } else { + // PINs don't match + setState(() { + _isPinMismatch = true; + }); + _clearPinFields(); + // Auto-hide error after 2 seconds + Timer(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + _isPinMismatch = false; + }); + } + }); + } + } + } else { + // Entering existing PIN + _verifyPin(currentPin); + } + } + + context.router.push(MainRoute()); + } + + void _clearPinFields() { + for (var controller in _controllers) { + controller.clear(); + } + _focusNodes[0].requestFocus(); + } + + void _createPin(String pin) { + // Add your PIN creation logic here + print('Creating PIN: $pin'); + // Navigate to next screen or show success message + } + + void _verifyPin(String pin) { + // Add your PIN verification logic here + print('Verifying PIN: $pin'); + // Navigate to next screen or show error + } + + void _resetPinCreation() { + setState(() { + _isConfirmingPin = false; + _firstPin = ''; + _isPinMismatch = false; + }); + _clearPinFields(); + } + + String get _getTitle { + if (widget.title != null) return widget.title!; + + if (widget.isCreatePin) { + return _isConfirmingPin ? 'Konfirmasi PIN' : 'Buat PIN Baru'; + } else { + return 'Masukan PIN'; + } + } + + String get _getDescription { + if (widget.isCreatePin) { + if (_isConfirmingPin) { + return 'Masukan kembali PIN untuk konfirmasi'; + } else { + return 'Buat PIN 6 digit untuk keamanan akun Anda'; + } + } else { + return 'Masukan PIN 6 digit Anda untuk melanjutkan'; + } + } + + @override + void dispose() { + for (var controller in _controllers) { + controller.dispose(); + } + for (var focusNode in _focusNodes) { + focusNode.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.isCreatePin ? 'Buat PIN' : 'Masukan PIN'), + leading: widget.isCreatePin && _isConfirmingPin + ? IconButton( + icon: Icon(Icons.arrow_back), + onPressed: _resetPinCreation, + ) + : null, + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + + // Title + Text( + _getTitle, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + + const SizedBox(height: 12), + + // Description + Text( + _getDescription, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + ), + + // Error message for PIN mismatch + if (_isPinMismatch) ...[ + const SizedBox(height: 8), + Text( + 'PIN tidak sama. Silakan coba lagi.', + style: AppStyle.sm.copyWith( + color: AppColor.error, + fontWeight: FontWeight.w500, + height: 1.4, + ), + ), + ], + + const SizedBox(height: 40), + + // PIN input fields + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(6, (index) { + return Expanded( + child: Padding( + padding: EdgeInsets.only(right: index == 5 ? 0 : 8.0), + child: TextFormField( + controller: _controllers[index], + focusNode: _focusNodes[index], + keyboardType: TextInputType.number, + maxLength: 1, + obscureText: true, // Hide PIN input + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: InputDecoration( + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: _isPinMismatch + ? AppColor.error + : AppColor.border, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: _isPinMismatch + ? AppColor.error + : AppColor.primary, + width: 2, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: AppColor.error), + ), + ), + textAlign: TextAlign.center, + style: AppStyle.lg.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + cursorColor: AppColor.primary, + onChanged: (value) { + setState(() { + _isPinMismatch = false; + }); + _onPinChanged(value, index); + }, + ), + ), + ); + }), + ), + + const SizedBox(height: 40), + + // Progress indicator for PIN creation + if (widget.isCreatePin) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.primary, + ), + ), + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _isConfirmingPin + ? AppColor.primary + : AppColor.border, + ), + ), + ], + ), + const SizedBox(height: 8), + Center( + child: Text( + _isConfirmingPin ? 'Langkah 2 dari 2' : 'Langkah 1 dari 2', + style: AppStyle.xs.copyWith(color: AppColor.textSecondary), + ), + ), + ], + + const Spacer(), + + // Continue Button + AppElevatedButton( + title: widget.isCreatePin + ? (_isConfirmingPin ? 'Konfirmasi' : 'Lanjutkan') + : 'Masuk', + onPressed: () { + String pin = _controllers + .map((controller) => controller.text) + .join(); + if (pin.length == 6) { + _processPinInput(); + } + }, + ), + + const SizedBox(height: 24), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/auth/register/register_page.dart b/lib/presentation/pages/auth/register/register_page.dart new file mode 100644 index 0000000..0096491 --- /dev/null +++ b/lib/presentation/pages/auth/register/register_page.dart @@ -0,0 +1,94 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../application/auth/register_form/register_form_bloc.dart'; +import '../../../../injection.dart'; +import '../../../components/button/button.dart'; +import '../../../components/toast/flushbar.dart'; +import '../../../router/app_router.gr.dart'; +import 'widgets/birth_date_field.dart'; +import 'widgets/name_field.dart'; + +@RoutePage() +class RegisterPage extends StatelessWidget implements AutoRouteWrapper { + final String phoneNumber; + const RegisterPage({super.key, required this.phoneNumber}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => + p.failureOrRegisterOption != c.failureOrRegisterOption, + listener: (context, state) { + state.failureOrRegisterOption.fold( + () {}, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (data) { + AppFlushbar.showSuccess(context, data.message); + Future.delayed(Duration(milliseconds: 1000), () { + context.router.push( + OtpRoute( + registrationToken: data.registrationToken, + phoneNumber: phoneNumber, + ), + ); + }); + }, + ), + ); + }, + child: Scaffold( + appBar: AppBar(title: const Text('Daftar')), + body: BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Form( + autovalidateMode: state.showErrorMessages + ? AutovalidateMode.always + : AutovalidateMode.disabled, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 40), + + RegisterNameField(), + SizedBox(height: 24), + RegisterBirthDateField(), + + const SizedBox(height: 50), + + Spacer(), + + // Continue Button + AppElevatedButton( + onPressed: state.isSubmitting + ? null + : () => context.read().add( + RegisterFormEvent.submitted(), + ), + title: 'Daftar & Lanjutkan', + isLoading: state.isSubmitting, + ), + + const SizedBox(height: 24), + ], + ), + ), + ); + }, + ), + ), + ); + } + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => + getIt() + ..add(RegisterFormEvent.phoneNumberChanged(phoneNumber)), + child: this, + ); +} diff --git a/lib/presentation/pages/auth/register/widgets/birth_date_field.dart b/lib/presentation/pages/auth/register/widgets/birth_date_field.dart new file mode 100644 index 0000000..87bc3c1 --- /dev/null +++ b/lib/presentation/pages/auth/register/widgets/birth_date_field.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/auth/register_form/register_form_bloc.dart'; +import '../../../../components/field/field.dart'; + +class RegisterBirthDateField extends StatelessWidget { + const RegisterBirthDateField({super.key}); + + @override + Widget build(BuildContext context) { + return DatePickerField( + label: 'Masukkan tanggal lahir', + onDateSelected: (value) { + context.read().add( + RegisterFormEvent.birthDateChanged(value), + ); + }, + ); + } +} diff --git a/lib/presentation/pages/auth/register/widgets/name_field.dart b/lib/presentation/pages/auth/register/widgets/name_field.dart new file mode 100644 index 0000000..ed512b3 --- /dev/null +++ b/lib/presentation/pages/auth/register/widgets/name_field.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/auth/register_form/register_form_bloc.dart'; +import '../../../../components/field/field.dart'; + +class RegisterNameField extends StatelessWidget { + const RegisterNameField({super.key}); + + @override + Widget build(BuildContext context) { + return AppTextFormField( + title: 'Masukkan nama', + hintText: 'John Doe', + onChanged: (value) { + context.read().add( + RegisterFormEvent.nameChanged(value), + ); + }, + validator: (value) { + if (context.read().state.name.isEmpty) { + return 'Masukkan nama'; + } + + return null; + }, + ); + } +} diff --git a/lib/presentation/pages/draw/draw_page.dart b/lib/presentation/pages/draw/draw_page.dart new file mode 100644 index 0000000..28e1316 --- /dev/null +++ b/lib/presentation/pages/draw/draw_page.dart @@ -0,0 +1,719 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:carousel_slider/carousel_slider.dart'; + +import '../../../common/theme/theme.dart'; +import '../../components/image/image.dart'; +import '../../router/app_router.gr.dart'; + +// Models (simplified) +class DrawEvent { + final String id; + final String name; + final String description; + final int entryPoints; + final String icon; + final String imageUrl; + final Color primaryColor; + final String prize; + final String prizeValue; + final DateTime drawDate; + final int totalParticipants; + final int hadiah; + final String status; // 'active', 'ended' + final int minSpending; + + DrawEvent({ + required this.id, + required this.name, + required this.description, + required this.entryPoints, + required this.icon, + required this.imageUrl, + required this.primaryColor, + required this.prize, + required this.prizeValue, + required this.drawDate, + required this.totalParticipants, + required this.hadiah, + required this.status, + required this.minSpending, + }); + + bool get isActive => status == 'active'; +} + +class UserEntry { + final String drawId; + final DateTime entryDate; + + UserEntry({required this.drawId, required this.entryDate}); +} + +class CarouselBanner { + final String id; + final String imageUrl; + final String title; + final String subtitle; + final Color backgroundColor; + + CarouselBanner({ + required this.id, + required this.imageUrl, + required this.title, + required this.subtitle, + required this.backgroundColor, + }); +} + +@RoutePage() +class DrawPage extends StatefulWidget { + const DrawPage({super.key}); + + @override + State createState() => _DrawPageState(); +} + +class _DrawPageState extends State { + final TextEditingController _dateFilterController = TextEditingController(); + final CarouselSliderController _carouselController = + CarouselSliderController(); + final ScrollController _scrollController = ScrollController(); + + int _currentBannerIndex = 0; + DateTime? _selectedFilterDate; + bool _isCollapsed = false; + + final List userEntries = [ + UserEntry( + drawId: "1", + entryDate: DateTime.now().subtract(Duration(hours: 3)), + ), + ]; + + final List banners = [ + CarouselBanner( + id: "1", + imageUrl: + "https://images.unsplash.com/photo-1610375461246-83df859d849d?w=800&h=400&fit=crop&crop=center", + title: "Gebyar Undian Emas", + subtitle: "Menangkan hadiah emas 3 gram!", + backgroundColor: Color(0xFFFF6B6B), + ), + CarouselBanner( + id: "2", + imageUrl: + "https://images.unsplash.com/photo-1592750475338-74b7b21085ab?w=800&h=400&fit=crop&crop=center", + title: "Undian iPhone 15 Pro", + subtitle: "Smartphone premium menanti Anda!", + backgroundColor: Color(0xFF4ECDC4), + ), + CarouselBanner( + id: "3", + imageUrl: + "https://images.unsplash.com/photo-1593640408182-31c70c8268f5?w=800&h=400&fit=crop&crop=center", + title: "Undian Laptop Gaming", + subtitle: "Laptop gaming terbaru untuk gamers!", + backgroundColor: Color(0xFF45B7D1), + ), + CarouselBanner( + id: "4", + imageUrl: + "https://images.unsplash.com/photo-1549298916-b41d501d3772?w=800&h=400&fit=crop&crop=center", + title: "Undian Sneakers Limited", + subtitle: "Sepatu branded edition terbatas!", + backgroundColor: Color(0xFF96CEB4), + ), + CarouselBanner( + id: "5", + imageUrl: + "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=800&h=400&fit=crop&crop=center", + title: "Undian Camera DSLR", + subtitle: "Kamera profesional untuk fotografer!", + backgroundColor: Color(0xFFFECEA8), + ), + CarouselBanner( + id: "6", + imageUrl: + "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=800&h=400&fit=crop&crop=center", + title: "Undian Gaming Console", + subtitle: "PlayStation 5 siap dimainkan!", + backgroundColor: Color(0xFFFF9AA2), + ), + CarouselBanner( + id: "7", + imageUrl: + "https://images.unsplash.com/photo-1512499617640-c74ae3a79d37?w=800&h=400&fit=crop&crop=center", + title: "Undian Motor Sport", + subtitle: "Motor sport impian Anda!", + backgroundColor: Color(0xFFB5EAD7), + ), + CarouselBanner( + id: "8", + imageUrl: + "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=800&h=400&fit=crop&crop=center", + title: "Undian Home Theater", + subtitle: "Sistem audio premium untuk rumah!", + backgroundColor: Color(0xFFC7CEDB), + ), + CarouselBanner( + id: "9", + imageUrl: + "https://images.unsplash.com/photo-1434493789847-2f02dc6ca35d?w=800&h=400&fit=crop&crop=center", + title: "Undian Luxury Watch", + subtitle: "Jam tangan mewah Swiss Made!", + backgroundColor: Color(0xFFFFB7B2), + ), + CarouselBanner( + id: "10", + imageUrl: + "https://images.unsplash.com/photo-1558618666-fbd1c326d4a4?w=800&h=400&fit=crop&crop=center", + title: "Undian Travel Voucher", + subtitle: "Liburan gratis ke destinasi impian!", + backgroundColor: Color(0xFFE2F0CB), + ), + ]; + + final List drawEvents = [ + DrawEvent( + id: "1", + name: "Emas 3 Gram", + description: "Gebyar Undian Enaklo\nMenangkan hadiah emas batangan", + entryPoints: 0, + icon: "👑", + imageUrl: + "https://images.unsplash.com/photo-1610375461246-83df859d849d?w=400&h=200&fit=crop&crop=center", + primaryColor: AppColor.primary, + prize: "Emas 3 Gram", + prizeValue: "Rp 2.500.000", + drawDate: DateTime.now().add(Duration(hours: 1, minutes: 20)), + totalParticipants: 245, + hadiah: 2, + status: 'active', + minSpending: 50000, + ), + DrawEvent( + id: "2", + name: "iPhone 15 Pro", + description: "Undian Smartphone Premium\nDapatkan iPhone terbaru", + entryPoints: 0, + icon: "📱", + imageUrl: + "https://images.unsplash.com/photo-1592750475338-74b7b21085ab?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF007AFF), + prize: "iPhone 15 Pro", + prizeValue: "Rp 18.000.000", + drawDate: DateTime.now().add(Duration(days: 2)), + totalParticipants: 1456, + hadiah: 1, + status: 'active', + minSpending: 100000, + ), + DrawEvent( + id: "3", + name: "Laptop Gaming", + description: "Undian Laptop Gaming ROG\nPerforma tinggi untuk gaming", + entryPoints: 0, + icon: "💻", + imageUrl: + "https://images.unsplash.com/photo-1593640408182-31c70c8268f5?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFFFF4444), + prize: "ROG Strix G15", + prizeValue: "Rp 15.000.000", + drawDate: DateTime.now().add(Duration(days: 5)), + totalParticipants: 892, + hadiah: 1, + status: 'active', + minSpending: 75000, + ), + DrawEvent( + id: "4", + name: "Sneakers Limited", + description: "Undian Sepatu Nike Air Jordan\nEdition terbatas collectors", + entryPoints: 0, + icon: "👟", + imageUrl: + "https://images.unsplash.com/photo-1549298916-b41d501d3772?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF32CD32), + prize: "Nike Air Jordan", + prizeValue: "Rp 3.500.000", + drawDate: DateTime.now().add(Duration(days: 3)), + totalParticipants: 567, + hadiah: 3, + status: 'active', + minSpending: 30000, + ), + DrawEvent( + id: "5", + name: "Camera DSLR", + description: + "Undian Kamera Canon EOS\nKamera profesional untuk fotografer", + entryPoints: 0, + icon: "📷", + imageUrl: + "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF8B4513), + prize: "Canon EOS R5", + prizeValue: "Rp 25.000.000", + drawDate: DateTime.now().add(Duration(days: 7)), + totalParticipants: 334, + hadiah: 1, + status: 'active', + minSpending: 150000, + ), + DrawEvent( + id: "6", + name: "PlayStation 5", + description: "Undian Gaming Console\nPS5 bundle dengan 3 games", + entryPoints: 0, + icon: "🎮", + imageUrl: + "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF0070D1), + prize: "PlayStation 5", + prizeValue: "Rp 8.500.000", + drawDate: DateTime.now().add(Duration(days: 4)), + totalParticipants: 1789, + hadiah: 2, + status: 'active', + minSpending: 60000, + ), + DrawEvent( + id: "7", + name: "Motor Yamaha R15", + description: "Undian Motor Sport\nYamaha R15 V4 warna special edition", + entryPoints: 0, + icon: "🏍️", + imageUrl: + "https://images.unsplash.com/photo-1512499617640-c74ae3a79d37?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFFDC143C), + prize: "Yamaha R15 V4", + prizeValue: "Rp 35.000.000", + drawDate: DateTime.now().add(Duration(days: 10)), + totalParticipants: 456, + hadiah: 1, + status: 'active', + minSpending: 200000, + ), + DrawEvent( + id: "8", + name: "Home Theater", + description: "Undian Sound System\nSony Home Theater 7.1 surround", + entryPoints: 0, + icon: "🔊", + imageUrl: + "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF4B0082), + prize: "Sony HT-A7000", + prizeValue: "Rp 12.000.000", + drawDate: DateTime.now().add(Duration(days: 6)), + totalParticipants: 298, + hadiah: 1, + status: 'active', + minSpending: 80000, + ), + DrawEvent( + id: "9", + name: "Luxury Watch", + description: "Undian Jam Tangan Mewah\nRolex Submariner Swiss Made", + entryPoints: 0, + icon: "⌚", + imageUrl: + "https://images.unsplash.com/photo-1434493789847-2f02dc6ca35d?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFFB8860B), + prize: "Rolex Submariner", + prizeValue: "Rp 150.000.000", + drawDate: DateTime.now().add(Duration(days: 14)), + totalParticipants: 123, + hadiah: 1, + status: 'active', + minSpending: 500000, + ), + DrawEvent( + id: "10", + name: "Travel Voucher Bali", + description: "Undian Liburan Gratis\nPaket tour Bali 4D3N all inclusive", + entryPoints: 0, + icon: "✈️", + imageUrl: + "https://images.unsplash.com/photo-1558618666-fbd1c326d4a4?w=400&h=200&fit=crop&crop=center", + primaryColor: Color(0xFF20B2AA), + prize: "Bali Tour Package", + prizeValue: "Rp 10.000.000", + drawDate: DateTime.now().subtract(Duration(days: 2)), + totalParticipants: 2156, + hadiah: 5, + status: 'ended', + minSpending: 40000, + ), + ]; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _dateFilterController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + final bool isCollapsed = + _scrollController.hasClients && + _scrollController.offset > (280 - kToolbarHeight); + + if (_isCollapsed != isCollapsed) { + setState(() { + _isCollapsed = isCollapsed; + }); + } + } + + List get filteredDraws { + List filtered = drawEvents; + + if (_selectedFilterDate != null) { + filtered = filtered.where((draw) { + return draw.drawDate.year == _selectedFilterDate!.year && + draw.drawDate.month == _selectedFilterDate!.month && + draw.drawDate.day == _selectedFilterDate!.day; + }).toList(); + } + + return filtered; + } + + String _getTimeRemaining(DateTime targetDate) { + final now = DateTime.now(); + final difference = targetDate.difference(now); + + if (difference.isNegative) return "Berakhir"; + + if (difference.inHours > 0) { + return "${difference.inHours}h ${difference.inMinutes % 60}m"; + } else if (difference.inMinutes > 0) { + return "${difference.inMinutes}m"; + } else { + return "Sekarang!"; + } + } + + Future _selectDate() async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedFilterDate ?? DateTime.now(), + firstDate: DateTime.now().subtract(Duration(days: 365)), + lastDate: DateTime.now().add(Duration(days: 365)), + ); + + if (picked != null) { + setState(() { + _selectedFilterDate = picked; + _dateFilterController.text = + "${picked.day}/${picked.month}/${picked.year}"; + }); + } + } + + void _clearDateFilter() { + setState(() { + _selectedFilterDate = null; + _dateFilterController.clear(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: CustomScrollView( + controller: _scrollController, + slivers: [ + // SliverAppBar with Carousel Background + SliverAppBar( + expandedHeight: 280, + floating: false, + pinned: true, + backgroundColor: AppColor.surface, + leading: Container( + margin: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(20), + ), + child: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + flexibleSpace: FlexibleSpaceBar( + title: AnimatedOpacity( + opacity: _isCollapsed ? 1.0 : 0.0, + duration: Duration(milliseconds: 200), + child: Text( + "Undian", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ), + titlePadding: EdgeInsets.only(left: 72, bottom: 16), + collapseMode: CollapseMode.parallax, + background: Stack( + children: [ + // Carousel Slider + Positioned.fill( + child: CarouselSlider.builder( + carouselController: _carouselController, + itemCount: banners.length, + options: CarouselOptions( + height: double.infinity, + viewportFraction: 1.0, + autoPlay: true, + autoPlayInterval: Duration(seconds: 4), + autoPlayAnimationDuration: Duration(milliseconds: 800), + onPageChanged: (index, reason) { + setState(() => _currentBannerIndex = index); + }, + ), + itemBuilder: (context, index, realIndex) { + final banner = banners[index]; + return GestureDetector( + onTap: () { + context.router.push(DrawDetailRoute()); + }, + child: AppNetworkImage(url: banner.imageUrl), + ); + }, + ), + ), + + // Page indicators + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: banners.asMap().entries.map((entry) { + return GestureDetector( + onTap: () => + _carouselController.animateToPage(entry.key), + child: Container( + width: _currentBannerIndex == entry.key ? 24 : 8, + height: 8, + margin: EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: _currentBannerIndex == entry.key + ? Colors.white + : Colors.white.withOpacity(0.4), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + ), + + // Date Filter + SliverToBoxAdapter( + child: Container( + margin: EdgeInsets.all(16), + child: TextFormField( + controller: _dateFilterController, + readOnly: true, + onTap: _selectDate, + decoration: InputDecoration( + hintText: "Filter berdasarkan tanggal", + hintStyle: AppStyle.md.copyWith( + color: AppColor.textSecondary, + ), + prefixIcon: Icon( + Icons.calendar_today, + color: AppColor.textSecondary, + ), + suffixIcon: _selectedFilterDate != null + ? IconButton( + icon: Icon( + Icons.clear, + color: AppColor.textSecondary, + ), + onPressed: _clearDateFilter, + ) + : null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.primary, width: 2), + ), + filled: true, + fillColor: AppColor.surface, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + ), + style: AppStyle.md.copyWith(color: AppColor.textPrimary), + ), + ), + ), + + // Draw List Header + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + "Daftar Undian (${filteredDraws.length})", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ), + ), + + // Draw List + SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final draw = filteredDraws[index]; + return Container( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: _buildSimpleDrawCard(draw), + ); + }, childCount: filteredDraws.length), + ), + + // Bottom padding + SliverToBoxAdapter(child: SizedBox(height: 100)), + ], + ), + ); + } + + Widget _buildSimpleDrawCard(DrawEvent draw) { + final timeRemaining = _getTimeRemaining(draw.drawDate); + + return GestureDetector( + onTap: () => context.router.push(DrawDetailRoute()), + child: Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [draw.primaryColor, draw.primaryColor.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.08), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Name + Text( + draw.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textWhite, + ), + ), + SizedBox(height: 6), + // Description + Text( + draw.description, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 8), + // Date and participants + Row( + children: [ + Icon( + Icons.access_time, + size: 16, + color: AppColor.textWhite.withOpacity(0.8), + ), + SizedBox(width: 4), + Text( + draw.isActive ? "Berakhir: $timeRemaining" : "Selesai", + style: AppStyle.xs.copyWith( + color: AppColor.textWhite.withOpacity(0.8), + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 16), + Icon( + Icons.people, + size: 16, + color: AppColor.textWhite.withOpacity(0.8), + ), + SizedBox(width: 4), + Text( + "${draw.totalParticipants}", + style: AppStyle.xs.copyWith( + color: AppColor.textWhite.withOpacity(0.8), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + ), + // Prize info + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(draw.icon, style: TextStyle(fontSize: 28)), + SizedBox(height: 4), + Text( + draw.prize, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + ), + ), + Text( + draw.prizeValue, + style: AppStyle.xs.copyWith( + color: AppColor.textWhite.withOpacity(0.8), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart b/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart new file mode 100644 index 0000000..55e742b --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart @@ -0,0 +1,1156 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:carousel_slider/carousel_slider.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../components/image/image.dart'; + +@RoutePage() +class DrawDetailPage extends StatefulWidget { + const DrawDetailPage({super.key}); + + @override + State createState() => _DrawDetailPageState(); +} + +class _DrawDetailPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + + late Animation _fadeAnimation; + late Animation _scaleAnimation; + + final CarouselSliderController _carouselController = + CarouselSliderController(); + final CarouselSliderController _numberCarouselController = + CarouselSliderController(); + final ScrollController _scrollController = ScrollController(); + + int _currentBannerIndex = 0; + int _currentNumberIndex = 0; + bool _isCollapsed = false; + + List lotteryNumbers = ['849302', '156789', '973421', '642853']; + bool isOngoing = true; + DateTime endTime = DateTime.now().add(Duration(hours: 2, minutes: 30)); + + final List prizes = [ + PrizeModel( + title: 'HADIAH UTAMA', + description: '3KG Emas Batangan', + icon: Icons.stars_rounded, + color: AppColor.warning, + isMain: true, + ), + PrizeModel( + title: 'HADIAH KE-2', + description: '1KG Emas Batangan', + icon: Icons.diamond_outlined, + color: AppColor.primary, + isMain: false, + ), + PrizeModel( + title: 'HADIAH KE-3', + description: 'Voucher Belanja 10 Juta', + icon: Icons.card_giftcard_outlined, + color: AppColor.success, + isMain: false, + ), + PrizeModel( + title: 'HADIAH HIBURAN', + description: 'Voucher Belanja 1 Juta', + icon: Icons.local_activity_outlined, + color: AppColor.info, + isMain: false, + ), + ]; + + final List banners = [ + BannerModel( + imageUrl: 'https://picsum.photos/800/400?random=1', + title: 'Undian Berhadiah Emas', + ), + BannerModel( + imageUrl: 'https://picsum.photos/800/400?random=2', + title: 'Kesempatan Menang 3KG Emas', + ), + BannerModel( + imageUrl: 'https://picsum.photos/800/400?random=3', + title: 'Undian Harian Terbesar', + ), + ]; + + @override + void initState() { + super.initState(); + + _scrollController.addListener(() { + bool isCollapsed = + _scrollController.hasClients && + _scrollController.offset > (280 - kToolbarHeight); + if (this._isCollapsed != isCollapsed) { + setState(() { + this._isCollapsed = isCollapsed; + }); + } + }); + + _fadeController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + _slideController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + _scaleController = AnimationController( + duration: Duration(milliseconds: 1000), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _startAnimations(); + } + + void _startAnimations() async { + await Future.delayed(Duration(milliseconds: 100)); + _fadeController.forward(); + await Future.delayed(Duration(milliseconds: 200)); + _slideController.forward(); + await Future.delayed(Duration(milliseconds: 300)); + _scaleController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + Widget _buildCard({required Widget child}) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 20), + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.08), + blurRadius: 30, + spreadRadius: 0, + offset: Offset(0, 15), + ), + ], + ), + child: child, + ); + } + + void _showFullImage(String imageUrl, String title) { + int dialogCurrentIndex = banners.indexWhere( + (banner) => banner.imageUrl == imageUrl, + ); + if (dialogCurrentIndex == -1) dialogCurrentIndex = 0; + + final CarouselSliderController dialogCarouselController = + CarouselSliderController(); + + showDialog( + context: context, + useSafeArea: false, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setDialogState) { + return Material( + type: MaterialType.transparency, + child: Container( + width: double.infinity, + height: MediaQuery.of(context).size.height, + color: Colors.black.withOpacity(0.9), + child: SafeArea( + child: Column( + children: [ + // Header dengan close button + Container( + padding: EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Gallery', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + color: Colors.white, + size: 24, + ), + ), + ), + ], + ), + ), + + // Main carousel + Expanded( + child: CarouselSlider.builder( + itemCount: banners.length, + carouselController: dialogCarouselController, + options: CarouselOptions( + height: double.infinity, + viewportFraction: 1.0, + enableInfiniteScroll: false, + initialPage: dialogCurrentIndex, + onPageChanged: (index, reason) { + setDialogState(() { + dialogCurrentIndex = index; + }); + }, + ), + itemBuilder: (context, index, realIndex) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16), + child: InteractiveViewer( + minScale: 0.8, + maxScale: 3.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.network( + banners[index].imageUrl, + fit: BoxFit.contain, + width: double.infinity, + loadingBuilder: (context, child, progress) { + if (progress == null) return child; + return Container( + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Center( + child: CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation( + Colors.white, + ), + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + color: Colors.white, + size: 48, + ), + SizedBox(height: 8), + Text( + 'Error loading image', + style: TextStyle( + color: Colors.white, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ), + ); + }, + ), + ), + + // Bottom area + Container( + padding: EdgeInsets.all(16), + child: Column( + children: [ + // Title + Text( + banners[dialogCurrentIndex].title, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + + SizedBox(height: 16), + + // Thumbnails + SizedBox( + height: 60, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: banners.length, + itemBuilder: (context, index) { + bool isActive = dialogCurrentIndex == index; + return GestureDetector( + onTap: () { + setDialogState(() { + dialogCurrentIndex = index; + }); + dialogCarouselController.animateToPage( + index, + ); + }, + child: Container( + width: 80, + height: 60, + margin: EdgeInsets.symmetric( + horizontal: 4, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isActive + ? Colors.white + : Colors.white.withOpacity(0.3), + width: isActive ? 3 : 1, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(6), + child: Stack( + children: [ + Image.network( + banners[index].imageUrl, + width: 80, + height: 60, + fit: BoxFit.cover, + ), + if (!isActive) + Container( + color: Colors.black.withOpacity( + 0.5, + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ); + } + + Widget _buildLotteryCard() { + return ScaleTransition( + scale: _scaleAnimation, + child: _buildCard( + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: isOngoing + ? AppColor.warning.withOpacity(0.2) + : AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + isOngoing + ? '🕐 UNDIAN SEDANG BERLANGSUNG' + : 'NOMOR UNDIAN GLOBAL', + style: TextStyle( + color: isOngoing ? AppColor.warning : AppColor.white, + fontWeight: FontWeight.w700, + fontSize: 11, + letterSpacing: 1, + ), + ), + ), + SizedBox(height: 20), + if (isOngoing) _buildCountdown() else _buildNumberCarousel(), + ], + ), + ), + ); + } + + Widget _buildCountdown() { + return StreamBuilder( + stream: Stream.periodic(Duration(seconds: 1), (_) => DateTime.now()), + builder: (context, snapshot) { + if (!snapshot.hasData) return Container(); + + final now = snapshot.data!; + final difference = endTime.difference(now); + + if (difference.isNegative) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + isOngoing = false; + }); + }); + return Container(); + } + + final hours = difference.inHours; + final minutes = difference.inMinutes % 60; + final seconds = difference.inSeconds % 60; + + return Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 24), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.warning.withOpacity(0.2), + AppColor.warning.withOpacity(0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.warning.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + children: [ + Text( + 'Pengundian dimulai dalam:', + style: TextStyle( + color: AppColor.warning, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildTimeUnit(hours.toString().padLeft(2, '0'), 'JAM'), + Text( + ':', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColor.warning, + ), + ), + _buildTimeUnit(minutes.toString().padLeft(2, '0'), 'MENIT'), + Text( + ':', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColor.warning, + ), + ), + _buildTimeUnit(seconds.toString().padLeft(2, '0'), 'DETIK'), + ], + ), + ], + ), + ); + }, + ); + } + + Widget _buildTimeUnit(String time, String label) { + return Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: AppColor.warning, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.3), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + ), + child: Text( + time, + style: TextStyle( + color: AppColor.white, + fontSize: 24, + fontWeight: FontWeight.w900, + ), + ), + ), + SizedBox(height: 4), + Text( + label, + style: TextStyle( + color: AppColor.warning, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ], + ); + } + + Widget _buildNumberCarousel() { + return Column( + children: [ + Container( + height: 80, + child: CarouselSlider.builder( + carouselController: _numberCarouselController, + itemCount: lotteryNumbers.length, + options: CarouselOptions( + height: 80, + viewportFraction: 1.0, + enableInfiniteScroll: true, + autoPlay: false, + onPageChanged: (index, reason) { + setState(() => _currentNumberIndex = index); + }, + ), + itemBuilder: (context, index, realIndex) { + return Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.white.withOpacity(0.2), + AppColor.white.withOpacity(0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.3), + width: 1, + ), + ), + child: Center( + child: Text( + lotteryNumbers[index], + style: TextStyle( + color: AppColor.white, + fontSize: 42, + fontWeight: FontWeight.w900, + letterSpacing: 12, + height: 1.0, + ), + ), + ), + ); + }, + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: lotteryNumbers.asMap().entries.map((entry) { + return GestureDetector( + onTap: () => _numberCarouselController.animateToPage(entry.key), + child: Container( + width: _currentNumberIndex == entry.key ? 24 : 8, + height: 8, + margin: EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: _currentNumberIndex == entry.key + ? AppColor.white + : AppColor.white.withOpacity(0.4), + ), + ), + ); + }).toList(), + ), + SizedBox(height: 12), + Text( + '${_currentNumberIndex + 1} dari ${lotteryNumbers.length} nomor', + style: TextStyle( + color: AppColor.white.withOpacity(0.8), + fontWeight: FontWeight.w500, + fontSize: 11, + ), + ), + ], + ); + } + + Widget _buildPrizeCard() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.5), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.3, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: _buildCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'DAFTAR HADIAH', + style: TextStyle( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w700, + fontSize: 14, + letterSpacing: 1.5, + ), + ), + SizedBox(height: 20), + ...prizes.map((prize) => _buildPrizeItem(prize)).toList(), + ], + ), + ), + ); + } + + Widget _buildPrizeItem(PrizeModel prize) { + return Container( + margin: EdgeInsets.only(bottom: 16), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + prize.color.withOpacity(prize.isMain ? 0.3 : 0.2), + prize.color.withOpacity(prize.isMain ? 0.2 : 0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: prize.color.withOpacity(prize.isMain ? 0.5 : 0.3), + width: prize.isMain ? 2 : 1, + ), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(prize.isMain ? 12 : 10), + decoration: BoxDecoration( + color: prize.color, + borderRadius: BorderRadius.circular(12), + boxShadow: prize.isMain + ? [ + BoxShadow( + color: prize.color.withOpacity(0.3), + blurRadius: 10, + offset: Offset(0, 4), + ), + ] + : null, + ), + child: Icon( + prize.icon, + color: AppColor.white, + size: prize.isMain ? 24 : 20, + ), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + prize.title, + style: TextStyle( + color: prize.color, + fontWeight: FontWeight.w700, + fontSize: 11, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4), + Text( + prize.description, + style: TextStyle( + color: AppColor.white, + fontWeight: prize.isMain + ? FontWeight.w700 + : FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ), + if (prize.isMain) + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: prize.color.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon(Icons.star, color: prize.color, size: 16), + ), + ], + ), + ); + } + + Widget _buildShoppingCard() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.8), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.5, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: _buildCard( + child: Column( + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + Icons.shopping_bag_outlined, + color: AppColor.white, + size: 24, + ), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Perbesar Peluang Menang!', + style: TextStyle( + color: AppColor.white, + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + SizedBox(height: 4), + Text( + 'Belanja sekarang untuk ticket tambahan', + style: TextStyle( + color: AppColor.white.withOpacity(0.8), + fontWeight: FontWeight.w500, + fontSize: 11, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.2), + width: 1, + ), + ), + child: Column( + children: [ + _buildBenefit('🎫', 'Setiap Rp 100K = 1 ticket undian'), + SizedBox(height: 12), + _buildBenefit( + '⚡', + 'Semakin banyak belanja, semakin berpeluang', + ), + SizedBox(height: 12), + _buildBenefit('🏆', 'Kesempatan menang hingga 3KG emas!'), + ], + ), + ), + SizedBox(height: 20), + Container( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Mengarahkan ke halaman belanja...'), + backgroundColor: AppColor.success, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.white, + foregroundColor: AppColor.primary, + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.shopping_cart_outlined, size: 20), + SizedBox(width: 12), + Text( + 'Mulai Belanja Sekarang', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildBenefit(String icon, String text) { + return Row( + children: [ + Text(icon, style: TextStyle(fontSize: 16)), + SizedBox(width: 12), + Expanded( + child: Text( + text, + style: TextStyle( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + fontSize: 11, + ), + ), + ), + ], + ); + } + + Widget _buildTermsAndConditions() { + return SlideTransition( + position: Tween(begin: Offset(0, 1.0), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.7, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: _buildCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.description_outlined, + color: AppColor.white.withOpacity(0.9), + size: 20, + ), + SizedBox(width: 12), + Text( + 'Syarat & Ketentuan', + style: TextStyle( + color: AppColor.white, + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ], + ), + SizedBox(height: 20), + _buildTermItem( + '1.', + 'Undian berlaku untuk seluruh pelanggan yang berbelanja minimal Rp 100.000', + ), + _buildTermItem( + '2.', + 'Setiap pembelian Rp 100.000 mendapat 1 nomor undian otomatis', + ), + _buildTermItem( + '3.', + 'Pengundian dilakukan secara live streaming setiap hari pukul 20:00 WIB', + ), + _buildTermItem( + '4.', + 'Pemenang akan dihubungi maksimal 24 jam setelah pengundian', + ), + _buildTermItem( + '5.', + 'Hadiah tidak dapat diuangkan atau dipindahtangankan', + ), + _buildTermItem( + '6.', + 'Keputusan panitia adalah final dan tidak dapat diganggu gugat', + ), + SizedBox(height: 20), + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(Icons.info_outline, color: AppColor.white, size: 16), + SizedBox(width: 12), + Expanded( + child: Text( + 'Untuk informasi lebih lanjut, hubungi customer service kami', + style: TextStyle( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + fontSize: 11, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildTermItem(String number, String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 24, + child: Text( + number, + style: TextStyle( + color: AppColor.white, + fontWeight: FontWeight.w600, + fontSize: 11, + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: Text( + text, + style: TextStyle( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + fontSize: 11, + height: 1.4, + ), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + controller: _scrollController, + physics: BouncingScrollPhysics(), + slivers: [ + SliverAppBar( + expandedHeight: 280, + floating: false, + pinned: true, + backgroundColor: AppColor.surface, + leading: Container( + margin: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(20), + ), + child: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + flexibleSpace: FlexibleSpaceBar( + title: AnimatedOpacity( + opacity: _isCollapsed ? 1.0 : 0.0, + duration: Duration(milliseconds: 200), + child: Text( + "Undian", + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + fontSize: 18, + ), + ), + ), + titlePadding: EdgeInsets.only(left: 72, bottom: 16), + collapseMode: CollapseMode.parallax, + background: Stack( + children: [ + Positioned.fill( + child: CarouselSlider.builder( + carouselController: _carouselController, + itemCount: banners.length, + options: CarouselOptions( + height: double.infinity, + viewportFraction: 1.0, + autoPlay: true, + autoPlayInterval: Duration(seconds: 4), + autoPlayAnimationDuration: Duration(milliseconds: 800), + onPageChanged: (index, reason) { + setState(() => _currentBannerIndex = index); + }, + ), + itemBuilder: (context, index, realIndex) { + final banner = banners[index]; + return GestureDetector( + onTap: () => + _showFullImage(banner.imageUrl, banner.title), + child: AppNetworkImage(url: banner.imageUrl), + ); + }, + ), + ), + + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: banners.asMap().entries.map((entry) { + return GestureDetector( + onTap: () => + _carouselController.animateToPage(entry.key), + child: Container( + width: _currentBannerIndex == entry.key ? 24 : 8, + height: 8, + margin: EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: _currentBannerIndex == entry.key + ? Colors.white + : Colors.white.withOpacity(0.4), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + ), + SliverToBoxAdapter( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: FadeTransition( + opacity: _fadeAnimation, + child: Column( + children: [ + SizedBox(height: 20), + _buildLotteryCard(), + SizedBox(height: 20), + _buildPrizeCard(), + SizedBox(height: 20), + if (isOngoing) _buildShoppingCard(), + if (isOngoing) SizedBox(height: 20), + _buildTermsAndConditions(), + SizedBox(height: 40), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class PrizeModel { + final String title; + final String description; + final IconData icon; + final Color color; + final bool isMain; + + PrizeModel({ + required this.title, + required this.description, + required this.icon, + required this.color, + this.isMain = false, + }); +} + +class BannerModel { + final String imageUrl; + final String title; + + BannerModel({required this.imageUrl, required this.title}); +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart new file mode 100644 index 0000000..e29d6a7 --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart @@ -0,0 +1,685 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; + +@RoutePage() +class DrawInfoPage extends StatefulWidget { + const DrawInfoPage({super.key}); + + @override + State createState() => _DrawInfoPageState(); +} + +class _DrawInfoPageState extends State + with TickerProviderStateMixin { + bool showHadiah = true; + + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + late AnimationController _tabController; + + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + + _slideController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + + _scaleController = AnimationController( + duration: Duration(milliseconds: 1000), + vsync: this, + ); + + _tabController = AnimationController( + duration: Duration(milliseconds: 300), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween(begin: Offset(0, 0.3), end: Offset.zero) + .animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _startAnimations(); + } + + void _startAnimations() async { + await Future.delayed(Duration(milliseconds: 100)); + _fadeController.forward(); + + await Future.delayed(Duration(milliseconds: 200)); + _slideController.forward(); + + await Future.delayed(Duration(milliseconds: 300)); + _scaleController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _tabController.dispose(); + super.dispose(); + } + + Widget _buildTitle() { + return SlideTransition( + position: _slideAnimation, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + children: [ + Container( + width: 3, + height: 28, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(2), + ), + ), + SizedBox(width: 12), + Text( + 'Hadiah & Info', + style: AppStyle.h3.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + ], + ), + ), + ); + } + + Widget _buildTabButtons() { + return ScaleTransition( + scale: _scaleAnimation, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + setState(() => showHadiah = true); + _tabController.forward(); + }, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: showHadiah + ? AppColor.white + : AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: showHadiah + ? AppColor.white + : AppColor.white.withOpacity(0.3), + width: 1, + ), + boxShadow: showHadiah + ? [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 15, + offset: Offset(0, 5), + ), + ] + : null, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.card_giftcard_rounded, + color: showHadiah + ? AppColor.primary + : AppColor.white.withOpacity(0.8), + size: 18, + ), + SizedBox(width: 6), + Text( + 'Hadiah', + style: AppStyle.sm.copyWith( + color: showHadiah + ? AppColor.primary + : AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: GestureDetector( + onTap: () { + setState(() => showHadiah = false); + _tabController.forward(); + }, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: EdgeInsets.symmetric(vertical: 14), + decoration: BoxDecoration( + color: !showHadiah + ? AppColor.white + : AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: !showHadiah + ? AppColor.white + : AppColor.white.withOpacity(0.3), + width: 1, + ), + boxShadow: !showHadiah + ? [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 15, + offset: Offset(0, 5), + ), + ] + : null, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.info_outline_rounded, + color: !showHadiah + ? AppColor.primary + : AppColor.white.withOpacity(0.8), + size: 18, + ), + SizedBox(width: 6), + Text( + 'Info Undian', + style: AppStyle.sm.copyWith( + color: !showHadiah + ? AppColor.primary + : AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildHadiahContent() { + return ListView( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 20), + children: [ + SizedBox(height: 20), + _buildPrizeCard( + 'Emas Batangan 3KG', + 'Hadiah utama untuk pemenang harian', + Icons.stars_rounded, + true, + 0, + ), + _buildPrizeCard( + 'Motor Yamaha NMAX', + 'Hadiah mingguan untuk 1 pemenang', + Icons.two_wheeler_rounded, + false, + 1, + ), + _buildPrizeCard( + 'Voucher Belanja 1 Juta', + 'Hadiah bulanan untuk 5 pemenang', + Icons.card_giftcard_rounded, + false, + 2, + ), + _buildPrizeCard( + 'Smartphone Samsung', + 'Hadiah khusus acara tertentu', + Icons.smartphone_rounded, + false, + 3, + ), + SizedBox(height: 20), + _buildContactButton(), + SizedBox(height: 30), + ], + ); + } + + Widget _buildInfoContent() { + return ListView( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 20), + children: [ + SizedBox(height: 20), + _buildInfoSection( + 'Cara Bermain', + [ + 'Setiap hari kamu akan mendapat 1 nomor undian otomatis', + 'Nomor undian global diumumkan setiap hari jam 8 malam', + 'Jika nomormu sama dengan nomor global, kamu menang!', + 'Pemenang akan dihubungi melalui kontak yang terdaftar', + ], + Icons.play_circle_outline_rounded, + 0, + ), + _buildInfoSection( + 'Syarat & Ketentuan', + [ + 'Peserta minimal berusia 18 tahun', + 'Satu akun per nomor telepon', + 'Hadiah wajib diambil dalam 30 hari', + 'Keputusan panitia tidak dapat diganggu gugat', + ], + Icons.gavel_rounded, + 1, + ), + _buildInfoSection( + 'FAQ', + [ + 'Q: Apakah gratis? A: Ya, sepenuhnya gratis', + 'Q: Berapa lama menunggu hasil? A: Maksimal 1 jam setelah pengumuman', + 'Q: Bisa ganti nomor? A: Tidak, nomor otomatis dari sistem', + 'Q: Pajak hadiah? A: Ditanggung penyelenggara', + ], + Icons.help_outline_rounded, + 2, + ), + SizedBox(height: 20), + _buildContactButton(), + SizedBox(height: 30), + ], + ); + } + + Widget _buildPrizeCard( + String title, + String description, + IconData icon, + bool isGold, + int index, + ) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 800 + (index * 200)), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 30 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: isGold + ? AppColor.white + : AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: isGold + ? AppColor.warning.withOpacity(0.3) + : AppColor.white.withOpacity(0.2), + width: isGold ? 2 : 1, + ), + boxShadow: [ + BoxShadow( + color: isGold + ? AppColor.warning.withOpacity(0.3) + : AppColor.black.withOpacity(0.05), + blurRadius: isGold ? 20 : 10, + offset: Offset(0, isGold ? 8 : 4), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: isGold + ? LinearGradient( + colors: [ + AppColor.warning, + AppColor.warning.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: isGold + ? AppColor.warning.withOpacity(0.3) + : AppColor.primary.withOpacity(0.3), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Icon(icon, color: AppColor.white, size: 24), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.md.copyWith( + color: isGold ? AppColor.primary : AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + SizedBox(height: 4), + Text( + description, + style: AppStyle.sm.copyWith( + color: isGold + ? AppColor.textSecondary + : AppColor.white.withOpacity(0.8), + fontWeight: FontWeight.w500, + height: 1.3, + ), + ), + ], + ), + ), + if (isGold) + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'TOP', + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildInfoSection( + String title, + List items, + IconData icon, + int index, + ) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 800 + (index * 200)), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 30 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColor.white.withOpacity(0.2), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 15, + offset: Offset(0, 8), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: AppColor.white, size: 18), + ), + SizedBox(width: 12), + Text( + title, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + SizedBox(height: 16), + ...items + .map( + (item) => Padding( + padding: EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 4, + height: 4, + margin: EdgeInsets.only(top: 8, right: 12), + decoration: BoxDecoration( + color: AppColor.warning, + shape: BoxShape.circle, + ), + ), + Expanded( + child: Text( + item, + style: AppStyle.sm.copyWith( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + height: 1.4, + ), + ), + ), + ], + ), + ), + ) + .toList(), + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildContactButton() { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 1200), + curve: Curves.elasticOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Menghubungi layanan pelanggan...', + style: AppStyle.sm.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + behavior: SnackBarBehavior.floating, + ), + ); + }, + borderRadius: BorderRadius.circular(16), + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.support_agent_rounded, + color: AppColor.white, + size: 20, + ), + SizedBox(width: 8), + Text( + 'Hubungi Layanan Pelanggan', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: Column( + children: [ + SizedBox(height: 20), + + // Title + _buildTitle(), + + SizedBox(height: 20), + + // Tab Buttons + _buildTabButtons(), + + SizedBox(height: 20), + + // Content + Expanded( + child: AnimatedSwitcher( + duration: Duration(milliseconds: 300), + transitionBuilder: + (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: Offset(0.0, 0.1), + end: Offset.zero, + ).animate(animation), + child: child, + ), + ); + }, + child: showHadiah + ? _buildHadiahContent() + : _buildInfoContent(), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart new file mode 100644 index 0000000..0e4571b --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart @@ -0,0 +1,478 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; + +@RoutePage() +class DrawMyNumberPage extends StatefulWidget { + const DrawMyNumberPage({super.key}); + + @override + State createState() => _DrawMyNumberPageState(); +} + +class _DrawMyNumberPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + late AnimationController _pulseController; + + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + late Animation _pulseAnimation; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + + _slideController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + + _scaleController = AnimationController( + duration: Duration(milliseconds: 1000), + vsync: this, + ); + + _pulseController = AnimationController( + duration: Duration(milliseconds: 2500), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween(begin: Offset(0, 0.3), end: Offset.zero) + .animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.03).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _startAnimations(); + } + + void _startAnimations() async { + await Future.delayed(Duration(milliseconds: 100)); + _fadeController.forward(); + + await Future.delayed(Duration(milliseconds: 200)); + _slideController.forward(); + + await Future.delayed(Duration(milliseconds: 300)); + _scaleController.forward(); + + await Future.delayed(Duration(milliseconds: 800)); + _pulseController.repeat(reverse: true); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _pulseController.dispose(); + super.dispose(); + } + + Widget _buildTitle() { + return SlideTransition( + position: _slideAnimation, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + children: [ + Container( + width: 3, + height: 28, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(2), + ), + ), + SizedBox(width: 12), + Text( + 'Nomorku', + style: AppStyle.h3.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + ], + ), + ), + ); + } + + Widget _buildMyNumberCard() { + return ScaleTransition( + scale: _scaleAnimation, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.08), + blurRadius: 30, + spreadRadius: 0, + offset: Offset(0, 15), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(24), + child: Column( + children: [ + // Label + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.08), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + 'NOMOR UNDIANMU HARI INI', + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w700, + letterSpacing: 1, + ), + ), + ), + + SizedBox(height: 20), + + // Animated Number + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary.withOpacity(0.05), + AppColor.primary.withOpacity(0.02), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.primary.withOpacity(0.1), + width: 1, + ), + ), + child: Text( + '123123', + style: TextStyle( + color: AppColor.primary, + fontSize: 42, + fontWeight: FontWeight.w900, + letterSpacing: 12, + height: 1.0, + ), + ), + ), + ); + }, + ), + + SizedBox(height: 16), + + // Status Badge + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 1200), + curve: Curves.elasticOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Text( + 'Menunggu Hasil', + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildShareButton() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.5), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.3, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Membagikan nomormu: 123123', + style: AppStyle.sm.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + behavior: SnackBarBehavior.floating, + ), + ); + }, + borderRadius: BorderRadius.circular(16), + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.share_rounded, color: AppColor.white, size: 20), + SizedBox(width: 8), + Text( + 'Bagikan Nomorku', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildHistorySection() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.8), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.5, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Riwayat Nomorku', + style: AppStyle.lg.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 12), + ], + ), + ), + ); + } + + Widget _buildHistoryCard( + String number, + String date, + String result, + int index, + ) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 800 + (index * 200)), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 30 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20, vertical: 6), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.15), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + // Number and Date + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + number, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + letterSpacing: 2, + ), + ), + SizedBox(height: 4), + Text( + date, + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.7), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + + // Result Badge + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColor.error.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColor.error.withOpacity(0.3), + width: 1, + ), + ), + child: Text( + result, + style: AppStyle.xs.copyWith( + color: AppColor.error.withOpacity(0.9), + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: ListView( + physics: BouncingScrollPhysics(), + children: [ + SizedBox(height: 20), + + // Title + _buildTitle(), + + SizedBox(height: 24), + + // My Number Card + _buildMyNumberCard(), + + SizedBox(height: 20), + + // Share Button + _buildShareButton(), + + SizedBox(height: 24), + + // History Section Title + _buildHistorySection(), + + // History Cards + _buildHistoryCard('138472', 'Kemarin', 'Kalah 😔', 0), + _buildHistoryCard('928374', '2 hari lalu', 'Kalah 😔', 1), + _buildHistoryCard('475829', '3 hari lalu', 'Kalah 😔', 2), + + SizedBox(height: 20), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart new file mode 100644 index 0000000..c07e12d --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart @@ -0,0 +1,949 @@ +import 'dart:math' show cos, sin; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; + +@RoutePage() +class DrawTodayPage extends StatefulWidget { + const DrawTodayPage({super.key}); + + @override + State createState() => _DrawTodayPageState(); +} + +class _DrawTodayPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + late AnimationController _pulseController; + late AnimationController _celebrationController; + + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + late Animation _pulseAnimation; + late Animation _celebrationAnimation; + + // Lottery Logic + String globalNumber = '849302'; + String userNumber = '849302'; // Change this to test winner/loser + bool isWinner = false; + bool hasCheckedResult = false; + + @override + void initState() { + super.initState(); + + // Check if user is winner + isWinner = userNumber == globalNumber; + + _fadeController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + + _slideController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + + _scaleController = AnimationController( + duration: Duration(milliseconds: 1000), + vsync: this, + ); + + _pulseController = AnimationController( + duration: Duration(milliseconds: 2000), + vsync: this, + ); + + _celebrationController = AnimationController( + duration: Duration(milliseconds: 3000), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween(begin: Offset(0, 0.3), end: Offset.zero) + .animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.02).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _celebrationAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _celebrationController, curve: Curves.elasticOut), + ); + + _startAnimations(); + } + + void _startAnimations() async { + await Future.delayed(Duration(milliseconds: 100)); + _fadeController.forward(); + + await Future.delayed(Duration(milliseconds: 200)); + _slideController.forward(); + + await Future.delayed(Duration(milliseconds: 300)); + _scaleController.forward(); + + if (isWinner) { + await Future.delayed(Duration(milliseconds: 600)); + _celebrationController.forward(); + } + + await Future.delayed(Duration(milliseconds: 800)); + _pulseController.repeat(reverse: true); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _pulseController.dispose(); + _celebrationController.dispose(); + super.dispose(); + } + + void _checkMyNumber() { + setState(() { + hasCheckedResult = true; + }); + + if (isWinner) { + _showWinnerDialog(); + } else { + _showLoserDialog(); + } + } + + void _showWinnerDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.warning, AppColor.warning.withOpacity(0.8)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.5), + blurRadius: 30, + spreadRadius: 5, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Animated Trophy + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 1000), + curve: Curves.elasticOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Transform.rotate( + angle: (1 - value) * 0.5, + child: Icon( + Icons.emoji_events_rounded, + color: AppColor.white, + size: 80, + ), + ), + ); + }, + ), + + SizedBox(height: 20), + + Text( + 'SELAMAT!', + style: AppStyle.h2.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w900, + letterSpacing: 2, + ), + ), + + SizedBox(height: 8), + + Text( + 'Kamu Pemenang Hari Ini!', + style: AppStyle.lg.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + + SizedBox(height: 16), + + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + Text( + 'Nomor Pemenang', + style: AppStyle.sm.copyWith( + color: AppColor.white.withOpacity(0.8), + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 8), + Text( + globalNumber, + style: TextStyle( + color: AppColor.white, + fontSize: 32, + fontWeight: FontWeight.w900, + letterSpacing: 8, + ), + ), + ], + ), + ), + + SizedBox(height: 20), + + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '🏆 Hadiah: 3KG Emas Batangan 🏆', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ), + + SizedBox(height: 24), + + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.white, + foregroundColor: AppColor.primary, + padding: EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Tutup', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Tim kami akan menghubungimu segera!', + ), + backgroundColor: AppColor.success, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + behavior: SnackBarBehavior.floating, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Klaim Hadiah', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + void _showLoserDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 20, + spreadRadius: 0, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.close_rounded, + color: AppColor.primary, + size: 40, + ), + ), + + SizedBox(height: 16), + + Text( + 'Belum Beruntung', + style: AppStyle.lg.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w700, + ), + ), + + SizedBox(height: 8), + + Text( + 'Jangan menyerah! Coba lagi besok ya', + textAlign: TextAlign.center, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + + SizedBox(height: 16), + + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.05), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Nomor Global:', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + Text( + globalNumber, + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w700, + letterSpacing: 2, + ), + ), + ], + ), + SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Nomor Kamu:', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + Text( + userNumber, + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w700, + letterSpacing: 2, + ), + ), + ], + ), + ], + ), + ), + + SizedBox(height: 20), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Oke, Sampai Jumpa Besok!', + style: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildTitle() { + return SlideTransition( + position: _slideAnimation, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + children: [ + Container( + width: 3, + height: 28, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(2), + ), + ), + SizedBox(width: 12), + Text( + 'Undian Hari Ini', + style: AppStyle.h3.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + ], + ), + ), + ); + } + + Widget _buildLotteryCard() { + return ScaleTransition( + scale: _scaleAnimation, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: isWinner + ? AppColor.warning.withOpacity(0.3) + : AppColor.black.withOpacity(0.08), + blurRadius: 30, + spreadRadius: 0, + offset: Offset(0, 15), + ), + ], + ), + child: Stack( + children: [ + // Winner celebration overlay + if (isWinner) + Positioned.fill( + child: AnimatedBuilder( + animation: _celebrationAnimation, + builder: (context, child) { + return CustomPaint( + painter: WinnerCelebrationPainter( + _celebrationAnimation.value, + ), + ); + }, + ), + ), + + Padding( + padding: EdgeInsets.all(24), + child: Column( + children: [ + // Label + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: isWinner + ? AppColor.warning.withOpacity(0.15) + : AppColor.primary.withOpacity(0.08), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + isWinner ? '🎉 NOMOR PEMENANG 🎉' : 'NOMOR UNDIAN GLOBAL', + style: AppStyle.xs.copyWith( + color: isWinner ? AppColor.warning : AppColor.primary, + fontWeight: FontWeight.w700, + letterSpacing: 1, + ), + ), + ), + + SizedBox(height: 20), + + // Animated Number + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: isWinner + ? [ + AppColor.warning.withOpacity(0.1), + AppColor.warning.withOpacity(0.05), + ] + : [ + AppColor.primary.withOpacity(0.05), + AppColor.primary.withOpacity(0.02), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isWinner + ? AppColor.warning.withOpacity(0.3) + : AppColor.primary.withOpacity(0.1), + width: 1, + ), + ), + child: Text( + globalNumber, + style: TextStyle( + color: isWinner + ? AppColor.warning + : AppColor.primary, + fontSize: 42, + fontWeight: FontWeight.w900, + letterSpacing: 12, + height: 1.0, + ), + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildPrizeCard() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.5), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.3, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.white.withOpacity(0.2), width: 1), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 15, + offset: Offset(0, 8), + ), + ], + ), + child: Column( + children: [ + // Animated Icon + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 1500), + curve: Curves.elasticOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.warning, + AppColor.warning.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.3), + blurRadius: 15, + offset: Offset(0, 6), + ), + ], + ), + child: Icon( + Icons.stars_rounded, + color: AppColor.white, + size: 24, + ), + ), + ); + }, + ), + + SizedBox(height: 16), + + Text( + 'HADIAH UTAMA', + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.7), + fontWeight: FontWeight.w600, + letterSpacing: 1.5, + ), + ), + + SizedBox(height: 6), + + Text( + 'Jika nomor undianmu sama,\nkamu pemenang 3KG Emas!', + textAlign: TextAlign.center, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + height: 1.4, + ), + ), + ], + ), + ), + ); + } + + Widget _buildInfoCards() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.8), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.5, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Column( + children: [ + // Check My Number Button + Container( + width: double.infinity, + margin: EdgeInsets.only(bottom: 12), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: hasCheckedResult ? null : _checkMyNumber, + borderRadius: BorderRadius.circular(16), + child: Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: hasCheckedResult + ? null + : LinearGradient( + colors: [ + AppColor.warning.withOpacity(0.8), + AppColor.warning, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + color: hasCheckedResult + ? AppColor.black.withOpacity(0.15) + : null, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: hasCheckedResult + ? AppColor.white.withOpacity(0.15) + : AppColor.warning.withOpacity(0.3), + width: 1, + ), + boxShadow: hasCheckedResult + ? null + : [ + BoxShadow( + color: AppColor.warning.withOpacity(0.3), + blurRadius: 15, + offset: Offset(0, 5), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + hasCheckedResult + ? Icons.check_circle_rounded + : Icons.casino_rounded, + color: hasCheckedResult + ? AppColor.white.withOpacity(0.5) + : AppColor.white, + size: 20, + ), + SizedBox(width: 8), + Text( + hasCheckedResult + ? 'Sudah Dicek' + : 'Cek Nomorku Sekarang!', + style: AppStyle.md.copyWith( + color: hasCheckedResult + ? AppColor.white.withOpacity(0.5) + : AppColor.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + ), + ), + + // Next Draw Card + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.black.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.white.withOpacity(0.15), + width: 1, + ), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.schedule_rounded, + color: AppColor.white, + size: 18, + ), + ), + + SizedBox(width: 12), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Undian Berikutnya', + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.7), + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 2), + Text( + 'Diumumkan jam 8 malam', + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ), + + SizedBox(height: 12), + + // Check Ticket Card + Container( + width: double.infinity, + padding: EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.08), + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: AppColor.white.withOpacity(0.12), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.confirmation_number_outlined, + color: AppColor.white.withOpacity(0.8), + size: 16, + ), + SizedBox(width: 8), + Text( + 'Nomormu hari ini: $userNumber', + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.9), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: ListView( + physics: BouncingScrollPhysics(), + children: [ + SizedBox(height: 20), + + // Title + _buildTitle(), + + SizedBox(height: 24), + + // Lottery Card + _buildLotteryCard(), + + SizedBox(height: 20), + + // Prize Card + _buildPrizeCard(), + + SizedBox(height: 16), + + // Info Cards + _buildInfoCards(), + + SizedBox(height: 20), + ], + ), + ), + ), + ), + ); + } +} + +// Custom painter for winner celebration effect +class WinnerCelebrationPainter extends CustomPainter { + final double animationValue; + + WinnerCelebrationPainter(this.animationValue); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..style = PaintingStyle.fill; + + // Draw floating golden particles + for (int i = 0; i < 12; i++) { + final angle = (i * 30) * (3.14159 / 180); + final radius = animationValue * 50; + final x = size.width / 2 + radius * cos(angle); + final y = size.height / 2 + radius * sin(angle); + + paint.color = AppColor.warning.withOpacity(0.4 * animationValue); + canvas.drawCircle(Offset(x, y), 3 * animationValue, paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart new file mode 100644 index 0000000..ce7885e --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart @@ -0,0 +1,536 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'dart:math' show cos, sin; + +import '../../../../../../common/theme/theme.dart'; + +@RoutePage() +class DrawWinnerPage extends StatefulWidget { + const DrawWinnerPage({super.key}); + + @override + State createState() => _DrawWinnerPageState(); +} + +class _DrawWinnerPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _scaleController; + late AnimationController _pulseController; + late AnimationController _celebrationController; + + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + late Animation _pulseAnimation; + late Animation _celebrationAnimation; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + + _slideController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + + _scaleController = AnimationController( + duration: Duration(milliseconds: 1000), + vsync: this, + ); + + _pulseController = AnimationController( + duration: Duration(milliseconds: 2000), + vsync: this, + ); + + _celebrationController = AnimationController( + duration: Duration(milliseconds: 3000), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween(begin: Offset(0, 0.3), end: Offset.zero) + .animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), + ); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _celebrationAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _celebrationController, curve: Curves.elasticOut), + ); + + _startAnimations(); + } + + void _startAnimations() async { + await Future.delayed(Duration(milliseconds: 100)); + _fadeController.forward(); + + await Future.delayed(Duration(milliseconds: 200)); + _slideController.forward(); + + await Future.delayed(Duration(milliseconds: 300)); + _scaleController.forward(); + + await Future.delayed(Duration(milliseconds: 600)); + _celebrationController.forward(); + + await Future.delayed(Duration(milliseconds: 800)); + _pulseController.repeat(reverse: true); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _scaleController.dispose(); + _pulseController.dispose(); + _celebrationController.dispose(); + super.dispose(); + } + + Widget _buildTitle() { + return SlideTransition( + position: _slideAnimation, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + children: [ + Container( + width: 3, + height: 28, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(2), + ), + ), + SizedBox(width: 12), + Text( + 'Pemenang', + style: AppStyle.h3.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + ], + ), + ), + ); + } + + Widget _buildTodayWinnerBanner() { + return ScaleTransition( + scale: _scaleAnimation, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.3), + blurRadius: 30, + spreadRadius: 0, + offset: Offset(0, 15), + ), + ], + ), + child: Stack( + children: [ + // Celebration Particles Effect + Positioned.fill( + child: AnimatedBuilder( + animation: _celebrationAnimation, + builder: (context, child) { + return CustomPaint( + painter: CelebrationPainter(_celebrationAnimation.value), + ); + }, + ), + ), + + // Main Content + Padding( + padding: EdgeInsets.all(24), + child: Column( + children: [ + // Trophy Icon with Animation + AnimatedBuilder( + animation: _celebrationAnimation, + builder: (context, child) { + return Transform.scale( + scale: _celebrationAnimation.value, + child: Transform.rotate( + angle: _celebrationAnimation.value * 0.1, + child: Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.warning, + AppColor.warning.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.4), + blurRadius: 20, + offset: Offset(0, 8), + ), + ], + ), + child: Icon( + Icons.emoji_events_rounded, + color: AppColor.white, + size: 32, + ), + ), + ), + ); + }, + ), + + SizedBox(height: 16), + + // Title with Emoji + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Pemenang Hari Ini', + style: AppStyle.lg.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w700, + ), + ), + SizedBox(width: 8), + TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 1500), + curve: Curves.elasticOut, + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Text('🎉', style: TextStyle(fontSize: 20)), + ); + }, + ), + ], + ), + + SizedBox(height: 20), + + // Winning Number + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary.withOpacity(0.05), + AppColor.primary.withOpacity(0.02), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColor.primary.withOpacity(0.1), + width: 1, + ), + ), + child: Text( + '849302', + style: TextStyle( + color: AppColor.primary, + fontSize: 36, + fontWeight: FontWeight.w900, + letterSpacing: 8, + height: 1.0, + ), + ), + ), + ); + }, + ), + + SizedBox(height: 16), + + // Winner Name + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Text( + 'Selamat untuk Andi***', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildHistorySection() { + return SlideTransition( + position: Tween(begin: Offset(0, 0.5), end: Offset.zero).animate( + CurvedAnimation( + parent: _slideController, + curve: Interval(0.3, 1.0, curve: Curves.easeOutCubic), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pemenang Kemarin', + style: AppStyle.lg.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 12), + ], + ), + ), + ); + } + + Widget _buildWinnerCard(String number, String name, String date, int index) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 800 + (index * 200)), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, 30 * (1 - value)), + child: Opacity( + opacity: value, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 20, vertical: 6), + padding: EdgeInsets.all(18), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.12), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColor.white.withOpacity(0.2), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 15, + offset: Offset(0, 8), + ), + ], + ), + child: Row( + children: [ + // Trophy Icon + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.warning, + AppColor.warning.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.warning.withOpacity(0.3), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.emoji_events_rounded, + color: AppColor.white, + size: 20, + ), + ), + + SizedBox(width: 16), + + // Winner Info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + number, + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w700, + letterSpacing: 2, + ), + ), + SizedBox(height: 4), + Text( + name, + style: AppStyle.sm.copyWith( + color: AppColor.warning, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 2), + Text( + date, + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.7), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + + // Winner Badge + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColor.success.withOpacity(0.3), + width: 1, + ), + ), + child: Text('🏆', style: TextStyle(fontSize: 12)), + ), + ], + ), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: ListView( + physics: BouncingScrollPhysics(), + children: [ + SizedBox(height: 20), + + // Title + _buildTitle(), + + SizedBox(height: 24), + + // Today's Winner Banner + _buildTodayWinnerBanner(), + + SizedBox(height: 24), + + // History Section Title + _buildHistorySection(), + + // Winner History Cards + _buildWinnerCard('138472', 'Budi***', 'Kemarin', 0), + _buildWinnerCard('928374', 'Sari***', '2 hari lalu', 1), + _buildWinnerCard('475829', 'Dono***', '3 hari lalu', 2), + _buildWinnerCard('629384', 'Lisa***', '4 hari lalu', 3), + + SizedBox(height: 20), + ], + ), + ), + ), + ), + ); + } +} + +// Custom painter for celebration particles effect +class CelebrationPainter extends CustomPainter { + final double animationValue; + + CelebrationPainter(this.animationValue); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..style = PaintingStyle.fill; + + // Draw floating particles + for (int i = 0; i < 8; i++) { + final angle = (i * 45) * (3.14159 / 180); + final radius = animationValue * 40; + final x = size.width / 2 + radius * cos(angle); + final y = size.height / 2 + radius * sin(angle); + + paint.color = AppColor.warning.withOpacity(0.3 * animationValue); + canvas.drawCircle(Offset(x, y), 2 * animationValue, paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/presentation/pages/draw/pages/draw_detail/widgets/bottom_navbar.dart b/lib/presentation/pages/draw/pages/draw_detail/widgets/bottom_navbar.dart new file mode 100644 index 0000000..f9dda29 --- /dev/null +++ b/lib/presentation/pages/draw/pages/draw_detail/widgets/bottom_navbar.dart @@ -0,0 +1,39 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +class DrawDetailBottomNavbar extends StatelessWidget { + final TabsRouter tabsRouter; + const DrawDetailBottomNavbar({super.key, required this.tabsRouter}); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + currentIndex: tabsRouter.activeIndex, + onTap: (index) { + tabsRouter.setActiveIndex(index); + }, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.today), + label: 'Hari Ini', + tooltip: 'Hari Ini', + ), + BottomNavigationBarItem( + icon: Icon(Icons.confirmation_number), + label: 'Nomorku', + tooltip: 'Nomorku', + ), + BottomNavigationBarItem( + icon: Icon(Icons.emoji_events), + label: 'Pemenang', + tooltip: 'Pemenang', + ), + BottomNavigationBarItem( + icon: Icon(Icons.info), + label: 'Hadiah & Info', + tooltip: 'Hadiah & Info', + ), + ], + ); + } +} diff --git a/lib/presentation/pages/main/main_page.dart b/lib/presentation/pages/main/main_page.dart new file mode 100644 index 0000000..6e46d27 --- /dev/null +++ b/lib/presentation/pages/main/main_page.dart @@ -0,0 +1,37 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../application/auth/auth_bloc.dart'; +import '../../router/app_router.gr.dart'; +import 'widgets/bottom_navbar.dart'; + +@RoutePage() +class MainPage extends StatefulWidget { + const MainPage({super.key}); + + @override + State createState() => _MainPageState(); +} + +class _MainPageState extends State { + @override + initState() { + super.initState(); + context.read().add(const AuthEvent.fetchCurrentUser()); + } + + @override + Widget build(BuildContext context) { + return AutoTabsRouter.pageView( + routes: [HomeRoute(), VoucherRoute(), OrderRoute(), ProfileRoute()], + physics: const NeverScrollableScrollPhysics(), + builder: (context, child, pageController) => Scaffold( + body: child, + bottomNavigationBar: MainBottomNavbar( + tabsRouter: AutoTabsRouter.of(context), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/home_page.dart b/lib/presentation/pages/main/pages/home/home_page.dart new file mode 100644 index 0000000..d92a23c --- /dev/null +++ b/lib/presentation/pages/main/pages/home/home_page.dart @@ -0,0 +1,200 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/auth/auth_bloc.dart'; +import '../../../../../application/customer/customer_point_loader/customer_point_loader_bloc.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../components/image/image.dart'; +import '../../../../router/app_router.gr.dart'; +import 'widgets/feature_section.dart'; +import 'widgets/lottery_card.dart'; +import 'widgets/point_card.dart'; +import 'widgets/popular_merchant_section.dart'; + +@RoutePage() +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _currentCarouselIndex = 0; + final CarouselSliderController _carouselController = + CarouselSliderController(); + + final List _carouselImages = [ + 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=800&h=400&fit=crop', + 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?w=800&h=400&fit=crop', + 'https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=800&h=400&fit=crop', + 'https://images.unsplash.com/photo-1574848794584-c740d6a5595f?w=800&h=400&fit=crop', + ]; + + @override + void initState() { + super.initState(); + if (context.read().state.isAuthenticated) { + context.read().add( + CustomerPointLoaderEvent.fetched(), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeaderSection(), + const SizedBox(height: 70), + HomeFeatureSection(), + HomeLotteryBanner(onTap: () => context.router.push(DrawRoute())), + HomePopularMerchantSection(), + ], + ), + ), + ); + } + + Widget _buildHeaderSection() { + return Stack( + clipBehavior: Clip.none, + children: [ + _buildCarouselBanner(), + _buildNotificationButton(), + + Positioned( + left: 0, + right: 0, + top: 225, + child: _buildCarouselIndicators(), + ), + Positioned( + left: 16, + right: 16, + top: 240, + child: HomePointCard( + point: context + .read() + .state + .customerPoint + .totalPoints + .toString(), + ), + ), + ], + ); + } + + // Notification Button + Widget _buildNotificationButton() { + return Positioned( + top: MediaQuery.of(context).padding.top + 10, + right: 16, + child: GestureDetector( + onTap: () => context.router.push(NotificationRoute()), + child: Stack( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColor.black.withOpacity(0.3), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.notifications_outlined, + color: AppColor.white, + size: 20, + ), + ), + Positioned( + right: 8, + top: 8, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: AppColor.primary, + shape: BoxShape.circle, + ), + ), + ), + ], + ), + ), + ); + } + + // Carousel Banner (Full Width) + Widget _buildCarouselBanner() { + return CarouselSlider( + carouselController: _carouselController, + options: CarouselOptions( + height: 280, + viewportFraction: 1.0, // Full width + enlargeCenterPage: false, + autoPlay: true, + autoPlayInterval: const Duration(seconds: 4), + onPageChanged: (index, reason) { + setState(() { + _currentCarouselIndex = index; + }); + }, + ), + items: _carouselImages + .skip(1) + .map((imageUrl) => _buildImageSlide(imageUrl)) + .toList(), + ); + } + + Widget _buildImageSlide(String imageUrl) { + return SizedBox( + width: double.infinity, + child: Image.network( + imageUrl, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + color: AppColor.textLight, + child: const Center( + child: CircularProgressIndicator(color: AppColor.primary), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder(); + }, + ), + ); + } + + Widget _buildCarouselIndicators() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(4, (index) { + return GestureDetector( + onTap: () => _carouselController.animateToPage(index), + child: Container( + width: _currentCarouselIndex == index ? 24 : 8, + height: 8, + margin: const EdgeInsets.symmetric(horizontal: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: _currentCarouselIndex == index + ? AppColor.primary + : AppColor.textLight, + ), + ), + ); + }), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/feature_card.dart b/lib/presentation/pages/main/pages/home/widgets/feature_card.dart new file mode 100644 index 0000000..83b715f --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/feature_card.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import '../../../../../../common/theme/theme.dart'; + +class HomeFeatureCard extends StatefulWidget { + final IconData icon; + final String title; + final Color iconColor; + final VoidCallback onTap; + + const HomeFeatureCard({ + super.key, + required this.icon, + required this.title, + required this.iconColor, + required this.onTap, + }); + + @override + State createState() => _HomeFeatureCardState(); +} + +class _HomeFeatureCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 100), + vsync: this, + ); + _scaleAnimation = Tween( + begin: 1.0, + end: 0.95, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + onTapDown: (_) => _controller.forward(), + onTapUp: (_) => _controller.reverse(), + onTapCancel: () => _controller.reverse(), + child: AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.08), + blurRadius: 12, + offset: const Offset(0, 4), + spreadRadius: 0, + ), + ], + ), + child: Icon(widget.icon, color: widget.iconColor, size: 28), + ), + const SizedBox(height: 10), + Text( + widget.title, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + letterSpacing: -0.2, + ), + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/feature_section.dart b/lib/presentation/pages/main/pages/home/widgets/feature_section.dart new file mode 100644 index 0000000..bda479a --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/feature_section.dart @@ -0,0 +1,52 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../../application/auth/auth_bloc.dart'; +import '../../../../../router/app_router.gr.dart'; +import 'feature_card.dart'; + +class HomeFeatureSection extends StatelessWidget { + const HomeFeatureSection({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + HomeFeatureCard( + icon: Icons.card_giftcard, + title: 'Reward', + iconColor: const Color(0xFF1976D2), + onTap: () => context.router.push(RewardRoute()), + ), + HomeFeatureCard( + icon: Icons.casino, + title: 'Undian', + iconColor: const Color(0xFF7B1FA2), + onTap: () => context.router.push(DrawRoute()), + ), + HomeFeatureCard( + icon: Icons.store, + title: 'Merchant', + iconColor: const Color(0xFF388E3C), + onTap: () => context.router.push(MerchantRoute()), + ), + HomeFeatureCard( + icon: Icons.blur_circular, + title: 'Wheels', + iconColor: const Color(0xFF388E3C), + onTap: () => state.isAuthenticated + ? context.router.push(FerrisWheelRoute()) + : context.router.push(OnboardingRoute()), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/lottery_card.dart b/lib/presentation/pages/main/pages/home/widgets/lottery_card.dart new file mode 100644 index 0000000..e516087 --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/lottery_card.dart @@ -0,0 +1,396 @@ +import 'package:flutter/material.dart'; +import '../../../../../../common/theme/theme.dart'; + +class HomeLotteryBanner extends StatefulWidget { + const HomeLotteryBanner({ + super.key, + this.onTap, + this.title = "🎰 UNDIAN BERHADIAH", + this.subtitle = "Kumpulkan voucher untuk menang hadiah menarik!", + this.showAnimation = true, + this.actionText = "IKUTI SEKARANG", + }); + + final VoidCallback? onTap; + final String title; + final String subtitle; + final bool showAnimation; + final String actionText; + + @override + State createState() => _HomeLotteryBannerState(); +} + +class _HomeLotteryBannerState extends State + with TickerProviderStateMixin { + late AnimationController _pulseController; + late AnimationController _shimmerController; + late AnimationController _floatingController; + late Animation _pulseAnimation; + late Animation _shimmerAnimation; + late Animation _floatingAnimation; + + @override + void initState() { + super.initState(); + + if (widget.showAnimation) { + // Pulse animation for the whole banner + _pulseController = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + + // Shimmer effect for the gradient + _shimmerController = AnimationController( + duration: const Duration(seconds: 3), + vsync: this, + ); + + // Floating animation for the icon + _floatingController = AnimationController( + duration: const Duration(seconds: 4), + vsync: this, + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.02).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _shimmerAnimation = Tween(begin: -2.0, end: 2.0).animate( + CurvedAnimation(parent: _shimmerController, curve: Curves.easeInOut), + ); + + _floatingAnimation = Tween(begin: -5.0, end: 5.0).animate( + CurvedAnimation(parent: _floatingController, curve: Curves.easeInOut), + ); + + _pulseController.repeat(reverse: true); + _shimmerController.repeat(reverse: true); + _floatingController.repeat(reverse: true); + } + } + + @override + void dispose() { + if (widget.showAnimation) { + _pulseController.dispose(); + _shimmerController.dispose(); + _floatingController.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget banner = Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.4), + blurRadius: 20, + offset: const Offset(0, 8), + spreadRadius: 0, + ), + BoxShadow( + color: Colors.orange.withOpacity(0.2), + blurRadius: 40, + offset: const Offset(0, 16), + spreadRadius: 0, + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Stack( + children: [ + // Main gradient background + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary, + Colors.orange.shade600, + Colors.red.shade500, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0.0, 0.6, 1.0], + ), + ), + child: Column( + children: [ + // Top section with icon and text + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Animated floating icon with multiple effects + widget.showAnimation + ? AnimatedBuilder( + animation: _floatingAnimation, + builder: (context, child) { + return Transform.translate( + offset: Offset(0, _floatingAnimation.value), + child: _buildIcon(), + ); + }, + ) + : _buildIcon(), + + const SizedBox(width: 20), + + // Enhanced text section - now expanded fully + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w900, + color: Colors.white, + letterSpacing: 0.5, + shadows: [ + Shadow( + offset: Offset(0, 2), + blurRadius: 4, + color: Colors.black26, + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + widget.subtitle, + style: TextStyle( + fontSize: 13, + color: Colors.white.withOpacity(0.95), + height: 1.2, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Bottom action button - full width + _buildActionButton(), + ], + ), + ), + + // Shimmer overlay effect + if (widget.showAnimation) + AnimatedBuilder( + animation: _shimmerAnimation, + builder: (context, child) { + return Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.white.withOpacity(0.1), + Colors.transparent, + ], + stops: const [0.0, 0.5, 1.0], + begin: Alignment(_shimmerAnimation.value, -1), + end: Alignment(_shimmerAnimation.value + 0.5, 1), + ), + ), + ), + ); + }, + ), + + // Decorative dots pattern + Positioned( + top: -20, + right: -20, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.05), + ), + ), + ), + Positioned( + bottom: -10, + left: -30, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.orange.withOpacity(0.1), + ), + ), + ), + ], + ), + ), + ); + + // Wrap with gesture detector and animations + if (widget.onTap != null) { + banner = GestureDetector(onTap: widget.onTap, child: banner); + } + + if (widget.showAnimation) { + return AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale(scale: _pulseAnimation.value, child: banner); + }, + ); + } + + return banner; + } + + Widget _buildIcon() { + return Container( + width: 64, + height: 64, + decoration: BoxDecoration( + gradient: RadialGradient( + colors: [ + Colors.yellow.shade300, + Colors.orange.shade400, + Colors.red.shade500, + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.orange.withOpacity(0.6), + blurRadius: 12, + spreadRadius: 2, + ), + BoxShadow( + color: Colors.yellow.withOpacity(0.3), + blurRadius: 20, + spreadRadius: 4, + ), + ], + ), + child: Stack( + children: [ + const Center( + child: Icon( + Icons.casino, + color: Colors.white, + size: 32, + shadows: [ + Shadow( + offset: Offset(0, 2), + blurRadius: 4, + color: Colors.black26, + ), + ], + ), + ), + // Sparkle effects + Positioned( + top: 8, + right: 8, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.white, + blurRadius: 4, + spreadRadius: 1, + ), + ], + ), + ), + ), + Positioned( + bottom: 10, + left: 10, + child: Container( + width: 4, + height: 4, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white70, + ), + ), + ), + ], + ), + ); + } + + Widget _buildActionButton() { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.white, Colors.yellow.shade100], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + borderRadius: BorderRadius.circular(25), + border: Border.all(color: Colors.white.withOpacity(0.3), width: 1), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + BoxShadow( + color: Colors.white.withOpacity(0.5), + blurRadius: 4, + offset: const Offset(0, -1), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.actionText, + style: TextStyle( + color: AppColor.primary, + fontSize: 14, + fontWeight: FontWeight.w800, + letterSpacing: 0.5, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.primary.withOpacity(0.1), + ), + child: Icon( + Icons.arrow_forward_rounded, + color: AppColor.primary, + size: 16, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/point_card.dart b/lib/presentation/pages/main/pages/home/widgets/point_card.dart new file mode 100644 index 0000000..779276a --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/point_card.dart @@ -0,0 +1,258 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../../application/auth/auth_bloc.dart'; +import '../../../../../../common/theme/theme.dart'; +import '../../../../../router/app_router.gr.dart'; + +class HomePointCard extends StatelessWidget { + final String point; + const HomePointCard({super.key, required this.point}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return GestureDetector( + onTap: () => state.isAuthenticated + ? context.router.push(PoinRoute()) + : context.router.push(OnboardingRoute()), + child: Container( + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.textLight.withOpacity(0.15), + spreadRadius: 0, + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Stack( + children: [ + _buildCoinPattern(), + Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + !state.isAuthenticated + ? Expanded( + child: Text( + 'Hi, Selamat Datang Di Enaklo', + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ) + : Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular( + 25, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.stars, + color: AppColor.white, + size: 18, + ), + SizedBox(width: 8), + Text( + '$point Poin', + style: AppStyle.md.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + 'Kamu punya $point poin', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontSize: 11, + ), + ), + ], + ), + ), + + SizedBox( + width: 120, + height: 40, + child: Stack( + children: [ + _buildCoin( + right: 0, + top: 0, + size: 24, + color: Colors.amber, + ), + _buildCoin( + right: 20, + top: 8, + size: 20, + color: Colors.orange, + ), + _buildCoin( + right: 40, + top: 4, + size: 18, + color: Colors.amber, + ), + _buildCoin( + right: 60, + top: 12, + size: 16, + color: Colors.orange, + ), + _buildCoin( + right: 80, + top: 8, + size: 14, + color: Colors.amber, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Text( + state.isAuthenticated + ? 'Tukarkan poinmu dengan hadiah menarik' + : 'Silahkan login untuk tukarkan poinmu', + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Icon( + Icons.arrow_forward_ios, + color: AppColor.textSecondary, + size: 16, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildCoinPattern() { + return Positioned.fill( + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Stack( + children: [ + Positioned( + right: -20, + top: -10, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.1), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + right: 40, + top: 30, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: Colors.orange.withOpacity(0.15), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + left: -15, + bottom: -20, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.08), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + left: 60, + bottom: 10, + child: Container( + width: 15, + height: 15, + decoration: BoxDecoration( + color: Colors.orange.withOpacity(0.12), + shape: BoxShape.circle, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildCoin({ + required double right, + required double top, + required double size, + required Color color, + }) { + return Positioned( + right: right, + top: top, + child: Container( + width: size, + height: size, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + child: Center( + child: Text( + '\$', + style: TextStyle( + color: Colors.white, + fontSize: size * 0.5, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/popular_merchant_card.dart b/lib/presentation/pages/main/pages/home/widgets/popular_merchant_card.dart new file mode 100644 index 0000000..b038e07 --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/popular_merchant_card.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../../sample/sample_data.dart'; +import '../../../../../components/image/image.dart'; + +class HomePopularMerchantCard extends StatelessWidget { + final MerchantModel merchant; + final VoidCallback? onTap; + + const HomePopularMerchantCard({ + super.key, + this.onTap, + required this.merchant, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.06), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Image Container + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.border, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: merchant.imageUrl.startsWith('http') + ? Image.network( + merchant.imageUrl, + width: 60, + height: 60, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder(width: 60, height: 60); + }, + ) + : Image.asset( + merchant.imageUrl, + width: 60, + height: 60, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder(width: 60, height: 60); + }, + ), + ), + ), + + const SizedBox(width: 12), + + // Title and Category + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + merchant.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + height: 1.2, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + + const SizedBox(height: 4), + + Text( + merchant.category, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + + const SizedBox(width: 8), + + // Rating and Status + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Status Badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: merchant.isOpen ? AppColor.success : AppColor.error, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + merchant.isOpen ? 'OPEN' : 'CLOSED', + style: AppStyle.xs.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ), + + const SizedBox(height: 8), + + // Rating + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 3, + ), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.star, size: 12, color: AppColor.warning), + const SizedBox(width: 2), + Text( + merchant.rating.toString(), + style: AppStyle.xs.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.primary, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/home/widgets/popular_merchant_section.dart b/lib/presentation/pages/main/pages/home/widgets/popular_merchant_section.dart new file mode 100644 index 0000000..4b154e4 --- /dev/null +++ b/lib/presentation/pages/main/pages/home/widgets/popular_merchant_section.dart @@ -0,0 +1,56 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../../sample/sample_data.dart'; +import '../../../../../router/app_router.gr.dart'; +import 'popular_merchant_card.dart'; + +class HomePopularMerchantSection extends StatelessWidget { + const HomePopularMerchantSection({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: Column( + children: [ + Row( + children: [ + Text( + 'Popular Merchants', + style: AppStyle.xl.copyWith(fontWeight: FontWeight.bold), + ), + Spacer(), + InkWell( + onTap: () => context.router.push(MerchantRoute()), + child: Row( + children: [ + Text( + 'Lihat Semua', + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w500, + color: AppColor.primary, + ), + ), + SizedBox(width: 4), + Icon( + Icons.arrow_forward_ios, + size: 12, + color: AppColor.primary, + ), + ], + ), + ), + ], + ), + SizedBox(height: 16), + ...List.generate( + merchants.length, + (index) => HomePopularMerchantCard(merchant: merchants[index]), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/order/order_page.dart b/lib/presentation/pages/main/pages/order/order_page.dart new file mode 100644 index 0000000..33b0a8a --- /dev/null +++ b/lib/presentation/pages/main/pages/order/order_page.dart @@ -0,0 +1,355 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import 'widgets/order_card.dart'; + +// Model untuk Order +class Order { + final String id; + final String customerName; + final DateTime orderDate; + final List items; + final double totalAmount; + final OrderStatus status; + final String? notes; + final String? phoneNumber; + final String? address; + + Order({ + required this.id, + required this.customerName, + required this.orderDate, + required this.items, + required this.totalAmount, + required this.status, + this.notes, + this.phoneNumber, + this.address, + }); +} + +class OrderItem { + final String name; + final int quantity; + final double price; + final String? imageUrl; + final String? notes; + + OrderItem({ + required this.name, + required this.quantity, + required this.price, + this.imageUrl, + this.notes, + }); +} + +enum OrderStatus { pending, processing, completed, cancelled } + +@RoutePage() +class OrderPage extends StatefulWidget { + const OrderPage({super.key}); + + @override + State createState() => _OrderPageState(); +} + +class _OrderPageState extends State with TickerProviderStateMixin { + late TabController _tabController; + bool _isLoading = true; + List _orders = []; + + // Filter states + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 5, vsync: this); + _loadOrders(); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + void _loadOrders() { + // Simulate loading + Future.delayed(const Duration(seconds: 2), () { + setState(() { + _isLoading = false; + // Uncomment untuk testing dengan data + _orders = _generateSampleOrders(); + }); + }); + } + + List _generateSampleOrders() { + return [ + Order( + id: "ORD-001", + customerName: "John Doe", + orderDate: DateTime.now().subtract(const Duration(hours: 2)), + address: "Jl. Malioboro No. 123, Yogyakarta", + items: [ + OrderItem( + name: "Nasi Gudeg", + quantity: 2, + price: 25000, + notes: "Pedas sedang", + ), + OrderItem(name: "Es Teh Manis", quantity: 2, price: 8000), + OrderItem(name: "Kerupuk", quantity: 1, price: 5000), + ], + totalAmount: 71000, + status: OrderStatus.pending, + notes: "Tolong diantar sebelum jam 2 siang", + ), + Order( + id: "ORD-002", + customerName: "Jane Smith", + orderDate: DateTime.now().subtract(const Duration(hours: 1)), + address: "Jl. Sultan Agung No. 45, Yogyakarta", + items: [ + OrderItem( + name: "Ayam Bakar", + quantity: 1, + price: 35000, + notes: "Tidak pedas", + ), + OrderItem(name: "Nasi Putih", quantity: 1, price: 5000), + OrderItem(name: "Lalapan", quantity: 1, price: 8000), + ], + totalAmount: 48000, + status: OrderStatus.processing, + ), + Order( + id: "ORD-003", + customerName: "Bob Wilson", + orderDate: DateTime.now().subtract(const Duration(minutes: 30)), + phoneNumber: "+62 811-2345-6789", + items: [ + OrderItem(name: "Gado-gado", quantity: 2, price: 20000), + OrderItem(name: "Lontong", quantity: 2, price: 3000), + OrderItem(name: "Es Jeruk", quantity: 2, price: 10000), + ], + totalAmount: 66000, + status: OrderStatus.completed, + ), + ]; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: _buildAppBar(), + body: Column( + children: [ + _buildTabBar(), + Expanded( + child: _isLoading ? _buildLoadingState() : _buildOrderContent(), + ), + ], + ), + ); + } + + PreferredSizeWidget _buildAppBar() { + return AppBar( + elevation: 0, + backgroundColor: AppColor.white, + automaticallyImplyLeading: false, + title: Text('Pesanan'), + actions: [ + IconButton( + onPressed: _showFilterDialog, + icon: const Icon(Icons.filter_list, color: AppColor.textSecondary), + ), + IconButton( + onPressed: _refreshOrders, + icon: const Icon(Icons.refresh, color: AppColor.textSecondary), + ), + ], + ); + } + + Widget _buildTabBar() { + return Container( + color: AppColor.white, + child: TabBar( + controller: _tabController, + isScrollable: true, + labelColor: AppColor.primary, + unselectedLabelColor: AppColor.textSecondary, + indicatorColor: AppColor.primary, + indicatorWeight: 3, + labelStyle: AppStyle.md.copyWith(fontWeight: FontWeight.w600), + tabAlignment: TabAlignment.start, + unselectedLabelStyle: AppStyle.md, + tabs: const [ + Tab(text: 'Semua'), + Tab(text: 'Menunggu'), + Tab(text: 'Diproses'), + Tab(text: 'Selesai'), + Tab(text: 'Dibatalkan'), + ], + ), + ); + } + + Widget _buildOrderContent() { + if (_orders.isEmpty) { + return _buildEmptyState(); + } + + return TabBarView( + controller: _tabController, + children: [ + _buildOrderList(_orders), + _buildOrderList( + _orders.where((o) => o.status == OrderStatus.pending).toList(), + ), + _buildOrderList( + _orders.where((o) => o.status == OrderStatus.processing).toList(), + ), + _buildOrderList( + _orders.where((o) => o.status == OrderStatus.completed).toList(), + ), + _buildOrderList( + _orders.where((o) => o.status == OrderStatus.cancelled).toList(), + ), + ], + ); + } + + Widget _buildOrderList(List orders) { + if (orders.isEmpty) { + return _buildEmptyState(); + } + + return RefreshIndicator( + onRefresh: _refreshOrders, + color: AppColor.primary, + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: orders.length, + itemBuilder: (context, index) { + return OrderCard(order: orders[index]); + }, + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(60), + ), + child: Icon( + Icons.receipt_long, + size: 60, + color: AppColor.primary.withOpacity(0.5), + ), + ), + const SizedBox(height: 24), + Text( + 'Belum Ada Pesanan', + style: AppStyle.h6.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + 'Pesanan akan muncul di sini setelah\npelanggan mulai memesan.', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + ElevatedButton.icon( + onPressed: _refreshOrders, + icon: const Icon(Icons.refresh, size: 20), + label: const Text('Muat Ulang'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(AppColor.primary), + ), + const SizedBox(height: 16), + Text( + 'Memuat pesanan...', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ); + } + + void _showFilterDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + 'Filter Pesanan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Add filter options here + Text('Opsi filter akan segera hadir...', style: AppStyle.md), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Tutup', + style: AppStyle.md.copyWith(color: AppColor.primary), + ), + ), + ], + ); + }, + ); + } + + Future _refreshOrders() async { + setState(() { + _isLoading = true; + }); + await Future.delayed(const Duration(seconds: 1)); + setState(() { + _isLoading = false; + // Uncomment untuk testing dengan data + _orders = _generateSampleOrders(); + }); + } +} diff --git a/lib/presentation/pages/main/pages/order/widgets/order_card.dart b/lib/presentation/pages/main/pages/order/widgets/order_card.dart new file mode 100644 index 0000000..0b14936 --- /dev/null +++ b/lib/presentation/pages/main/pages/order/widgets/order_card.dart @@ -0,0 +1,125 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../router/app_router.gr.dart'; +import '../order_page.dart'; + +class OrderCard extends StatelessWidget { + final Order order; + const OrderCard({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => context.router.push(OrderDetailRoute(order: order)), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColor.border.withOpacity(0.3), + width: 1, + ), + ), + ), + child: Row( + children: [ + // Left side - Order info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Order ID + Text( + order.id, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 4), + // Order Date + Text( + DateFormat('dd MMM yyyy • HH:mm').format(order.orderDate), + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + + // Right side - Status and Total + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Status + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: _getStatusColor().withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: _getStatusColor().withOpacity(0.2), + width: 1, + ), + ), + child: Text( + _getStatusText(), + style: AppStyle.xs.copyWith( + color: _getStatusColor(), + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 6), + // Total Amount + Text( + 'Rp ${_formatCurrency(order.totalAmount)}', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Color _getStatusColor() { + switch (order.status) { + case OrderStatus.pending: + return AppColor.warning; + case OrderStatus.processing: + return AppColor.info; + case OrderStatus.completed: + return AppColor.success; + case OrderStatus.cancelled: + return AppColor.error; + } + } + + String _getStatusText() { + switch (order.status) { + case OrderStatus.pending: + return 'Menunggu'; + case OrderStatus.processing: + return 'Diproses'; + case OrderStatus.completed: + return 'Selesai'; + case OrderStatus.cancelled: + return 'Dibatalkan'; + } + } + + String _formatCurrency(double amount) { + final formatter = NumberFormat('#,###'); + return formatter.format(amount); + } +} diff --git a/lib/presentation/pages/main/pages/profile/profile_page.dart b/lib/presentation/pages/main/pages/profile/profile_page.dart new file mode 100644 index 0000000..94cb39b --- /dev/null +++ b/lib/presentation/pages/main/pages/profile/profile_page.dart @@ -0,0 +1,469 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/auth/auth_bloc.dart'; +import '../../../../../application/auth/logout_form/logout_form_bloc.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../../injection.dart'; +import '../../../../components/toast/flushbar.dart'; +import '../../../../router/app_router.gr.dart'; + +@RoutePage() +class ProfilePage extends StatelessWidget implements AutoRouteWrapper { + const ProfilePage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.failureOrAuthOption.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showAuthFailureToast(context, f), + (_) => context.router.replaceAll([OnboardingRoute()]), + ), + ); + }, + + child: Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: Text('Profil'), automaticallyImplyLeading: false), + body: BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + child: Column( + children: [ + // Profile Header + Container( + width: double.infinity, + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Column( + children: [ + // Profile Avatar & Info + GestureDetector( + onTap: () => state.isAuthenticated + ? context.router.push(AccountMyRoute()) + : context.router.push(OnboardingRoute()), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Stack( + children: [ + // Background Pattern + Positioned( + top: -20, + right: -20, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.1), + ), + ), + ), + Positioned( + top: 30, + right: 20, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.08), + ), + ), + ), + Positioned( + bottom: -10, + left: -10, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.06), + ), + ), + ), + // Decorative Lines + Positioned( + top: 10, + left: -5, + child: Transform.rotate( + angle: 0.5, + child: Container( + width: 30, + height: 2, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ), + Positioned( + bottom: 15, + right: 10, + child: Transform.rotate( + angle: -0.5, + child: Container( + width: 25, + height: 2, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ), + // Main Content + Padding( + padding: const EdgeInsets.all(16.0), + child: !state.isAuthenticated + ? Row( + children: [ + Expanded( + child: Text( + 'Silahkan Masuk', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.white, + letterSpacing: 0.5, + ), + ), + ), + ], + ) + : Row( + children: [ + // Avatar + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: AppColor.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.black + .withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon( + Icons.person, + size: 30, + color: AppColor.primary, + ), + ), + const SizedBox(width: 16), + // User Info + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + state.user.name, + style: AppStyle.lg.copyWith( + fontWeight: + FontWeight.bold, + color: AppColor.white, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + state.user.phoneNumber, + style: AppStyle.sm.copyWith( + color: AppColor.white + .withOpacity(0.9), + ), + ), + ], + ), + ), + // Arrow Icon + Icon( + Icons.arrow_forward_ios, + color: AppColor.white, + size: 14, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Menu Items + if (state.isAuthenticated) ...[ + Container( + color: AppColor.white, + child: Column( + children: [ + _buildMenuItem( + icon: Icons.location_on_outlined, + title: 'Alamat Tersimpan', + onTap: () => context.router.push(AddressRoute()), + ), + _buildMenuItem( + icon: Icons.payment_outlined, + title: 'Pembayaran', + onTap: () => context.router.push(PaymentRoute()), + ), + _buildMenuItem( + icon: Icons.help_outline, + title: 'Pusat Bantuan', + onTap: () {}, + ), + _buildMenuItem( + icon: Icons.settings_outlined, + title: 'Pengaturan', + onTap: () {}, + showDivider: false, + ), + ], + ), + ), + const SizedBox(height: 8), + ], + + // Legal & Privacy Section + Container( + color: AppColor.white, + child: Column( + children: [ + _buildMenuItem( + icon: Icons.description_outlined, + title: 'Syarat dan Ketentuan', + onTap: () {}, + ), + _buildMenuItem( + icon: Icons.privacy_tip_outlined, + title: 'Kebijakan Privasi', + onTap: () {}, + showDivider: false, + ), + ], + ), + ), + + const SizedBox(height: 8), + + // Customer Service Section + Container( + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Butuh Bantuan?', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + 'Customer Service kami siap untuk membantu', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + const SizedBox(height: 16), + // WhatsApp Customer Service + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.chat, + color: AppColor.white, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Enaklo Customer Service (chat only)', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + Text( + '0812-1111-8456', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.success, + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: AppColor.textSecondary, + size: 14, + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Footer Section + if (state.isAuthenticated) + Container( + color: AppColor.white, + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Version 4.6.1', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + GestureDetector( + onTap: () => _showLogoutDialog( + context, + onLogout: () => context + .read() + .add(const LogoutFormEvent.submitted()), + ), + child: Text( + 'Logout', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 100), // Bottom spacing + ], + ), + ); + }, + ), + ), + ); + } + + Widget _buildMenuItem({ + required IconData icon, + required String title, + required VoidCallback onTap, + bool showDivider = true, + }) { + return Column( + children: [ + ListTile( + leading: Icon(icon, color: AppColor.textSecondary, size: 24), + title: Text( + title, + style: AppStyle.md.copyWith(color: AppColor.textPrimary), + ), + trailing: Icon( + Icons.arrow_forward_ios, + color: AppColor.textSecondary, + size: 16, + ), + onTap: onTap, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 4, + ), + ), + if (showDivider) + Divider(height: 1, color: AppColor.borderLight, indent: 60), + ], + ); + } + + void _showLogoutDialog( + BuildContext context, { + required VoidCallback onLogout, + }) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: AppColor.white, + title: Text( + 'Logout', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600), + ), + content: Text( + 'Apakah Anda yakin ingin keluar dari aplikasi?', + style: AppStyle.md, + ), + actions: [ + TextButton( + child: Text( + 'Batal', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + onPressed: onLogout, + child: Text( + 'Logout', + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + }, + ); + } + + @override + Widget wrappedRoute(BuildContext context) => + BlocProvider(create: (_) => getIt(), child: this); +} diff --git a/lib/presentation/pages/main/pages/voucher/voucher_page.dart b/lib/presentation/pages/main/pages/voucher/voucher_page.dart new file mode 100644 index 0000000..5581794 --- /dev/null +++ b/lib/presentation/pages/main/pages/voucher/voucher_page.dart @@ -0,0 +1,93 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import '../../../../components/field/field.dart'; +import 'widgets/voucher_card.dart'; + +@RoutePage() +class VoucherPage extends StatelessWidget { + const VoucherPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Voucher'), + automaticallyImplyLeading: false, + bottom: PreferredSize( + preferredSize: Size.fromHeight(70), + child: SearchTextField( + hintText: 'Punya kode promo? Masukkan disini', + prefixIcon: Icons.local_offer, + ), + ), + ), + body: Column( + children: [ + // Voucher Belanja Section + Expanded( + child: Column( + children: [ + // Section Header + Padding( + padding: EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Voucher Belanja', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + '10 voucher', + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), + + // Voucher List + Expanded( + child: ListView( + padding: EdgeInsets.symmetric(horizontal: 16), + children: [ + VoucherCard( + title: 'New User Voucher - Diskon 50% hingga Rp35K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + VoucherCard( + title: 'New User Voucher - Diskon 35% hingga Rp50K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + VoucherCard( + title: 'New User Voucher - Diskon 25% hingga Rp50K', + subtitle: 'Tanpa Min. Belanja', + expireDate: '25 Sep 2025', + minTransaction: '-', + ), + SizedBox(height: 16), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart b/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart new file mode 100644 index 0000000..1d80ec9 --- /dev/null +++ b/lib/presentation/pages/main/pages/voucher/widgets/voucher_card.dart @@ -0,0 +1,217 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../../common/ui/clipper/voucher_clipper.dart'; +import '../../../../../../common/ui/painter/dashed_line_painter.dart'; +import '../../../../../router/app_router.gr.dart'; + +class VoucherCard extends StatelessWidget { + final String title; + final String subtitle; + final String expireDate; + final String minTransaction; + const VoucherCard({ + super.key, + required this.title, + required this.subtitle, + required this.expireDate, + required this.minTransaction, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => context.router.push(VoucherDetailRoute()), + child: Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 12, + offset: Offset(0, 4), + spreadRadius: 0, + ), + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 6, + offset: Offset(0, 2), + spreadRadius: 0, + ), + ], + ), + child: ClipPath( + clipper: VoucherClipper(), + child: Container( + decoration: BoxDecoration(color: Colors.white), + child: Column( + children: [ + // Main Content + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 4), + Text( + subtitle, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + SizedBox(width: 12), + // Voucher Icon + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.grey[300]!, + width: 1, + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.local_offer_outlined, + color: AppColor.primary, + size: 24, + ), + Positioned( + top: 6, + right: 6, + child: Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: AppColor.error, + shape: BoxShape.circle, + ), + child: Icon( + Icons.percent, + color: Colors.white, + size: 10, + ), + ), + ), + ], + ), + ), + ], + ), + ), + + // Dashed line divider + Container( + height: 1, + margin: EdgeInsets.symmetric(horizontal: 20), + child: CustomPaint( + size: Size(double.infinity, 1), + painter: DashedLinePainter(), + ), + ), + + // Bottom Section + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Berlaku Hingga', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontSize: 10, + ), + ), + SizedBox(height: 2), + Text( + expireDate, + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Min Transaksi', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontSize: 10, + ), + ), + SizedBox(height: 2), + Text( + minTransaction, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + SizedBox(width: 16), + // Pakai Button + Container( + padding: EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + 'Pakai', + style: TextStyle( + color: AppColor.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart b/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart new file mode 100644 index 0000000..53719d8 --- /dev/null +++ b/lib/presentation/pages/main/pages/voucher/widgets/voucher_empty_card.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; + +class VoucherEmptyCard extends StatelessWidget { + const VoucherEmptyCard({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Empty State Icon + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.local_offer_outlined, + size: 60, + color: AppColor.primary.withOpacity(0.5), + ), + ), + + SizedBox(height: 24), + + // Empty State Title + Text( + 'Belum Ada Voucher', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + + SizedBox(height: 8), + + // Empty State Description + Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: Text( + 'Kamu belum memiliki voucher saat ini.\nCoba masukkan kode promo di atas atau\ncari promo menarik lainnya.', + textAlign: TextAlign.center, + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + height: 1.5, + ), + ), + ), + + SizedBox(height: 32), + + // Explore Button + ElevatedButton( + onPressed: () { + // Navigate to explore or promo page + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), + ), + child: Text( + 'Jelajahi Promo', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/widgets/bottom_navbar.dart b/lib/presentation/pages/main/widgets/bottom_navbar.dart new file mode 100644 index 0000000..adb2625 --- /dev/null +++ b/lib/presentation/pages/main/widgets/bottom_navbar.dart @@ -0,0 +1,39 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +class MainBottomNavbar extends StatelessWidget { + final TabsRouter tabsRouter; + const MainBottomNavbar({super.key, required this.tabsRouter}); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + currentIndex: tabsRouter.activeIndex, + onTap: (index) { + tabsRouter.setActiveIndex(index); + }, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Home', + tooltip: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.discount), + label: 'Voucher', + tooltip: 'Voucher', + ), + BottomNavigationBarItem( + icon: Icon(Icons.list), + label: 'Pesanan', + tooltip: 'Pesanan', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profil', + tooltip: 'Profil', + ), + ], + ); + } +} diff --git a/lib/presentation/pages/merchant/merchant_page.dart b/lib/presentation/pages/merchant/merchant_page.dart new file mode 100644 index 0000000..bac7b57 --- /dev/null +++ b/lib/presentation/pages/merchant/merchant_page.dart @@ -0,0 +1,199 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import '../../../sample/sample_data.dart'; +import '../../components/field/field.dart'; +import '../../router/app_router.gr.dart'; +import 'widgets/empty_merchant_card.dart'; +import 'widgets/merchant_card.dart'; + +@RoutePage() +class MerchantPage extends StatefulWidget { + const MerchantPage({super.key}); + + @override + State createState() => _MerchantPageState(); +} + +class _MerchantPageState extends State { + final TextEditingController _searchController = TextEditingController(); + final List _allMerchants = _generateMockMerchants(); + List _filteredMerchants = []; + String? _selectedCategory; + late List _categories; + + @override + void initState() { + super.initState(); + _filteredMerchants = _allMerchants; + _categories = _getAllCategories(); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + List _getAllCategories() { + final categories = _allMerchants + .map((merchant) => merchant.category) + .toSet() + .toList(); + categories.sort(); + return categories; + } + + void _filterMerchants(String query) { + setState(() { + var merchants = _allMerchants; + + // Filter by category first + if (_selectedCategory != null) { + merchants = merchants + .where((merchant) => merchant.category == _selectedCategory) + .toList(); + } + + // Then filter by search query + if (query.isNotEmpty) { + merchants = merchants + .where( + (merchant) => + merchant.name.toLowerCase().contains(query.toLowerCase()) || + merchant.category.toLowerCase().contains(query.toLowerCase()), + ) + .toList(); + } + + _filteredMerchants = merchants; + }); + } + + void _onCategorySelected(String? category) { + setState(() { + _selectedCategory = category; + }); + _filterMerchants(_searchController.text); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar( + title: const Text('Merchants'), + bottom: PreferredSize( + preferredSize: const Size.fromHeight( + 130, + ), // Increased height for filter chips + child: Column( + children: [ + // Search Field + SearchTextField( + hintText: 'Search merchants...', + prefixIcon: Icons.search, + controller: _searchController, + // onChanged: _filterMerchants, + onClear: () { + _searchController.clear(); + _filterMerchants(''); + }, + ), + const SizedBox(height: 12), + // Category Filter Chips + // Category Filter Chips - Updated with AppColor and opacity 1 + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + // "All" filter chip + Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + label: const Text('Semua'), + selected: _selectedCategory == null, + onSelected: (selected) { + _onCategorySelected(null); + }, + selectedColor: AppColor.primary, + backgroundColor: AppColor.white, + checkmarkColor: AppColor.white, + labelStyle: TextStyle( + color: _selectedCategory == null + ? AppColor.white + : AppColor.primary, + ), + side: BorderSide(color: AppColor.primary, width: 1), + ), + ), + // Category filter chips + ..._categories.map( + (category) => Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + label: Text(category), + selected: _selectedCategory == category, + onSelected: (selected) { + _onCategorySelected(selected ? category : null); + }, + selectedColor: AppColor.primary, + backgroundColor: AppColor.white, + checkmarkColor: AppColor.white, + labelStyle: TextStyle( + color: _selectedCategory == category + ? AppColor.white + : AppColor.primary, + ), + side: BorderSide(color: AppColor.primary, width: 1), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ), + body: _filteredMerchants.isEmpty + ? _buildEmptyState() + : Padding( + padding: const EdgeInsets.all(16), + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 0.85, + ), + itemCount: _filteredMerchants.length, + itemBuilder: (context, index) { + return _buildMerchantCard(_filteredMerchants[index]); + }, + ), + ), + ); + } + + Widget _buildMerchantCard(MerchantModel merchant) { + return MerchantCard( + merchant: merchant, + onTap: () { + context.router.push(MerchantDetailRoute(merchant: merchant)); + }, + ); + } + + Widget _buildEmptyState() { + return const EmptyMerchantCard(); + } + + static List _generateMockMerchants() { + return merchants; + } +} diff --git a/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart b/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart new file mode 100644 index 0000000..313a148 --- /dev/null +++ b/lib/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart @@ -0,0 +1,702 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../../sample/sample_data.dart'; + +// Models +class ProductCategory { + final String id; + final String name; + final String icon; + final int productCount; + + ProductCategory({ + required this.id, + required this.name, + required this.icon, + required this.productCount, + }); +} + +class Product { + final String id; + final String name; + final String description; + final int price; + final String categoryId; + final String imageUrl; + final bool isAvailable; + final double rating; + final int soldCount; + + Product({ + required this.id, + required this.name, + required this.description, + required this.price, + required this.categoryId, + required this.imageUrl, + required this.isAvailable, + required this.rating, + required this.soldCount, + }); +} + +@RoutePage() +class MerchantDetailPage extends StatefulWidget { + final MerchantModel merchant; + + const MerchantDetailPage({super.key, required this.merchant}); + + @override + State createState() => _MerchantDetailPageState(); +} + +class _MerchantDetailPageState extends State { + final ScrollController _scrollController = ScrollController(); + final List _productSectionKeys = []; + + String _selectedCategoryId = ""; + + // Sample data + final List categories = [ + ProductCategory(id: "1", name: "Makanan", icon: "🍽️", productCount: 8), + ProductCategory(id: "2", name: "Minuman", icon: "🥤", productCount: 6), + ProductCategory(id: "3", name: "Snack", icon: "🍿", productCount: 5), + ProductCategory(id: "4", name: "Es Krim", icon: "🍦", productCount: 4), + ProductCategory(id: "5", name: "Paket", icon: "📦", productCount: 3), + ]; + + final List products = [ + // Makanan + Product( + id: "1", + name: "Nasi Gudeg", + description: "Gudeg khas Yogyakarta dengan ayam dan telur", + price: 25000, + categoryId: "1", + imageUrl: "https://via.placeholder.com/150", + isAvailable: true, + rating: 4.5, + soldCount: 50, + ), + Product( + id: "2", + name: "Soto Ayam", + description: "Soto ayam kuning dengan nasi dan kerupuk", + price: 18000, + categoryId: "1", + imageUrl: "https://via.placeholder.com/150", + isAvailable: true, + rating: 4.3, + soldCount: 75, + ), + Product( + id: "3", + name: "Gado-gado", + description: "Gado-gado segar dengan bumbu kacang", + price: 15000, + categoryId: "1", + imageUrl: "https://via.placeholder.com/150", + isAvailable: false, + rating: 4.2, + soldCount: 30, + ), + + // Minuman + Product( + id: "4", + name: "Es Teh Manis", + description: "Es teh manis segar", + price: 5000, + categoryId: "2", + imageUrl: "https://via.placeholder.com/150", + isAvailable: true, + rating: 4.0, + soldCount: 120, + ), + Product( + id: "5", + name: "Jus Jeruk", + description: "Jus jeruk segar tanpa gula tambahan", + price: 12000, + categoryId: "2", + imageUrl: "https://via.placeholder.com/150", + isAvailable: true, + rating: 4.4, + soldCount: 45, + ), + + // Snack + Product( + id: "6", + name: "Keripik Pisang", + description: "Keripik pisang renyah dan manis", + price: 8000, + categoryId: "3", + imageUrl: "https://via.placeholder.com/150", + isAvailable: true, + rating: 4.1, + soldCount: 25, + ), + ]; + + @override + void initState() { + super.initState(); + _selectedCategoryId = categories.isNotEmpty ? categories.first.id : ""; + + // Initialize keys for each category + for (int i = 0; i < categories.length; i++) { + _productSectionKeys.add(GlobalKey()); + } + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToCategory(String categoryId) { + setState(() { + _selectedCategoryId = categoryId; + }); + + final categoryIndex = categories.indexWhere((cat) => cat.id == categoryId); + if (categoryIndex >= 0 && categoryIndex < _productSectionKeys.length) { + final key = _productSectionKeys[categoryIndex]; + final context = key.currentContext; + + if (context != null) { + Future.delayed(Duration(milliseconds: 100), () { + if (mounted) { + Scrollable.ensureVisible( + context, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } + }); + } + } + } + + List _getProductsByCategory(String categoryId) { + return products + .where((product) => product.categoryId == categoryId) + .toList(); + } + + String _formatCurrency(int amount) { + return amount.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: CustomScrollView( + controller: _scrollController, + slivers: [ + // App Bar with merchant info + SliverAppBar( + expandedHeight: 280, + pinned: true, + backgroundColor: AppColor.primary, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: AppColor.textWhite), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + IconButton( + icon: Icon(Icons.share, color: AppColor.textWhite), + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.favorite_border, color: AppColor.textWhite), + onPressed: () {}, + ), + ], + flexibleSpace: FlexibleSpaceBar(background: _buildMerchantHeader()), + ), + + // Categories (will be pinned) + SliverPersistentHeader( + pinned: true, + delegate: _SliverCategoryDelegate( + categories: categories, + selectedCategoryId: _selectedCategoryId, + onCategoryTap: _scrollToCategory, + ), + ), + + // Product sections by category + ...categories.map((category) { + final categoryProducts = _getProductsByCategory(category.id); + final categoryIndex = categories.indexOf(category); + + return SliverToBoxAdapter( + key: _productSectionKeys[categoryIndex], + child: _buildProductSection(category, categoryProducts), + ); + }).toList(), + ], + ), + ); + } + + Widget _buildMerchantHeader() { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Stack( + children: [ + // Background decoration + Positioned( + right: 20, + top: 60, + child: Opacity( + opacity: 0.1, + child: Icon(Icons.store, size: 100, color: AppColor.textWhite), + ), + ), + + // Content + Positioned( + left: 20, + right: 20, + bottom: 20, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Merchant avatar + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.2), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + ), + child: Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.asset(widget.merchant.imageUrl), + ), + ), + ), + + SizedBox(height: 16), + + // Merchant name + Text( + widget.merchant.name, + style: AppStyle.h4.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + + SizedBox(height: 4), + + // Category and status + Row( + children: [ + Text( + widget.merchant.category, + style: AppStyle.md.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + SizedBox(width: 12), + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: widget.merchant.isOpen + ? AppColor.success + : AppColor.error, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + widget.merchant.isOpen ? "BUKA" : "TUTUP", + style: AppStyle.xs.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + + SizedBox(height: 8), + + // Rating + Row( + children: [ + Icon(Icons.star, color: AppColor.warning, size: 16), + SizedBox(width: 4), + Text( + "${widget.merchant.rating}", + style: AppStyle.md.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 8), + Text( + "• ${products.length} produk", + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.8), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildProductSection( + ProductCategory category, + List categoryProducts, + ) { + return Container( + margin: EdgeInsets.only(bottom: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section header + Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Row( + children: [ + Text(category.icon, style: AppStyle.h5), + SizedBox(width: 8), + Text( + category.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(width: 8), + Text( + "(${categoryProducts.length})", + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + + // Products grid + if (categoryProducts.isEmpty) + _buildEmptyCategory() + else + GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 16), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.75, + ), + itemCount: categoryProducts.length, + itemBuilder: (context, index) { + return _buildProductCard(categoryProducts[index]); + }, + ), + ], + ), + ); + } + + Widget _buildProductCard(Product product) { + return Container( + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.06), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Product image + Expanded( + flex: 3, + child: Stack( + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.vertical( + top: Radius.circular(12), + ), + ), + child: Center( + child: Icon( + Icons.fastfood, + size: 40, + color: AppColor.textLight, + ), + ), + ), + + // Availability overlay + if (!product.isAvailable) + Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: AppColor.black.withOpacity(0.6), + borderRadius: BorderRadius.vertical( + top: Radius.circular(12), + ), + ), + child: Center( + child: Text( + "HABIS", + style: AppStyle.sm.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + + // Rating badge + Positioned( + top: 8, + right: 8, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.star, size: 12, color: AppColor.warning), + SizedBox(width: 2), + Text( + "${product.rating}", + style: AppStyle.xs.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ), + + // Product info + Expanded( + flex: 2, + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + + Spacer(), + + // Price and sold count + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Rp ${_formatCurrency(product.price)}", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + Text( + "${product.soldCount} terjual", + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildEmptyCategory() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight), + ), + child: Center( + child: Column( + children: [ + Icon( + Icons.inventory_2_outlined, + size: 48, + color: AppColor.textLight, + ), + SizedBox(height: 12), + Text( + "Belum ada produk", + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ); + } +} + +// Custom delegate for pinned category bar +class _SliverCategoryDelegate extends SliverPersistentHeaderDelegate { + final List categories; + final String selectedCategoryId; + final Function(String) onCategoryTap; + + _SliverCategoryDelegate({ + required this.categories, + required this.selectedCategoryId, + required this.onCategoryTap, + }); + + @override + double get minExtent => 60; + + @override + double get maxExtent => 60; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Container( + height: 60, + decoration: BoxDecoration( + color: AppColor.surface, + border: Border( + bottom: BorderSide(color: AppColor.borderLight, width: 1), + ), + ), + child: ListView.builder( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + itemCount: categories.length, + itemBuilder: (context, index) { + final category = categories[index]; + final isSelected = category.id == selectedCategoryId; + + return GestureDetector( + onTap: () => onCategoryTap(category.id), + child: Container( + margin: EdgeInsets.only(right: 12), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColor.primary : AppColor.backgroundLight, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: isSelected ? AppColor.primary : AppColor.border, + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(category.icon, style: AppStyle.md), + SizedBox(width: 8), + Text( + category.name, + style: AppStyle.md.copyWith( + color: isSelected + ? AppColor.textWhite + : AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 4), + Container( + padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: isSelected + ? AppColor.textWhite.withOpacity(0.2) + : AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "${category.productCount}", + style: AppStyle.xs.copyWith( + color: isSelected + ? AppColor.textWhite + : AppColor.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + return oldDelegate is _SliverCategoryDelegate && + (oldDelegate.selectedCategoryId != selectedCategoryId || + oldDelegate.categories != categories); + } +} diff --git a/lib/presentation/pages/merchant/widgets/empty_merchant_card.dart b/lib/presentation/pages/merchant/widgets/empty_merchant_card.dart new file mode 100644 index 0000000..379457f --- /dev/null +++ b/lib/presentation/pages/merchant/widgets/empty_merchant_card.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +class EmptyMerchantCard extends StatelessWidget { + final String? title; + final String? subtitle; + final IconData? icon; + + const EmptyMerchantCard({super.key, this.title, this.subtitle, this.icon}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColor.primaryWithOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + icon ?? Icons.search_off, + size: 64, + color: AppColor.primary, + ), + ), + const SizedBox(height: 24), + Text( + title ?? 'No merchants found', + style: AppStyle.xl.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + subtitle ?? + 'Try adjusting your search terms or check back later for new merchants', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + decoration: BoxDecoration( + color: AppColor.primaryWithOpacity(0.1), + borderRadius: BorderRadius.circular(24), + border: Border.all( + color: AppColor.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.refresh, size: 18, color: AppColor.primary), + const SizedBox(width: 8), + Text( + 'Refresh', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/merchant/widgets/merchant_card.dart b/lib/presentation/pages/merchant/widgets/merchant_card.dart new file mode 100644 index 0000000..e528436 --- /dev/null +++ b/lib/presentation/pages/merchant/widgets/merchant_card.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; +import '../../../../sample/sample_data.dart'; +import '../../../components/image/image.dart'; + +class MerchantCard extends StatelessWidget { + final MerchantModel merchant; + final VoidCallback? onTap; + + const MerchantCard({super.key, required this.merchant, this.onTap}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.borderLight, width: 0.5), + boxShadow: [ + BoxShadow( + color: AppColor.textSecondary.withOpacity(0.08), + blurRadius: 6, + offset: const Offset(0, 1), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Column( + children: [ + // Merchant Image/Icon - Made more compact + Container( + width: double.infinity, + height: 100, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + child: merchant.imageUrl.startsWith('assets') + ? Image.asset( + merchant.imageUrl, + fit: BoxFit.fill, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder(width: 60, height: 60); + }, + ) + : Image.network( + merchant.imageUrl, + width: double.infinity, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColor.primary, + value: + loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder( + width: double.infinity, + height: 100, + showBorderRadius: false, + ); + }, + ), + ), + ), + const SizedBox(height: 10), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Merchant Name - Flexible to prevent overflow + Flexible( + child: Text( + merchant.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + height: 1.2, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(height: 3), + + // Category - More compact + Text( + merchant.category, + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + height: 1.2, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + + // Rating and Status - Compact layout + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Rating + Flexible( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.star_rounded, + size: 14, + color: AppColor.warning, + ), + const SizedBox(width: 3), + Text( + merchant.rating.toStringAsFixed(1), + style: AppStyle.xs.copyWith( + fontWeight: FontWeight.w500, + color: AppColor.textPrimary, + ), + ), + ], + ), + ), + + // Status Badge - More compact + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: merchant.isOpen + ? AppColor.successWithOpacity(0.12) + : AppColor.errorWithOpacity(0.12), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + merchant.isOpen ? 'Open' : 'Closed', + style: TextStyle( + fontSize: 10, + color: merchant.isOpen + ? AppColor.success + : AppColor.error, + fontWeight: FontWeight.w600, + height: 1, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart b/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart new file mode 100644 index 0000000..bbb1165 --- /dev/null +++ b/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; + +class WheelSection { + final Color color; + final IconData icon; + final String prize; + final int value; + + WheelSection({ + required this.color, + required this.icon, + required this.prize, + required this.value, + }); +} + +class PrizeHistory { + final String prize; + final DateTime dateTime; + final int value; + final Color color; + final IconData icon; + final String? gamePrizeId; // Added for API integration + + PrizeHistory({ + required this.prize, + required this.dateTime, + required this.value, + required this.color, + required this.icon, + this.gamePrizeId, + }); + + // CopyWith method for updating properties + PrizeHistory copyWith({ + String? prize, + DateTime? dateTime, + int? value, + Color? color, + IconData? icon, + String? gamePrizeId, + }) { + return PrizeHistory( + prize: prize ?? this.prize, + dateTime: dateTime ?? this.dateTime, + value: value ?? this.value, + color: color ?? this.color, + icon: icon ?? this.icon, + gamePrizeId: gamePrizeId ?? this.gamePrizeId, + ); + } + + // Convert to Map for storage/API calls + Map toMap() { + return { + 'prize': prize, + 'dateTime': dateTime.toIso8601String(), + 'value': value, + 'gamePrizeId': gamePrizeId, + }; + } + + // Create from Map for loading from storage/API + factory PrizeHistory.fromMap( + Map map, { + required Color color, + required IconData icon, + }) { + return PrizeHistory( + prize: map['prize'] ?? '', + dateTime: DateTime.tryParse(map['dateTime'] ?? '') ?? DateTime.now(), + value: map['value'] ?? 0, + color: color, + icon: icon, + gamePrizeId: map['gamePrizeId'], + ); + } + + // Format date for display + String get formattedDate { + return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; + } + + // Get prize category based on value + String get prizeCategory { + if (value >= 1000000) return 'Jackpot'; + if (value >= 100000) return 'Big Prize'; + if (value >= 10000) return 'Medium Prize'; + if (value >= 1000) return 'Small Prize'; + return 'Token/Spin'; + } + + // Get prize category color + Color get categoryColor { + if (value >= 1000000) return const Color(0xFFFFD700); // Gold + if (value >= 100000) return const Color(0xFFC0C0C0); // Silver + if (value >= 10000) return const Color(0xFFCD7F32); // Bronze + return color; // Default color + } +} diff --git a/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart b/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart new file mode 100644 index 0000000..605a945 --- /dev/null +++ b/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart @@ -0,0 +1,1030 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'dart:math' as math; + +import '../../../../application/auth/auth_bloc.dart'; +import '../../../../application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../common/painter/wheel_painter.dart'; +import '../../../../injection.dart'; +import '../../../../domain/game/game.dart'; + +// Simple models for UI state +class PrizeHistory { + final String prize; + final DateTime dateTime; + final int value; + final Color color; + final IconData icon; + final String? gamePrizeId; + + PrizeHistory({ + required this.prize, + required this.dateTime, + required this.value, + required this.color, + required this.icon, + this.gamePrizeId, + }); +} + +@RoutePage() +class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper { + const FerrisWheelPage({super.key}); + + @override + State createState() => _FerrisWheelPageState(); + + @override + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (context) => + getIt() + ..add(const FerrisWheelLoaderEvent.fetched()), + child: this, + ); +} + +class _FerrisWheelPageState extends State + with TickerProviderStateMixin { + // Animation Controllers + late AnimationController _rotationController; + late AnimationController _glowController; + late AnimationController _pulseController; + late AnimationController _idleRotationController; + + // Animations + Animation? _spinAnimation; + Animation? _pulseAnimation; + Animation? _idleRotationAnimation; + + // Audio Players + final AudioPlayer _bgmPlayer = AudioPlayer(); + final AudioPlayer _sfxPlayer = AudioPlayer(); + + // Audio Settings + bool _isSoundEnabled = true; + bool _isMusicEnabled = true; + + // Game State + int tokens = 3; + bool isSpinning = false; + String resultText = 'Belum pernah spin'; + double currentRotation = 0.0; + int currentTabIndex = 0; + + // Game Data + List prizeHistory = []; + List gamePrizes = []; + + @override + void initState() { + super.initState(); + _initializeAudio(); + _initializeAnimations(); + } + + @override + void dispose() { + _rotationController.dispose(); + _glowController.dispose(); + _pulseController.dispose(); + _idleRotationController.dispose(); + _bgmPlayer.dispose(); + _sfxPlayer.dispose(); + super.dispose(); + } + + void _initializeAudio() async { + try { + await _bgmPlayer.setSource(AssetSource('audio/carnaval_main_theme.mp3')); + await _bgmPlayer.setReleaseMode(ReleaseMode.loop); + await _bgmPlayer.setVolume(0.5); + if (_isMusicEnabled) await _bgmPlayer.resume(); + } catch (e) { + print('Error initializing audio: $e'); + } + } + + void _playSound(String soundPath, {double volume = 0.7}) async { + if (!_isSoundEnabled) return; + try { + await _sfxPlayer.stop(); + await _sfxPlayer.setSource(AssetSource(soundPath)); + await _sfxPlayer.setVolume(volume); + await _sfxPlayer.resume(); + } catch (e) { + print('Error playing sound: $e'); + } + } + + void _playButtonTap() => _playSound('audio/button_tap.mp3', volume: 0.3); + void _playTokenSound() => _playSound('audio/token_sound.mp3', volume: 0.5); + void _playWheelSpin() => _playSound('audio/wheel_spin.mp3', volume: 0.8); + void _playWinSound() => _playSound('audio/win_medium.mp3', volume: 0.8); + + void _toggleSound() { + setState(() => _isSoundEnabled = !_isSoundEnabled); + if (_isSoundEnabled) _playButtonTap(); + } + + void _toggleMusic() { + setState(() => _isMusicEnabled = !_isMusicEnabled); + _isMusicEnabled ? _bgmPlayer.resume() : _bgmPlayer.pause(); + if (_isSoundEnabled) _playButtonTap(); + } + + void _initializeAnimations() { + _rotationController = AnimationController( + duration: const Duration(seconds: 4), + vsync: this, + ); + _glowController = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + ); + _pulseController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _idleRotationController = AnimationController( + duration: const Duration(seconds: 10), + vsync: this, + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.1).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + _idleRotationAnimation = Tween( + begin: 0.0, + end: 2 * math.pi, + ).animate(_idleRotationController); + + _glowController.repeat(reverse: true); + _pulseController.repeat(reverse: true); + _idleRotationController.repeat(); + } + + int _selectPrizeWithWeight() { + if (gamePrizes.isEmpty) return 0; + + // Jika GamePrize memiliki properti weight, gunakan ini: + // List weights = gamePrizes.map((prize) => prize.weight ?? 1).toList(); + + // Untuk sementara, gunakan bobot default yang berbeda + // Hadiah langka (index awal) memiliki bobot lebih kecil + List weights = []; + for (int i = 0; i < gamePrizes.length; i++) { + // Bobot menurun: hadiah pertama langka, hadiah terakhir mudah didapat + int weight = gamePrizes.length - i; + weights.add(weight); + } + + // Hitung total bobot + int totalWeight = weights.reduce((a, b) => a + b); + + // Generate random number + int randomNum = math.Random().nextInt(totalWeight); + + // Tentukan hadiah berdasarkan bobot + int currentWeight = 0; + for (int i = 0; i < gamePrizes.length; i++) { + currentWeight += weights[i]; + if (randomNum < currentWeight) { + return i; + } + } + + // Fallback (seharusnya tidak pernah terjadi) + return gamePrizes.length - 1; + } + + Color _getPrizeColor(GamePrize prize, int index) { + final colors = [ + AppColor.primary, + AppColor.info, + AppColor.warning, + AppColor.success, + AppColor.primaryDark, + AppColor.secondary, + AppColor.error, + ]; + return colors[index % colors.length]; + } + + void _spinWheel() { + if (isSpinning || tokens <= 0 || gamePrizes.isEmpty) return; + + _playWheelSpin(); + _playTokenSound(); + + setState(() { + isSpinning = true; + tokens--; + resultText = 'Sedang berputar...'; + }); + + _idleRotationController.stop(); + _idleRotationController.reset(); + + int selectedPrizeIndex = _selectPrizeWithWeight(); + double sectionAngle = (2 * math.pi) / gamePrizes.length; + double currentPos = currentRotation; + + // TAMBAH offset ke tengah section (bukan garis) + double targetForSelectedSection = + -(selectedPrizeIndex * sectionAngle) - (sectionAngle / 2); + + double spins = 6 * 2 * math.pi; + double finalRotation = currentPos + spins + targetForSelectedSection; + + while (finalRotation <= currentPos + spins) { + finalRotation += 2 * math.pi; + } + + _spinAnimation = Tween(begin: currentPos, end: finalRotation) + .animate( + CurvedAnimation( + parent: _rotationController, + curve: Curves.easeOutCubic, + ), + ); + + _rotationController.reset(); + _rotationController.animateTo(1.0).then((_) { + final wonPrize = gamePrizes[selectedPrizeIndex]; + _playWinSound(); + + setState(() { + currentRotation = finalRotation; + isSpinning = false; + resultText = 'Selamat! Anda mendapat ${wonPrize.name}!'; + prizeHistory.insert( + 0, + PrizeHistory( + prize: wonPrize.name, + dateTime: DateTime.now(), + value: 100, + color: _getPrizeColor(wonPrize, selectedPrizeIndex), + icon: Icons.card_giftcard, + gamePrizeId: wonPrize.id, + ), + ); + }); + + _showWinDialog(wonPrize); + _idleRotationController.repeat(); + }); + } + + void _showWinDialog(GamePrize wonPrize) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: AppColor.primaryGradient, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(40), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.2), + blurRadius: 15, + spreadRadius: 2, + ), + ], + ), + child: Icon( + Icons.card_giftcard, + color: AppColor.primary, + size: 40, + ), + ), + const SizedBox(height: 16), + Text( + 'Selamat!', + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + ), + ), + const SizedBox(height: 8), + Text( + 'Anda mendapatkan', + style: AppStyle.lg.copyWith(color: AppColor.textWhite), + ), + const SizedBox(height: 4), + Text( + wonPrize.name, + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + ), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + _playButtonTap(); + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.white, + foregroundColor: AppColor.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 12, + ), + ), + child: Text( + 'Terima Kasih', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ), + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state.isFetching) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: AppColor.primaryGradient, + ), + ), + child: Center( + child: SpinKitFadingCircle(color: AppColor.textWhite, size: 36), + ), + ), + ); + } + + if (gamePrizes.isEmpty && state.ferrisWheel.prizes.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + gamePrizes = state.ferrisWheel.prizes; + }); + }); + } + + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: AppColor.primaryGradient, + ), + ), + child: SafeArea( + child: Column( + children: [ + _buildHeader(), + _buildTabSelector(), + const SizedBox(height: 20), + Expanded( + child: currentTabIndex == 0 + ? _buildMainContent() + : currentTabIndex == 1 + ? _buildPrizeListContent() + : _buildHistoryContent(), + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + IconButton( + onPressed: () { + _playButtonTap(); + context.router.back(); + }, + icon: Icon(Icons.close, color: AppColor.textWhite, size: 28), + ), + Expanded( + child: Text( + 'SPIN & WIN', + style: AppStyle.h6.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + letterSpacing: 2, + ), + ), + ), + IconButton( + onPressed: _toggleMusic, + icon: Icon( + _isMusicEnabled ? Icons.volume_up : Icons.volume_off, + color: AppColor.textWhite, + ), + ), + IconButton( + onPressed: _toggleSound, + icon: Icon( + _isSoundEnabled ? Icons.graphic_eq : Icons.volume_mute, + color: AppColor.textWhite, + ), + ), + ], + ), + ); + } + + Widget _buildTabSelector() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(25), + ), + child: Row( + children: [ + for (int i = 0; i < 3; i++) + Expanded( + child: GestureDetector( + onTap: () { + _playButtonTap(); + setState(() => currentTabIndex = i); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: currentTabIndex == i + ? AppColor.white + : Colors.transparent, + borderRadius: BorderRadius.circular(25), + ), + child: Text( + ['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i], + textAlign: TextAlign.center, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: currentTabIndex == i + ? AppColor.primary + : AppColor.textWhite, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildMainContent() { + return Column( + children: [ + _buildUserInfoCard(), + Text( + resultText, + style: AppStyle.md.copyWith(color: AppColor.textWhite), + ), + const SizedBox(height: 20), + Expanded( + child: Center( + child: gamePrizes.isEmpty + ? SpinKitFadingCircle(color: AppColor.textWhite, size: 36) + : _buildWheelSection(), + ), + ), + _buildBottomBanner(), + ], + ); + } + + Widget _buildUserInfoCard() { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BlocBuilder( + builder: (context, auth) { + return Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + 'G', + style: AppStyle.lg.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + auth.user.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + auth.user.phoneNumber, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ], + ); + }, + ), + Row( + children: [ + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: AppColor.warning, + shape: BoxShape.circle, + ), + child: Icon(Icons.circle, size: 12, color: AppColor.textWhite), + ), + const SizedBox(width: 8), + Text( + 'Token: $tokens', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildWheelSection() { + return Stack( + alignment: Alignment.center, + children: [ + // Glow Effect + AnimatedBuilder( + animation: _glowController, + builder: (context, child) => Container( + width: 340, + height: 340, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.white.withOpacity( + 0.3 + 0.2 * _glowController.value, + ), + blurRadius: 40, + spreadRadius: 10, + ), + ], + ), + ), + ), + // Spinning Wheel + AnimatedBuilder( + animation: isSpinning ? _rotationController : _idleRotationController, + builder: (context, child) { + double rotationAngle = isSpinning + ? (_spinAnimation?.value ?? currentRotation) + : (currentRotation + (_idleRotationAnimation?.value ?? 0.0)); + + return Transform.rotate( + angle: rotationAngle, + child: CustomPaint( + size: const Size(320, 320), + painter: WheelPainter( + gamePrizes: gamePrizes, + getPrizeColor: _getPrizeColor, + ), + ), + ); + }, + ), + // Spin Button + _buildSpinButton(), + // Pointer + Positioned( + top: 30, + child: Container( + width: 0, + height: 0, + decoration: BoxDecoration( + border: Border( + left: BorderSide(width: 15, color: Colors.transparent), + right: BorderSide(width: 15, color: Colors.transparent), + bottom: BorderSide(width: 30, color: AppColor.error), + ), + ), + ), + ), + ], + ); + } + + Widget _buildSpinButton() { + return AnimatedBuilder( + animation: _pulseAnimation ?? const AlwaysStoppedAnimation(1.0), + builder: (context, child) => Transform.scale( + scale: _pulseAnimation?.value ?? 1.0, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.warning, AppColor.warning], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 6), + ), + BoxShadow( + color: AppColor.warning.withOpacity(0.5), + blurRadius: 20, + spreadRadius: (_pulseAnimation?.value ?? 1.0) * 5, + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(50), + onTap: _spinWheel, + child: Center( + child: Text( + 'SPIN', + style: AppStyle.lg.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + letterSpacing: 1, + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildBottomBanner() { + return Container( + margin: const EdgeInsets.all(20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient(colors: [AppColor.warning, AppColor.warning]), + borderRadius: BorderRadius.circular(15), + ), + child: Text( + 'Spin 30x lagi buat mainin spesial spin', + textAlign: TextAlign.center, + style: AppStyle.lg.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + Widget _buildPrizeListContent() { + return Container( + margin: const EdgeInsets.all(20), + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + 'Daftar Hadiah', + style: AppStyle.h6.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ), + const SizedBox(height: 20), + Expanded( + child: gamePrizes.isEmpty + ? Center( + child: SpinKitFadingCircle( + color: AppColor.white.withOpacity(0.7), + size: 36, + ), + ) + : ListView.builder( + itemCount: gamePrizes.length, + itemBuilder: (context, index) { + final prize = gamePrizes[index]; + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: _getPrizeColor(prize, index), + borderRadius: BorderRadius.circular(25), + ), + child: Icon( + Icons.card_giftcard, + color: AppColor.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + prize.name, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + 'ID: ${prize.id}', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + Text( + 'Game ID: ${prize.gameId}', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: _getPrizeColor( + prize, + index, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(15), + ), + child: Text( + 'Hadiah', + style: AppStyle.xs.copyWith( + color: _getPrizeColor(prize, index), + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildHistoryContent() { + return Container( + margin: const EdgeInsets.all(20), + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + 'Riwayat Hadiah', + style: AppStyle.h6.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ), + const SizedBox(height: 20), + Expanded( + child: prizeHistory.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.history, + size: 80, + color: AppColor.white.withOpacity(0.5), + ), + const SizedBox(height: 16), + Text( + 'Belum ada riwayat spin', + style: AppStyle.lg.copyWith( + color: AppColor.white.withOpacity(0.7), + ), + ), + const SizedBox(height: 8), + Text( + 'Mulai spin untuk melihat riwayat hadiah', + style: AppStyle.md.copyWith( + color: AppColor.white.withOpacity(0.5), + ), + ), + ], + ), + ) + : ListView.builder( + itemCount: prizeHistory.length, + itemBuilder: (context, index) { + final history = prizeHistory[index]; + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: history.color, + borderRadius: BorderRadius.circular(25), + ), + child: Icon( + history.icon, + color: AppColor.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + history.prize, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + '${history.dateTime.day}/${history.dateTime.month}/${history.dateTime.year} ${history.dateTime.hour.toString().padLeft(2, '0')}:${history.dateTime.minute.toString().padLeft(2, '0')}', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + if (history.gamePrizeId != null) + Text( + 'ID: ${history.gamePrizeId}', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary + .withOpacity(0.7), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.1), + borderRadius: BorderRadius.circular(15), + ), + child: Text( + 'Menang', + style: AppStyle.sm.copyWith( + color: AppColor.success, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/notification/notification_page.dart b/lib/presentation/pages/notification/notification_page.dart new file mode 100644 index 0000000..12306fb --- /dev/null +++ b/lib/presentation/pages/notification/notification_page.dart @@ -0,0 +1,270 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; + +@RoutePage() +class NotificationPage extends StatelessWidget { + const NotificationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar( + title: Text( + 'Notifikasi', + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + backgroundColor: AppColor.backgroundLight, + elevation: 0, + iconTheme: IconThemeData(color: AppColor.textPrimary), + actions: [ + TextButton( + onPressed: () => _markAllAsRead(context), + child: Text( + 'Tandai Semua', + style: AppStyle.sm.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + bottom: PreferredSize( + preferredSize: Size.fromHeight(1), + child: Container(height: 1, color: AppColor.borderLight), + ), + ), + body: ListView( + padding: EdgeInsets.symmetric(vertical: 8), + children: [ + // Today Section + _buildSectionHeader('Hari Ini'), + _buildNotificationItem( + icon: Icons.local_offer, + iconColor: AppColor.primary, + iconBgColor: AppColor.primaryWithOpacity(0.1), + title: 'Voucher Baru Tersedia!', + subtitle: 'Dapatkan diskon 50% untuk pembelian pertama Anda', + time: '2 menit lalu', + isUnread: true, + ), + _buildNotificationItem( + icon: Icons.shopping_bag, + iconColor: AppColor.success, + iconBgColor: AppColor.successWithOpacity(0.1), + title: 'Pesanan Sedang Dikirim', + subtitle: 'Pesanan #ORD-2024-001 sedang dalam perjalanan', + time: '1 jam lalu', + isUnread: true, + ), + _buildNotificationItem( + icon: Icons.payment, + iconColor: AppColor.info, + iconBgColor: AppColor.info.withOpacity(0.1), + title: 'Pembayaran Berhasil', + subtitle: + 'Pembayaran untuk pesanan #ORD-2024-001 telah dikonfirmasi', + time: '3 jam lalu', + isUnread: false, + ), + + // Yesterday Section + _buildSectionHeader('Kemarin'), + _buildNotificationItem( + icon: Icons.star, + iconColor: AppColor.warning, + iconBgColor: AppColor.warningWithOpacity(0.1), + title: 'Berikan Rating Produk', + subtitle: 'Bagaimana pengalaman Anda dengan produk yang dibeli?', + time: '1 hari lalu', + isUnread: false, + ), + _buildNotificationItem( + icon: Icons.local_shipping, + iconColor: AppColor.success, + iconBgColor: AppColor.successWithOpacity(0.1), + title: 'Pesanan Telah Diterima', + subtitle: 'Pesanan #ORD-2024-002 telah sampai di tujuan', + time: '1 hari lalu', + isUnread: false, + ), + + // This Week Section + _buildSectionHeader('Minggu Ini'), + _buildNotificationItem( + icon: Icons.campaign, + iconColor: AppColor.primary, + iconBgColor: AppColor.primaryWithOpacity(0.1), + title: 'Flash Sale 12.12!', + subtitle: 'Jangan lewatkan flash sale dengan diskon hingga 70%', + time: '3 hari lalu', + isUnread: false, + ), + _buildNotificationItem( + icon: Icons.card_giftcard, + iconColor: AppColor.secondary, + iconBgColor: AppColor.secondary.withOpacity(0.1), + title: 'Poin Reward Ditambahkan', + subtitle: 'Selamat! Anda mendapat 100 poin dari transaksi terakhir', + time: '5 hari lalu', + isUnread: false, + ), + _buildNotificationItem( + icon: Icons.security, + iconColor: AppColor.textSecondary, + iconBgColor: AppColor.textSecondary.withOpacity(0.1), + title: 'Keamanan Akun', + subtitle: 'Login dari perangkat baru terdeteksi', + time: '6 hari lalu', + isUnread: false, + ), + + SizedBox(height: 16), + ], + ), + ); + } + + Widget _buildSectionHeader(String title) { + return Padding( + padding: EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text( + title, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textSecondary, + ), + ), + ); + } + + Widget _buildNotificationItem({ + required IconData icon, + required Color iconColor, + required Color iconBgColor, + required String title, + required String subtitle, + required String time, + required bool isUnread, + }) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: isUnread + ? AppColor.primary.withOpacity(0.02) + : AppColor.backgroundLight, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isUnread + ? AppColor.primary.withOpacity(0.1) + : AppColor.borderLight, + ), + ), + child: InkWell( + onTap: () => _handleNotificationTap(title), + borderRadius: BorderRadius.circular(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon Container + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: iconColor, size: 24), + ), + SizedBox(width: 12), + + // Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + title, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ), + if (isUnread) + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColor.primary, + shape: BoxShape.circle, + ), + ), + ], + ), + SizedBox(height: 4), + Text( + subtitle, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.4, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 8), + Text( + time, + style: AppStyle.xs.copyWith(color: AppColor.textLight), + ), + ], + ), + ), + + // Options Menu + IconButton( + onPressed: () => _showNotificationOptions(title), + icon: Icon(Icons.more_vert, color: AppColor.textLight, size: 20), + constraints: BoxConstraints(), + padding: EdgeInsets.all(4), + ), + ], + ), + ), + ); + } + + void _markAllAsRead(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Semua notifikasi ditandai sebagai dibaca', + style: AppStyle.md.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.success, + behavior: SnackBarBehavior.floating, + margin: EdgeInsets.all(16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ); + } + + void _handleNotificationTap(String title) { + // Handle notification tap - navigate to relevant page + print('Notification tapped: $title'); + } + + void _showNotificationOptions(String title) { + // Show bottom sheet or popup menu for notification options + print('Show options for: $title'); + } +} diff --git a/lib/presentation/pages/onboarding/onboarding_page.dart b/lib/presentation/pages/onboarding/onboarding_page.dart new file mode 100644 index 0000000..6fb6749 --- /dev/null +++ b/lib/presentation/pages/onboarding/onboarding_page.dart @@ -0,0 +1,208 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:carousel_slider/carousel_slider.dart'; + +import '../../../common/theme/theme.dart'; +import '../../components/assets/assets.gen.dart'; +import '../../components/button/button.dart'; +import '../../components/image/image.dart'; +import '../../router/app_router.gr.dart'; + +@RoutePage() +class OnboardingPage extends StatefulWidget { + const OnboardingPage({super.key}); + + @override + State createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends State { + int currentIndex = 0; + final CarouselSliderController _carouselController = + CarouselSliderController(); + + final List onboardingData = [ + OnboardingData( + image: Assets.images.onboarding1.path, + title: 'Nikmati Hidangan Kapanpun, Dimanapun', + subtitle: + 'Bebas pilih cara penyajian, bisa dine-in di restoran atau pesan antar langsung ke lokasimu', + ), + OnboardingData( + image: Assets.images.onboarding2.path, + title: 'Bahan Berkualitas Premium', + subtitle: + 'Kami hanya menggunakan bahan segar pilihan untuk menghadirkan cita rasa terbaik di setiap hidangan', + ), + OnboardingData( + image: Assets.images.onboarding3.path, + title: 'Pesan dengan Praktis', + subtitle: + 'Aplikasi dengan tampilan sederhana memudahkanmu memesan makanan favorit kapan saja', + ), + ]; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final imageHeight = screenHeight * 0.6; // 60% dari tinggi layar + + return Scaffold( + backgroundColor: Colors.white, // Changed to white background + body: Column( + children: [ + // Image carousel - 60% of screen height + SizedBox(height: imageHeight, child: _buildImageCarousel()), + + // Bottom content - remaining space + Expanded(child: _buildBottomContent()), + ], + ), + ); + } + + Widget _buildImageCarousel() { + return Stack( + children: [ + // Carousel + CarouselSlider.builder( + carouselController: _carouselController, + itemCount: onboardingData.length, + itemBuilder: (context, index, realIndex) { + return SizedBox( + width: double.infinity, // Full width + child: ClipRRect( + child: Image.asset( + onboardingData[index].image, + fit: BoxFit.fill, + width: double.infinity, + errorBuilder: (context, error, stackTrace) { + return ImagePlaceholder(); + }, + ), + ), + ); + }, + options: CarouselOptions( + height: double.infinity, + viewportFraction: 1.0, + enableInfiniteScroll: true, + autoPlay: true, + onPageChanged: (index, reason) { + setState(() { + currentIndex = index; + }); + }, + ), + ), + + // White overlay gradient at bottom + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + height: 100, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.1), + Color(0x80FFFFFF), // White with 50% opacity + Colors.white, // Pure white + ], + ), + ), + ), + ), + + // Dots indicator positioned over the white overlay + Positioned( + bottom: 30, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + onboardingData.length, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + height: 8, + width: currentIndex == index ? 24 : 8, + decoration: BoxDecoration( + color: currentIndex == index + ? AppColor.primary + : AppColor.borderLight, + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + ), + ], + ); + } + + Widget _buildBottomContent() { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + const SizedBox(height: 16), + + // Title + Text( + onboardingData[currentIndex].title, + textAlign: TextAlign.center, + style: AppStyle.xl.copyWith(fontWeight: FontWeight.w800), + ), + + const SizedBox(height: 12), + + // Subtitle + Text( + onboardingData[currentIndex].subtitle, + textAlign: TextAlign.center, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w500, + color: AppColor.textSecondary, + ), + ), + + const Spacer(), + + AppElevatedButton( + onPressed: () => context.router.push(const LoginRoute()), + title: 'Masuk', + ), + + const SizedBox(height: 12), + + TextButton( + onPressed: () => context.router.push(const MainRoute()), + child: Text( + 'Lewati tahap ini', + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } +} + +class OnboardingData { + final String image; + final String title; + final String subtitle; + + OnboardingData({ + required this.image, + required this.title, + required this.subtitle, + }); +} diff --git a/lib/presentation/pages/order/order_detail/order_detail_page.dart b/lib/presentation/pages/order/order_detail/order_detail_page.dart new file mode 100644 index 0000000..7016635 --- /dev/null +++ b/lib/presentation/pages/order/order_detail/order_detail_page.dart @@ -0,0 +1,893 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import '../../main/pages/order/order_page.dart'; + +@RoutePage() +class OrderDetailPage extends StatefulWidget { + final Order order; + + const OrderDetailPage({super.key, required this.order}); + + @override + State createState() => _OrderDetailPageState(); +} + +class _OrderDetailPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.white, + appBar: _buildAppBar(), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildOrderHeader(), + const SizedBox(height: 24), + _buildCustomerInfo(), + const SizedBox(height: 24), + _buildOrderItems(), + const SizedBox(height: 24), + _buildOrderSummary(), + if (widget.order.notes != null) ...[ + const SizedBox(height: 24), + _buildOrderNotes(), + ], + const SizedBox(height: 32), + _buildActionButtons(), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + PreferredSizeWidget _buildAppBar() { + return AppBar( + title: Text('Detail Pesanan'), + actions: [ + IconButton( + onPressed: _shareOrder, + icon: const Icon(Icons.share, color: AppColor.textSecondary), + ), + PopupMenuButton( + onSelected: _handleMenuAction, + icon: const Icon(Icons.more_vert, color: AppColor.textSecondary), + itemBuilder: (context) => [ + const PopupMenuItem( + value: 'edit', + child: Row( + children: [ + Icon(Icons.edit, size: 20), + SizedBox(width: 12), + Text('Edit Pesanan'), + ], + ), + ), + const PopupMenuItem( + value: 'duplicate', + child: Row( + children: [ + Icon(Icons.content_copy, size: 20), + SizedBox(width: 12), + Text('Duplikat Pesanan'), + ], + ), + ), + const PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Icon(Icons.delete, size: 20, color: Colors.red), + SizedBox(width: 12), + Text('Hapus Pesanan', style: TextStyle(color: Colors.red)), + ], + ), + ), + ], + ), + ], + ); + } + + Widget _buildOrderHeader() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.order.id, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + _buildStatusBadge(widget.order.status), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Icon(Icons.access_time, size: 16, color: AppColor.textSecondary), + const SizedBox(width: 8), + Text( + _formatDateTime(widget.order.orderDate), + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ], + ), + ); + } + + Widget _buildStatusBadge(OrderStatus status) { + Color backgroundColor; + Color textColor; + String text; + + switch (status) { + case OrderStatus.pending: + backgroundColor = Colors.orange.withOpacity(0.1); + textColor = Colors.orange; + text = 'Menunggu'; + break; + case OrderStatus.processing: + backgroundColor = Colors.blue.withOpacity(0.1); + textColor = Colors.blue; + text = 'Diproses'; + break; + case OrderStatus.completed: + backgroundColor = Colors.green.withOpacity(0.1); + textColor = Colors.green; + text = 'Selesai'; + break; + case OrderStatus.cancelled: + backgroundColor = Colors.red.withOpacity(0.1); + textColor = Colors.red; + text = 'Dibatalkan'; + break; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + text, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ); + } + + Widget _buildCustomerInfo() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Informasi Pelanggan', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildInfoRow( + icon: Icons.person, + label: 'Nama', + value: widget.order.customerName, + ), + if (widget.order.phoneNumber != null) ...[ + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.phone, + label: 'Telepon', + value: widget.order.phoneNumber!, + isClickable: true, + ), + ], + if (widget.order.address != null) ...[ + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.location_on, + label: 'Alamat', + value: widget.order.address!, + isMultiline: true, + ), + ], + ], + ), + ); + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + bool isClickable = false, + bool isMultiline = false, + }) { + return Row( + crossAxisAlignment: isMultiline + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, size: 18, color: AppColor.primary), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + GestureDetector( + onTap: isClickable ? () => _callCustomer(value) : null, + child: Text( + value, + style: AppStyle.sm.copyWith( + color: isClickable + ? AppColor.primary + : AppColor.textPrimary, + fontWeight: FontWeight.w500, + decoration: isClickable ? TextDecoration.underline : null, + ), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildOrderItems() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Item Pesanan', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 16), + ...widget.order.items.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + return Column( + children: [ + if (index > 0) + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider(color: AppColor.border, height: 1), + ), + _buildOrderItem(item), + ], + ); + }).toList(), + ], + ), + ); + } + + Widget _buildOrderItem(OrderItem item) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: item.imageUrl != null + ? ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + item.imageUrl!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.restaurant, + color: AppColor.primary, + size: 24, + ); + }, + ), + ) + : Icon(Icons.restaurant, color: AppColor.primary, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + 'Rp ${_formatCurrency(item.price)} x ${item.quantity}', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + if (item.notes != null) ...[ + const SizedBox(height: 4), + Text( + 'Catatan: ${item.notes}', + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontStyle: FontStyle.italic, + ), + ), + ], + ], + ), + ), + Text( + 'Rp ${_formatCurrency(item.price * item.quantity)}', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ], + ); + } + + Widget _buildOrderSummary() { + final subtotal = widget.order.items.fold( + 0, + (sum, item) => sum + (item.price * item.quantity), + ); + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Ringkasan Pesanan', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildSummaryRow('Subtotal', subtotal), + const SizedBox(height: 8), + _buildSummaryRow('Ongkos Kirim', 10000), + const SizedBox(height: 8), + _buildSummaryRow('Pajak', subtotal * 0.1), + const SizedBox(height: 12), + Divider(color: AppColor.border, height: 1), + const SizedBox(height: 12), + _buildSummaryRow('Total', widget.order.totalAmount, isBold: true), + ], + ), + ); + } + + Widget _buildSummaryRow(String label, double amount, {bool isBold = false}) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + ), + ), + Text( + 'Rp ${_formatCurrency(amount)}', + style: AppStyle.sm.copyWith( + color: isBold ? AppColor.textPrimary : AppColor.textSecondary, + fontWeight: isBold ? FontWeight.bold : FontWeight.w500, + ), + ), + ], + ); + } + + Widget _buildOrderNotes() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.note, size: 18, color: AppColor.primary), + const SizedBox(width: 8), + Text( + 'Catatan Pesanan', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColor.border), + ), + child: Text( + widget.order.notes!, + style: AppStyle.sm.copyWith( + color: AppColor.textPrimary, + height: 1.5, + ), + ), + ), + ], + ), + ); + } + + Widget _buildActionButtons() { + return Column( + children: [ + if (widget.order.status == OrderStatus.pending) ...[ + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _cancelOrder, + icon: const Icon(Icons.cancel, size: 20), + label: const Text('Batalkan'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red, + side: const BorderSide(color: Colors.red), + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + onPressed: _processOrder, + icon: const Icon(Icons.check, size: 20), + label: const Text('Proses'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ], + ), + ] else if (widget.order.status == OrderStatus.processing) ...[ + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: _completeOrder, + icon: const Icon(Icons.check_circle, size: 20), + label: const Text('Selesaikan Pesanan'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: AppColor.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ], + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: _contactCustomer, + icon: const Icon(Icons.chat, size: 20), + label: const Text('Hubungi Pelanggan'), + style: OutlinedButton.styleFrom( + foregroundColor: AppColor.primary, + side: BorderSide(color: AppColor.primary), + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ], + ); + } + + String _formatDateTime(DateTime dateTime) { + final months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Ags', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + final day = dateTime.day; + final month = months[dateTime.month - 1]; + final year = dateTime.year; + final hour = dateTime.hour.toString().padLeft(2, '0'); + final minute = dateTime.minute.toString().padLeft(2, '0'); + + return '$day $month $year, $hour:$minute WIB'; + } + + String _formatCurrency(double amount) { + return amount + .toStringAsFixed(0) + .replaceAllMapped( + RegExp(r'(\d)(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + void _shareOrder() { + // Implement share functionality + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Fitur berbagi akan segera hadir'), + backgroundColor: AppColor.primary, + ), + ); + } + + void _handleMenuAction(String action) { + switch (action) { + case 'edit': + _editOrder(); + break; + case 'duplicate': + _duplicateOrder(); + break; + case 'delete': + _deleteOrder(); + break; + } + } + + void _editOrder() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Fitur edit pesanan akan segera hadir'), + backgroundColor: AppColor.primary, + ), + ); + } + + void _duplicateOrder() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Pesanan berhasil diduplikat'), + backgroundColor: Colors.green, + ), + ); + } + + void _deleteOrder() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + 'Hapus Pesanan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + content: Text( + 'Apakah Anda yakin ingin menghapus pesanan ${widget.order.id}? Tindakan ini tidak dapat dibatalkan.', + style: AppStyle.md, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Batal', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ), + ElevatedButton( + onPressed: () { + context.router.back(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Pesanan berhasil dihapus'), + backgroundColor: Colors.red, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + child: const Text('Hapus'), + ), + ], + ), + ); + } + + void _processOrder() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + 'Proses Pesanan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + content: Text( + 'Apakah Anda yakin ingin memproses pesanan ${widget.order.id}?', + style: AppStyle.md, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Batal', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Pesanan sedang diproses'), + backgroundColor: Colors.blue, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: Colors.white, + ), + child: const Text('Proses'), + ), + ], + ), + ); + } + + void _completeOrder() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + 'Selesaikan Pesanan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + content: Text( + 'Apakah pesanan ${widget.order.id} sudah selesai dan siap dikirim?', + style: AppStyle.md, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Belum', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Pesanan berhasil diselesaikan'), + backgroundColor: Colors.green, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + child: const Text('Selesai'), + ), + ], + ), + ); + } + + void _cancelOrder() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + 'Batalkan Pesanan', + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + content: Text( + 'Apakah Anda yakin ingin membatalkan pesanan ${widget.order.id}?', + style: AppStyle.md, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Tidak', + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Pesanan berhasil dibatalkan'), + backgroundColor: Colors.red, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + child: const Text('Batalkan'), + ), + ], + ), + ); + } + + void _contactCustomer() { + showModalBottomSheet( + context: context, + backgroundColor: AppColor.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) => Container( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.border, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 20), + Text( + 'Hubungi ${widget.order.customerName}', + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 24), + if (widget.order.phoneNumber != null) + ListTile( + leading: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.phone, color: Colors.green), + ), + title: const Text('Telepon'), + subtitle: Text(widget.order.phoneNumber!), + onTap: () => _callCustomer(widget.order.phoneNumber!), + ), + ListTile( + leading: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.chat, color: Colors.blue), + ), + title: const Text('Kirim Pesan'), + subtitle: const Text('Via WhatsApp'), + onTap: () => _sendMessage(), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + void _callCustomer(String phoneNumber) { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Menghubungi $phoneNumber...'), + backgroundColor: Colors.green, + ), + ); + } + + void _sendMessage() { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Membuka WhatsApp...'), + backgroundColor: Colors.blue, + ), + ); + } +} diff --git a/lib/presentation/pages/poin/pages/poin_history_page.dart b/lib/presentation/pages/poin/pages/poin_history_page.dart new file mode 100644 index 0000000..391d30c --- /dev/null +++ b/lib/presentation/pages/poin/pages/poin_history_page.dart @@ -0,0 +1,388 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +// Models +enum TransactionType { + all('Semua'), + redeemed('Poin Ditukar'), + earned('Poin Didapat'), + refunded('Poin Dikembalikan'), + bonus('Bonus Poin'); + + const TransactionType(this.label); + final String label; +} + +class PointTransaction { + final String id; + final String title; + final String category; + final String source; + final int points; + final TransactionType type; + final DateTime date; + final String? productImage; + + PointTransaction({ + required this.id, + required this.title, + required this.category, + required this.source, + required this.points, + required this.type, + required this.date, + this.productImage, + }); + + bool get isPositive => + type == TransactionType.earned || + type == TransactionType.refunded || + type == TransactionType.bonus; +} + +@RoutePage() +class PoinHistoryPage extends StatefulWidget { + const PoinHistoryPage({super.key}); + + @override + State createState() => _PoinHistoryPageState(); +} + +class _PoinHistoryPageState extends State { + TransactionType selectedFilter = TransactionType.all; + + // Sample transaction data + final List allTransactions = [ + PointTransaction( + id: "t1", + title: "Es Teh Manis", + category: "Minuman", + source: "Penukaran Voucher", + points: -1500, + type: TransactionType.redeemed, + date: DateTime.now().subtract(Duration(hours: 2)), + productImage: "🧊", + ), + PointTransaction( + id: "t2", + title: "Nasi Gudeg", + category: "Makanan", + source: "Transaksi Pembelian", + points: 400, + type: TransactionType.earned, + date: DateTime.now().subtract(Duration(days: 1)), + productImage: "🍛", + ), + PointTransaction( + id: "t3", + title: "Member Emas", + category: "Membership", + source: "Bonus Bulanan", + points: 2000, + type: TransactionType.bonus, + date: DateTime.now().subtract(Duration(days: 2)), + productImage: "🎁", + ), + PointTransaction( + id: "t4", + title: "Kopi Susu", + category: "Minuman", + source: "Pembatalan Pesanan", + points: 2000, + type: TransactionType.refunded, + date: DateTime.now().subtract(Duration(days: 3)), + productImage: "☕", + ), + PointTransaction( + id: "t5", + title: "Diskon 50%", + category: "Voucher", + source: "Penukaran Voucher", + points: -5000, + type: TransactionType.redeemed, + date: DateTime.now().subtract(Duration(days: 5)), + productImage: "🏷️", + ), + PointTransaction( + id: "t6", + title: "Gado-gado", + category: "Makanan", + source: "Transaksi Pembelian", + points: 350, + type: TransactionType.earned, + date: DateTime.now().subtract(Duration(days: 7)), + productImage: "🥗", + ), + PointTransaction( + id: "t7", + title: "Hari Kemerdekaan", + category: "Event", + source: "Bonus Special", + points: 1700, + type: TransactionType.bonus, + date: DateTime.now().subtract(Duration(days: 12)), + productImage: "🇮🇩", + ), + PointTransaction( + id: "t8", + title: "Keripik Singkong", + category: "Cemilan", + source: "Penukaran Voucher", + points: -1000, + type: TransactionType.redeemed, + date: DateTime.now().subtract(Duration(days: 14)), + productImage: "🥔", + ), + PointTransaction( + id: "t9", + title: "Review Produk", + category: "Aktivitas", + source: "Bonus Review", + points: 500, + type: TransactionType.bonus, + date: DateTime.now().subtract(Duration(days: 20)), + productImage: "⭐", + ), + PointTransaction( + id: "t10", + title: "Bakso", + category: "Makanan", + source: "Pembatalan Pesanan", + points: 3000, + type: TransactionType.refunded, + date: DateTime.now().subtract(Duration(days: 25)), + productImage: "🍲", + ), + ]; + + List get filteredTransactions { + if (selectedFilter == TransactionType.all) { + return allTransactions; + } + return allTransactions.where((t) => t.type == selectedFilter).toList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.white, + appBar: AppBar( + title: Text("Riwayat Poin"), + centerTitle: true, + elevation: 0, + ), + body: Column( + children: [ + _buildFilterChips(), + Expanded(child: _buildTransactionList()), + ], + ), + ); + } + + Widget _buildFilterChips() { + return Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: TransactionType.values.map((type) { + final isSelected = selectedFilter == type; + return Container( + margin: EdgeInsets.only(right: 8), + child: FilterChip( + selected: isSelected, + label: Text(type.label), + onSelected: (selected) { + setState(() { + selectedFilter = type; + }); + }, + backgroundColor: AppColor.white, + selectedColor: AppColor.primary, + checkmarkColor: AppColor.white, + labelStyle: AppStyle.sm.copyWith( + color: isSelected ? AppColor.white : AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + side: BorderSide( + color: isSelected ? AppColor.primary : AppColor.border, + width: 1, + ), + elevation: 0, + pressElevation: 1, + ), + ); + }).toList(), + ), + ), + ); + } + + Widget _buildTransactionList() { + final transactions = filteredTransactions; + + if (transactions.isEmpty) { + return _buildEmptyState(); + } + + return ListView.builder( + padding: EdgeInsets.all(16), + itemCount: transactions.length, + itemBuilder: (context, index) { + final transaction = transactions[index]; + return _buildTransactionCard(transaction); + }, + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + shape: BoxShape.circle, + ), + child: Icon( + Icons.history_outlined, + size: 48, + color: AppColor.textLight, + ), + ), + SizedBox(height: 16), + Text( + "Belum Ada Transaksi", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textSecondary, + ), + ), + SizedBox(height: 8), + Text( + "Transaksi ${selectedFilter.label.toLowerCase()}\nbelum tersedia", + style: AppStyle.sm.copyWith(color: AppColor.textLight), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildTransactionCard(PointTransaction transaction) { + final isPositive = transaction.isPositive; + final formattedDate = _formatDate(transaction.date); + + return Container( + margin: EdgeInsets.only(bottom: 16), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + border: Border(bottom: BorderSide(color: AppColor.border, width: 1)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Category label + Text( + "Poin Didapat", + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w500, + ), + ), + + SizedBox(height: 8), + + // Title and Points Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + transaction.title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox(width: 12), + Row( + children: [ + // Green circle icon + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: isPositive ? Color(0xFF10B981) : AppColor.error, + shape: BoxShape.circle, + ), + child: Icon(Icons.add, color: Colors.white, size: 14), + ), + SizedBox(width: 6), + Text( + "${isPositive ? '+' : ''}${transaction.points} Poin", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: isPositive ? Color(0xFF10B981) : AppColor.error, + ), + ), + ], + ), + ], + ), + + SizedBox(height: 12), + + // Date + Text( + formattedDate, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ); + } + + String _formatDate(DateTime date) { + final now = DateTime.now(); + final difference = now.difference(date); + + if (difference.inDays == 0) { + if (difference.inHours == 0) { + return "${difference.inMinutes} menit lalu"; + } + return "${difference.inHours} jam lalu"; + } else if (difference.inDays == 1) { + return "Kemarin"; + } else if (difference.inDays < 7) { + return "${difference.inDays} hari lalu"; + } else { + final months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Ags', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + return "${date.day} ${months[date.month - 1]} ${date.year}"; + } + } +} diff --git a/lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart b/lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart new file mode 100644 index 0000000..635c410 --- /dev/null +++ b/lib/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart @@ -0,0 +1,789 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; +import '../../poin_page.dart'; + +@RoutePage() +class ProductRedeemPage extends StatefulWidget { + final Product product; + final PointCard pointCard; + + const ProductRedeemPage({ + super.key, + required this.product, + required this.pointCard, + }); + + @override + State createState() => _ProductRedeemPageState(); +} + +class _ProductRedeemPageState extends State + with TickerProviderStateMixin { + bool _isProcessing = false; + bool _showSuccess = false; + String _redeemCode = ''; + late AnimationController _pulseController; + late AnimationController _successController; + late Animation _pulseAnimation; + late Animation _successAnimation; + + @override + void initState() { + super.initState(); + _pulseController = AnimationController( + duration: Duration(milliseconds: 1500), + vsync: this, + ); + _successController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.1).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + _successAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _successController, curve: Curves.elasticOut), + ); + + _pulseController.repeat(reverse: true); + } + + @override + void dispose() { + _pulseController.dispose(); + _successController.dispose(); + super.dispose(); + } + + bool get canRedeem => + widget.pointCard.availablePoints >= widget.product.pointsRequired; + int get pointsShortage => + widget.product.pointsRequired - widget.pointCard.availablePoints; + + Future _processRedeem() async { + setState(() { + _isProcessing = true; + }); + + // Simulate API call + await Future.delayed(Duration(seconds: 2)); + + // Generate mock redeem code + _redeemCode = + 'RDM${DateTime.now().millisecondsSinceEpoch.toString().substring(8)}'; + + setState(() { + _isProcessing = false; + _showSuccess = true; + }); + + _pulseController.stop(); + _successController.forward(); + + // Auto dismiss after 3 seconds + Future.delayed(Duration(seconds: 3), () { + if (mounted) { + Navigator.pop( + context, + true, + ); // Return true to indicate successful redemption + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.white, + body: CustomScrollView( + slivers: [ + // Custom App Bar with product image + SliverAppBar( + expandedHeight: 280, + floating: false, + pinned: true, + backgroundColor: AppColor.white, + elevation: 0, + flexibleSpace: FlexibleSpaceBar( + background: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary.withOpacity(0.1), + AppColor.backgroundLight, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Center( + child: AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _isProcessing ? _pulseAnimation.value : 1.0, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColor.white, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.2), + blurRadius: 20, + offset: Offset(0, 8), + ), + ], + ), + child: Center( + child: Text( + widget.product.image, + style: TextStyle(fontSize: 48), + ), + ), + ), + ); + }, + ), + ), + ), + ), + ), + + // Product Details + SliverToBoxAdapter( + child: _showSuccess ? _buildSuccessView() : _buildProductDetails(), + ), + ], + ), + ); + } + + Widget _buildProductDetails() { + return Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Product Name & Popular Badge + Row( + children: [ + Expanded( + child: Text( + widget.product.name, + style: AppStyle.h4.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + ), + if (widget.product.isPopular) + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: AppColor.warning, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.local_fire_department, + color: AppColor.white, + size: 14, + ), + SizedBox(width: 4), + Text( + "Popular", + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + + SizedBox(height: 8), + + // Description + Text( + widget.product.fullDescription ?? widget.product.description, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + + SizedBox(height: 24), + + // Points Required Card + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primary.withOpacity(0.1), + AppColor.primary.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.primary.withOpacity(0.3)), + ), + child: Column( + children: [ + Row( + children: [ + Icon( + Icons.stars_rounded, + color: AppColor.warning, + size: 24, + ), + SizedBox(width: 8), + Text( + "Points Required", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${widget.product.pointsRequired}", + style: AppStyle.h3.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.primary, + ), + ), + Text( + "Points needed", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "${widget.pointCard.availablePoints}", + style: AppStyle.h3.copyWith( + fontWeight: FontWeight.w700, + color: canRedeem + ? AppColor.success + : AppColor.error, + ), + ), + Text( + "Your points", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ], + ), + ], + ), + ), + + SizedBox(height: 20), + + // Insufficient Points Warning + if (!canRedeem) + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.error.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.error.withOpacity(0.3)), + ), + child: Row( + children: [ + Icon( + Icons.warning_amber_rounded, + color: AppColor.error, + size: 24, + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Insufficient Points", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.error, + ), + ), + Text( + "You need ${pointsShortage} more points to redeem this item", + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + ], + ), + ), + ], + ), + ), + + SizedBox(height: 24), + + // Terms & Conditions + _buildInfoSection( + "Terms & Conditions", + widget.product.termsAndConditions ?? + "• Valid for single use only\n• Cannot be combined with other offers\n• No cash value\n• Subject to availability\n• Valid at participating locations only", + ), + + SizedBox(height: 16), + + // Validity + _buildInfoSection( + "Validity", + widget.product.validUntil ?? + "Valid until: 30 days from redemption date", + ), + + SizedBox(height: 32), + + // Redeem Button + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: (_isProcessing || !canRedeem) ? null : _processRedeem, + style: ElevatedButton.styleFrom( + backgroundColor: canRedeem + ? AppColor.primary + : AppColor.textLight, + foregroundColor: AppColor.white, + elevation: canRedeem ? 4 : 0, + shadowColor: AppColor.primary.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: _isProcessing + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + AppColor.white, + ), + ), + ), + SizedBox(width: 12), + Text( + "Processing...", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ], + ) + : Text( + canRedeem ? "Redeem Now" : "Insufficient Points", + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ), + ), + + SizedBox(height: 20), + + // Alternative action for insufficient points + if (!canRedeem) + SizedBox( + width: double.infinity, + height: 48, + child: OutlinedButton( + onPressed: () { + // Navigate to earn points page or show earn points options + _showEarnPointsOptions(); + }, + style: OutlinedButton.styleFrom( + foregroundColor: AppColor.primary, + side: BorderSide(color: AppColor.primary, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add_circle_outline, size: 20), + SizedBox(width: 8), + Text( + "Earn More Points", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.primary, + ), + ), + ], + ), + ), + ), + + SizedBox(height: 40), + ], + ), + ); + } + + Widget _buildSuccessView() { + return AnimatedBuilder( + animation: _successAnimation, + builder: (context, child) { + return Transform.scale( + scale: _successAnimation.value, + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + children: [ + SizedBox(height: 40), + + // Success Icon + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: AppColor.success, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.success.withOpacity(0.3), + blurRadius: 20, + offset: Offset(0, 8), + ), + ], + ), + child: Icon( + Icons.check_rounded, + color: AppColor.white, + size: 48, + ), + ), + + SizedBox(height: 24), + + // Success Message + Text( + "Redemption Successful!", + style: AppStyle.h4.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.success, + ), + textAlign: TextAlign.center, + ), + + SizedBox(height: 12), + + Text( + "Your ${widget.product.name} is ready!", + style: AppStyle.lg.copyWith(color: AppColor.textSecondary), + textAlign: TextAlign.center, + ), + + SizedBox(height: 32), + + // Redeem Code Card + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.success, width: 2), + boxShadow: [ + BoxShadow( + color: AppColor.success.withOpacity(0.1), + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + children: [ + Text( + "Your Redeem Code", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textSecondary, + ), + ), + SizedBox(height: 12), + Container( + padding: EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _redeemCode, + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.primary, + letterSpacing: 2, + ), + ), + ), + SizedBox(height: 12), + ], + ), + ), + + SizedBox(height: 24), + + // Points Deducted Info + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(Icons.stars, color: AppColor.primary, size: 20), + SizedBox(width: 8), + Text( + "${widget.product.pointsRequired} points deducted", + style: AppStyle.md.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildInfoSection(String title, String content) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 8), + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Text( + content, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.6, + ), + ), + ), + ], + ); + } + + void _showEarnPointsOptions() { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => Container( + height: MediaQuery.of(context).size.height * 0.6, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + children: [ + // Handle + Container( + margin: EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.textLight.withOpacity(0.5), + borderRadius: BorderRadius.circular(2), + ), + ), + + Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Earn More Points", + style: AppStyle.h5.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 8), + Text( + "You need ${pointsShortage} more points to redeem this item", + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + + SizedBox(height: 24), + + // Earn Points Options + _buildEarnOption( + "🛍️", + "Shop & Earn", + "Earn 1 point for every \$1 spent", + "Earn up to 500 points per day", + ), + + _buildEarnOption( + "🎯", + "Complete Missions", + "Daily and weekly challenges", + "Earn 100-1000 points per mission", + ), + + _buildEarnOption( + "👥", + "Refer Friends", + "Invite friends to join", + "Earn 500 points per referral", + ), + + SizedBox(height: 20), + + // Close Button + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + "Got it", + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildEarnOption( + String icon, + String title, + String description, + String reward, + ) { + return Container( + margin: EdgeInsets.only(bottom: 12), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColor.border), + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + ), + child: Center(child: Text(icon, style: TextStyle(fontSize: 24))), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + Text( + description, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + Text( + reward, + style: AppStyle.xs.copyWith( + color: AppColor.primary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + Icon(Icons.arrow_forward_ios, color: AppColor.textLight, size: 16), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/poin/poin_page.dart b/lib/presentation/pages/poin/poin_page.dart new file mode 100644 index 0000000..b94665a --- /dev/null +++ b/lib/presentation/pages/poin/poin_page.dart @@ -0,0 +1,800 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../application/customer/customer_point_loader/customer_point_loader_bloc.dart'; +import '../../../common/theme/theme.dart'; +import '../../router/app_router.gr.dart'; + +// Models +class PointCard { + final int totalPoints; + final int usedPoints; + final String membershipLevel; + + PointCard({ + required this.totalPoints, + required this.usedPoints, + required this.membershipLevel, + }); + + int get availablePoints => totalPoints - usedPoints; +} + +class Category { + final String id; + final String name; + final String icon; + final List products; + + Category({ + required this.id, + required this.name, + required this.icon, + required this.products, + }); +} + +class Product { + final String id; + final String name; + final String image; + final int pointsRequired; + final String description; + final bool isPopular; + final String? fullDescription; + final String? validUntil; + final String? termsAndConditions; + + Product({ + required this.id, + required this.name, + required this.image, + required this.pointsRequired, + required this.description, + this.isPopular = false, + this.fullDescription, + this.validUntil, + this.termsAndConditions, + }); +} + +@RoutePage() +class PoinPage extends StatefulWidget { + const PoinPage({super.key}); + + @override + State createState() => _PoinPageState(); +} + +class _PoinPageState extends State { + final ScrollController _scrollController = ScrollController(); + + // Sample data - Indonesian content + final PointCard pointCard = PointCard( + totalPoints: 15000, + usedPoints: 3500, + membershipLevel: "Member Emas", + ); + + final List categories = [ + Category( + id: "c1", + name: "Minuman", + icon: "🥤", + products: [ + Product( + id: "p1", + name: "Es Teh Manis", + image: "🧊", + pointsRequired: 1500, + description: "Teh manis dingin segar", + isPopular: true, + ), + Product( + id: "p2", + name: "Kopi Susu", + image: "☕", + pointsRequired: 2000, + description: "Kopi dengan susu creamy", + ), + Product( + id: "p3", + name: "Jus Jeruk", + image: "🍊", + pointsRequired: 2500, + description: "Jus jeruk segar alami", + ), + ], + ), + Category( + id: "c2", + name: "Makanan", + icon: "🍽️", + products: [ + Product( + id: "p4", + name: "Nasi Gudeg", + image: "🍛", + pointsRequired: 4000, + description: "Gudeg Jogja autentik", + isPopular: true, + ), + Product( + id: "p5", + name: "Gado-gado", + image: "🥗", + pointsRequired: 3500, + description: "Sayuran dengan bumbu kacang", + ), + Product( + id: "p6", + name: "Bakso", + image: "🍲", + pointsRequired: 3000, + description: "Bakso sapi dengan mie", + ), + ], + ), + Category( + id: "c3", + name: "Cemilan", + icon: "🍪", + products: [ + Product( + id: "p7", + name: "Keripik Singkong", + image: "🥔", + pointsRequired: 1000, + description: "Keripik singkong renyah", + ), + Product( + id: "p8", + name: "Onde-onde", + image: "🍡", + pointsRequired: 1500, + description: "Onde-onde isi kacang hijau", + ), + Product( + id: "p9", + name: "Pisang Goreng", + image: "🍌", + pointsRequired: 1200, + description: "Pisang goreng krispy", + ), + ], + ), + Category( + id: "c4", + name: "Voucher", + icon: "🎟️", + products: [ + Product( + id: "p10", + name: "Diskon 50%", + image: "🏷️", + pointsRequired: 5000, + description: "Potongan harga 50% untuk semua menu", + isPopular: true, + ), + Product( + id: "p11", + name: "Gratis Ongkir", + image: "🚚", + pointsRequired: 2000, + description: "Bebas ongkos kirim untuk pesanan apapun", + ), + Product( + id: "p12", + name: "Buy 1 Get 1", + image: "🎁", + pointsRequired: 25000, // High points untuk demonstrasi insufficient + description: "Beli 1 gratis 1 untuk minuman", + ), + ], + ), + ]; + + Map categoryKeys = {}; + String? activeCategoryId; // Track active category + + @override + void initState() { + super.initState(); + activeCategoryId = categories.first.id; // Set first category as active + _initializeCategoryKeys(); + context.read().add( + CustomerPointLoaderEvent.fetched(), + ); + } + + void _initializeCategoryKeys() { + categoryKeys.clear(); + for (var category in categories) { + categoryKeys[category.id] = GlobalKey(); + } + } + + void _scrollToCategory(String categoryId) { + // Update active category state FIRST + setState(() { + activeCategoryId = categoryId; + }); + + // Tunggu sampai widget selesai rebuild dan keys ter-attach + Future.delayed(Duration(milliseconds: 50), () { + final key = categoryKeys[categoryId]; + if (key?.currentContext != null) { + print("Scrolling to category: $categoryId"); // Debug log + + try { + Scrollable.ensureVisible( + key!.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + alignment: 0.1, // Position kategori sedikit dari atas + ); + } catch (e) { + print("Error scrolling to category: $e"); + } + } else { + print("Key not found for category: $categoryId"); // Debug log + print("Available keys: ${categoryKeys.keys.toList()}"); // Debug log + + // Retry dengan delay lebih lama jika belum ready + Future.delayed(Duration(milliseconds: 200), () { + final retryKey = categoryKeys[categoryId]; + if (retryKey?.currentContext != null) { + Scrollable.ensureVisible( + retryKey!.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + body: NestedScrollView( + controller: _scrollController, + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + // Sticky AppBar + SliverAppBar( + elevation: 0, + title: Text("Poin"), + centerTitle: true, + floating: false, + pinned: true, // Made sticky + snap: false, + actions: [ + IconButton( + icon: Icon(Icons.history), + onPressed: () => context.router.push(PoinHistoryRoute()), + ), + ], + ), + + // Point Card Section + SliverToBoxAdapter(child: _buildPointCard()), + + // Sticky Category Tabs + SliverPersistentHeader( + pinned: true, + key: ValueKey(activeCategoryId), // Simplified key + delegate: _StickyHeaderDelegate( + child: _buildCategoryTabs(), + height: 66, + activeCategoryId: activeCategoryId, // Pass active category ID + ), + ), + ]; + }, + body: ListView.builder( + padding: EdgeInsets.only(top: 16), + itemCount: categories.length, + itemBuilder: (context, index) { + final category = categories[index]; + return _buildCategorySection(category); + }, + ), + ), + ); + } + + Widget _buildPointCard() { + return BlocBuilder( + builder: (context, state) { + return Container( + margin: EdgeInsets.all(16), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [AppColor.primary, AppColor.primaryDark], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + blurRadius: 12, + offset: Offset(0, 6), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text( + // "", + // style: AppStyle.sm.copyWith( + // color: AppColor.textWhite.withOpacity(0.9), + // fontWeight: FontWeight.w500, + // ), + // ), + // SizedBox(height: 4), + Text( + "${state.customerPoint.totalPoints}", + style: AppStyle.h2.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w700, + ), + ), + Text( + "Poin Tersedia", + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + ], + ), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.stars_rounded, + color: AppColor.textWhite, + size: 32, + ), + ), + ], + ), + + // SizedBox(height: 16), + // Container( + // height: 8, + // decoration: BoxDecoration( + // color: AppColor.white.withOpacity(0.3), + // borderRadius: BorderRadius.circular(4), + // ), + // child: FractionallySizedBox( + // widthFactor: + // (pointCard.totalPoints - pointCard.usedPoints) / + // pointCard.totalPoints, + // alignment: Alignment.centerLeft, + // child: Container( + // decoration: BoxDecoration( + // color: AppColor.textWhite, + // borderRadius: BorderRadius.circular(4), + // ), + // ), + // ), + // ), + // SizedBox(height: 8), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "Terpakai: ${pointCard.usedPoints}", + // style: AppStyle.xs.copyWith( + // color: AppColor.textWhite.withOpacity(0.8), + // ), + // ), + // Text( + // "Total: ${pointCard.totalPoints}", + // style: AppStyle.xs.copyWith( + // color: AppColor.textWhite.withOpacity(0.8), + // ), + // ), + // ], + // ), + ], + ), + ); + }, + ); + } + + Widget _buildCategoryTabs() { + return Container( + color: AppColor.background, // Background untuk sticky header + padding: EdgeInsets.symmetric(vertical: 8), + child: Container( + height: 50, + child: ListView.builder( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 16), + itemCount: categories.length, + itemBuilder: (context, index) { + final category = categories[index]; + final isActive = + activeCategoryId == + category.id; // Check if this category is active + + return GestureDetector( + onTap: () => _scrollToCategory(category.id), + child: Container( + margin: EdgeInsets.only(right: 12), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + color: isActive + ? AppColor.primary + : AppColor.white, // Change background when active + borderRadius: BorderRadius.circular(25), + border: Border.all( + color: isActive + ? AppColor.primary + : AppColor.border, // Change border when active + width: 2, + ), + boxShadow: [ + BoxShadow( + color: AppColor.textLight.withOpacity(0.1), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(category.icon, style: TextStyle(fontSize: 16)), + SizedBox(width: 6), + Text( + category.name, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w500, + color: isActive + ? AppColor.textWhite + : AppColor + .textPrimary, // Change text color when active + ), + ), + ], + ), + ), + ); + }, + ), + ), + ); + } + + Widget _buildCategorySection(Category category) { + return Container( + key: categoryKeys[category.id], + margin: EdgeInsets.only(bottom: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Text(category.icon, style: TextStyle(fontSize: 20)), + SizedBox(width: 8), + Text( + category.name, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ], + ), + ), + SizedBox(height: 12), + // Fixed height yang lebih besar untuk menghindari overflow + Container( + height: 240, + child: ListView.builder( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 16), + itemCount: category.products.length, + itemBuilder: (context, index) { + final product = category.products[index]; + return _buildProductCard(product); + }, + ), + ), + ], + ), + ); + } + + Widget _buildProductCard(Product product) { + final canRedeem = pointCard.availablePoints >= product.pointsRequired; + final pointsShortage = canRedeem + ? 0 + : product.pointsRequired - pointCard.availablePoints; + + return Container( + width: 160, + margin: EdgeInsets.only(right: 12), + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.textLight.withOpacity(0.15), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Product Image + Container( + height: 90, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.vertical( + top: Radius.circular(16), + ), + ), + child: Stack( + children: [ + Center( + child: Text( + product.image, + style: TextStyle(fontSize: 36), + ), + ), + if (product.isPopular) + Positioned( + top: 6, + right: 6, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 6, + vertical: 3, + ), + decoration: BoxDecoration( + color: AppColor.warning, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "Populer", + style: AppStyle.xs.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ), + ), + ], + ), + ), + + // Product Info + Expanded( + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: canRedeem + ? AppColor.textPrimary + : AppColor.textLight, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4), + Expanded( + child: Text( + product.description, + style: AppStyle.xs.copyWith( + color: canRedeem + ? AppColor.textSecondary + : AppColor.textLight, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.stars, + size: 14, + color: canRedeem + ? AppColor.warning + : AppColor.textLight, + ), + SizedBox(width: 4), + Expanded( + child: Text( + "${product.pointsRequired}", + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: canRedeem + ? AppColor.primary + : AppColor.textLight, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 32, + child: ElevatedButton( + onPressed: canRedeem + ? () => _redeemProduct(product) + : null, + style: ElevatedButton.styleFrom( + backgroundColor: canRedeem + ? AppColor.primary + : AppColor.textLight, + foregroundColor: AppColor.white, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: FittedBox( + child: Text( + canRedeem ? "Tukar" : "Poin Kurang", + style: AppStyle.xs.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + + // Overlay untuk insufficient points + if (!canRedeem) + Container( + decoration: BoxDecoration( + color: AppColor.textLight.withOpacity(0.7), + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.lock_outline, + color: AppColor.textSecondary, + size: 20, + ), + SizedBox(height: 4), + Text( + "Butuh ${pointsShortage}", + style: AppStyle.xs.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textSecondary, + fontSize: 10, + ), + textAlign: TextAlign.center, + ), + Text( + "poin lagi", + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontSize: 10, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + void _redeemProduct(Product product) { + context.router.push( + ProductRedeemRoute(product: product, pointCard: pointCard), + ); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } +} + +// Custom SliverPersistentHeaderDelegate untuk sticky category tabs +class _StickyHeaderDelegate extends SliverPersistentHeaderDelegate { + final Widget child; + final double height; + final String? activeCategoryId; // Track active category + + _StickyHeaderDelegate({ + required this.child, + required this.height, + required this.activeCategoryId, // Track category changes + }); + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return child; + } + + @override + double get maxExtent => height; + + @override + double get minExtent => height; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + // Rebuild when active category changes + if (oldDelegate is _StickyHeaderDelegate) { + bool categoryChanged = oldDelegate.activeCategoryId != activeCategoryId; + + print("shouldRebuild - Category changed: $categoryChanged"); + print( + "Old category: ${oldDelegate.activeCategoryId}, New category: $activeCategoryId", + ); + + return categoryChanged; + } + return true; + } +} diff --git a/lib/presentation/pages/reward/reward_page.dart b/lib/presentation/pages/reward/reward_page.dart new file mode 100644 index 0000000..1e0952d --- /dev/null +++ b/lib/presentation/pages/reward/reward_page.dart @@ -0,0 +1,478 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +// Models +class Reward { + final String id; + final String name; + final String image; + final int pointsUsed; + final String description; + final DateTime redeemedAt; + final RewardStatus status; + final String? couponCode; + final String? validUntil; + final String? termsAndConditions; + final String categoryName; + final String categoryIcon; + + Reward({ + required this.id, + required this.name, + required this.image, + required this.pointsUsed, + required this.description, + required this.redeemedAt, + required this.status, + required this.categoryName, + required this.categoryIcon, + this.couponCode, + this.validUntil, + this.termsAndConditions, + }); + + String get statusText { + switch (status) { + case RewardStatus.active: + return "Aktif"; + case RewardStatus.used: + return "Sudah Digunakan"; + case RewardStatus.expired: + return "Kadaluarsa"; + } + } + + Color get statusColor { + switch (status) { + case RewardStatus.active: + return AppColor.success; + case RewardStatus.used: + return AppColor.textSecondary; + case RewardStatus.expired: + return AppColor.error; + } + } +} + +enum RewardStatus { active, used, expired } + +@RoutePage() +class RewardPage extends StatefulWidget { + const RewardPage({super.key}); + + @override + State createState() => _RewardPageState(); +} + +class _RewardPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + // Sample reward data + final List rewards = [ + Reward( + id: "r1", + name: "Es Teh Manis", + image: "🧊", + pointsUsed: 1500, + description: "Teh manis dingin segar", + redeemedAt: DateTime.now().subtract(Duration(hours: 2)), + status: RewardStatus.active, + categoryName: "Minuman", + categoryIcon: "🥤", + couponCode: "ETM123456", + validUntil: "31 Des 2025", + termsAndConditions: + "Berlaku untuk dine-in dan take away. Tidak dapat digabung dengan promo lain.", + ), + Reward( + id: "r2", + name: "Diskon 50%", + image: "🏷️", + pointsUsed: 5000, + description: "Potongan harga 50% untuk semua menu", + redeemedAt: DateTime.now().subtract(Duration(days: 1)), + status: RewardStatus.used, + categoryName: "Voucher", + categoryIcon: "🎟️", + couponCode: "DISC50789", + validUntil: "15 Des 2025", + termsAndConditions: + "Berlaku untuk pembelian minimum Rp 50.000. Tidak berlaku untuk menu promo.", + ), + Reward( + id: "r3", + name: "Nasi Gudeg", + image: "🍛", + pointsUsed: 4000, + description: "Gudeg Jogja autentik", + redeemedAt: DateTime.now().subtract(Duration(days: 3)), + status: RewardStatus.active, + categoryName: "Makanan", + categoryIcon: "🍽️", + couponCode: "GUDEG456", + validUntil: "25 Des 2025", + termsAndConditions: + "Berlaku untuk 1 porsi. Dapat dimakan di tempat atau dibawa pulang.", + ), + Reward( + id: "r4", + name: "Gratis Ongkir", + image: "🚚", + pointsUsed: 2000, + description: "Bebas ongkos kirim untuk pesanan apapun", + redeemedAt: DateTime.now().subtract(Duration(days: 15)), + status: RewardStatus.expired, + categoryName: "Voucher", + categoryIcon: "🎟️", + validUntil: "20 Agu 2025", + termsAndConditions: + "Berlaku untuk area Jabodetabek. Minimum pembelian Rp 25.000.", + ), + Reward( + id: "r5", + name: "Kopi Susu", + image: "☕", + pointsUsed: 2000, + description: "Kopi dengan susu creamy", + redeemedAt: DateTime.now().subtract(Duration(days: 7)), + status: RewardStatus.used, + categoryName: "Minuman", + categoryIcon: "🥤", + couponCode: "KOPI987654", + validUntil: "30 Nov 2025", + termsAndConditions: + "Berlaku untuk size regular. Dapat request level manis.", + ), + ]; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 4, vsync: this); + } + + List get filteredRewards { + switch (_tabController.index) { + case 0: + return rewards; // Semua + case 1: + return rewards.where((r) => r.status == RewardStatus.active).toList(); + case 2: + return rewards.where((r) => r.status == RewardStatus.used).toList(); + case 3: + return rewards.where((r) => r.status == RewardStatus.expired).toList(); + default: + return rewards; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.backgroundLight, + appBar: AppBar(title: Text("Reward Saya"), centerTitle: true), + body: Column( + children: [ + _buildCleanTabBar(), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + _buildRewardList(), // Semua + _buildRewardList(), // Aktif + _buildRewardList(), // Digunakan + _buildRewardList(), // Kadaluarsa + ], + ), + ), + ], + ), + ); + } + + Widget _buildCleanTabBar() { + final activeCount = rewards + .where((r) => r.status == RewardStatus.active) + .length; + final usedCount = rewards + .where((r) => r.status == RewardStatus.used) + .length; + final expiredCount = rewards + .where((r) => r.status == RewardStatus.expired) + .length; + + return Container( + margin: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 20, + offset: Offset(0, 4), + ), + ], + ), + child: TabBar( + controller: _tabController, + indicatorSize: TabBarIndicatorSize.tab, + isScrollable: true, + tabAlignment: TabAlignment.start, + dividerColor: Colors.transparent, + onTap: (index) => setState(() {}), + tabs: [ + _buildCleanTab("Semua", rewards.length), + _buildCleanTab("Aktif", activeCount), + _buildCleanTab("Terpakai", usedCount), + _buildCleanTab("Expired", expiredCount), + ], + ), + ); + } + + Widget _buildCleanTab(String label, int count) { + return Container(height: 44, child: Center(child: Text("$label ($count)"))); + } + + Widget _buildRewardList() { + final filteredData = filteredRewards; + + if (filteredData.isEmpty) { + return _buildEmptyState(); + } + + return ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8), + itemCount: filteredData.length, + itemBuilder: (context, index) { + final reward = filteredData[index]; + return _buildCleanRewardCard(reward); + }, + ); + } + + Widget _buildEmptyState() { + String message; + String icon; + + switch (_tabController.index) { + case 1: + message = "Belum ada reward aktif"; + icon = "🎯"; + break; + case 2: + message = "Belum ada reward yang digunakan"; + icon = "✅"; + break; + case 3: + message = "Tidak ada reward yang kadaluarsa"; + icon = "⏰"; + break; + default: + message = "Belum ada reward"; + icon = "🎁"; + } + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(icon, style: TextStyle(fontSize: 64)), + SizedBox(height: 16), + Text( + message, + style: AppStyle.lg.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 8), + Text( + "Tukar poin Anda untuk mendapatkan reward menarik", + style: AppStyle.sm.copyWith(color: AppColor.textLight), + textAlign: TextAlign.center, + ), + SizedBox(height: 24), + ElevatedButton( + onPressed: () => context.router.back(), + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + elevation: 0, + ), + child: Text( + "Tukar Poin Sekarang", + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ), + ], + ), + ); + } + + Widget _buildCleanRewardCard(Reward reward) { + return Container( + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 20, + offset: Offset(0, 4), + ), + ], + ), + child: InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(16), + child: Padding( + padding: EdgeInsets.all(20), + child: Row( + children: [ + // Product Image - Simplified + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(14), + ), + child: Center( + child: Text(reward.image, style: TextStyle(fontSize: 24)), + ), + ), + SizedBox(width: 16), + + // Reward Info - Cleaner layout + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + reward.name, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + ), + _buildStatusBadge(reward), + ], + ), + SizedBox(height: 4), + Text( + reward.description, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + ), + ), + SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.access_time, + size: 12, + color: AppColor.textLight, + ), + SizedBox(width: 4), + Text( + _formatDate(reward.redeemedAt), + style: AppStyle.xs.copyWith( + color: AppColor.textLight, + ), + ), + Spacer(), + Icon(Icons.stars, size: 14, color: AppColor.warning), + SizedBox(width: 4), + Text( + "${reward.pointsUsed}", + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildStatusBadge(Reward reward) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: reward.statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + reward.statusText, + style: AppStyle.xs.copyWith( + color: reward.statusColor, + fontWeight: FontWeight.w600, + ), + ), + ); + } + + String _formatDate(DateTime date) { + final months = [ + '', + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + final now = DateTime.now(); + final difference = now.difference(date); + + if (difference.inDays == 0) { + if (difference.inHours == 0) { + return "${difference.inMinutes} menit lalu"; + } + return "${difference.inHours} jam lalu"; + } else if (difference.inDays == 1) { + return "Kemarin"; + } else if (difference.inDays < 7) { + return "${difference.inDays} hari lalu"; + } else { + return "${date.day} ${months[date.month]} ${date.year}"; + } + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } +} diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart new file mode 100644 index 0000000..84f74e2 --- /dev/null +++ b/lib/presentation/pages/splash/splash_page.dart @@ -0,0 +1,135 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'dart:async'; + +import '../../../application/auth/auth_bloc.dart'; +import '../../../common/theme/theme.dart'; +import '../../components/assets/assets.gen.dart'; +import '../../router/app_router.gr.dart'; + +@RoutePage() +class SplashPage extends StatefulWidget { + const SplashPage({super.key}); + + @override + State createState() => _SplashPageState(); +} + +class _SplashPageState extends State + with SingleTickerProviderStateMixin { + late AnimationController _logoController; + + late Animation _logoScaleAnimation; + late Animation _logoOpacityAnimation; + + @override + void initState() { + super.initState(); + _initAnimations(); + _startAnimations(); + _navigateToHome(); + } + + void _initAnimations() { + // Logo Animation Controller + _logoController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + // Logo Animations + _logoScaleAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), + ); + + _logoOpacityAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _logoController, curve: Curves.easeIn)); + } + + void _startAnimations() { + // Start logo animation + _logoController.forward(); + } + + void _navigateToHome() { + Timer(const Duration(milliseconds: 2500), () { + if (mounted) { + context.read().add(const AuthEvent.fetchCurrentUser()); + } + }); + } + + @override + void dispose() { + _logoController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.isAuthenticated) { + context.router.replace(const MainRoute()); + } else { + context.router.replace(const OnboardingRoute()); + } + }, + child: Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [AppColor.backgroundLight, AppColor.background], + ), + ), + child: Center( + child: AnimatedBuilder( + animation: _logoController, + builder: (context, child) { + return Transform.scale( + scale: _logoScaleAnimation.value, + child: Opacity( + opacity: _logoOpacityAnimation.value, + child: Container( + width: 140, + height: 140, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColor.primaryWithOpacity(0.4), + blurRadius: 25, + offset: const Offset(0, 12), + ), + ], + ), + child: ClipOval( + child: Padding( + padding: const EdgeInsets.all(20), + child: Assets.images.logo.image(fit: BoxFit.contain), + ), + ), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart b/lib/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart new file mode 100644 index 0000000..112d8d5 --- /dev/null +++ b/lib/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart @@ -0,0 +1,299 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../common/theme/theme.dart'; + +@RoutePage() +class VoucherDetailPage extends StatelessWidget { + const VoucherDetailPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar( + title: Text( + 'Detail Voucher', + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + ), + backgroundColor: AppColor.backgroundLight, + elevation: 0, + iconTheme: IconThemeData(color: AppColor.textPrimary), + bottom: PreferredSize( + preferredSize: Size.fromHeight(1), + child: Container(height: 1, color: AppColor.borderLight), + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Voucher Card Section + Container( + margin: EdgeInsets.all(16), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.3), + offset: Offset(0, 4), + blurRadius: 12, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Voucher Icon + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppColor.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.local_offer, + color: AppColor.white, + size: 24, + ), + ), + SizedBox(height: 16), + + // Title + Text( + 'New User Voucher - Diskon 50% hingga Rp35K', + style: AppStyle.xl.copyWith( + color: AppColor.white, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + + // Subtitle + Text( + 'Tanpa Min. Belanja', + style: AppStyle.md.copyWith( + color: AppColor.white.withOpacity(0.9), + ), + ), + SizedBox(height: 20), + + // Voucher Details Row + Row( + children: [ + Expanded( + child: _buildDetailItem( + icon: Icons.schedule, + label: 'Berlaku hingga', + value: '25 Sep 2025', + ), + ), + Container( + width: 1, + height: 40, + color: AppColor.white.withOpacity(0.3), + margin: EdgeInsets.symmetric(horizontal: 16), + ), + Expanded( + child: _buildDetailItem( + icon: Icons.shopping_cart, + label: 'Min. Transaksi', + value: '-', + ), + ), + ], + ), + ], + ), + ), + + // Action Buttons + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // Copy voucher code functionality + _copyVoucherCode(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.backgroundLight, + foregroundColor: AppColor.primary, + elevation: 0, + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: AppColor.border), + ), + ), + icon: Icon(Icons.copy, size: 20), + label: Text( + 'Salin Kode', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.primary, + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () { + // Use voucher functionality + _useVoucher(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + foregroundColor: AppColor.white, + elevation: 0, + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Gunakan', + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w600, + color: AppColor.white, + ), + ), + ), + ), + ], + ), + ), + + SizedBox(height: 24), + + // Description Section + _buildSection( + title: 'Deskripsi', + content: + 'Dapatkan diskon hingga 50% untuk pembelian pertama Anda! Voucher ini khusus untuk pengguna baru dan berlaku untuk semua kategori produk tanpa minimum pembelian.', + ), + SizedBox(height: 16), + + // Terms and Conditions Section + _buildSection( + title: 'Syarat dan Ketentuan', + content: _getDefaultTermsAndConditions(), + ), + + SizedBox(height: 24), + ], + ), + ), + ); + } + + Widget _buildDetailItem({ + required IconData icon, + required String label, + required String value, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 16, color: AppColor.white.withOpacity(0.8)), + SizedBox(width: 4), + Text( + label, + style: AppStyle.xs.copyWith( + color: AppColor.white.withOpacity(0.8), + ), + ), + ], + ), + SizedBox(height: 4), + Text( + value, + style: AppStyle.sm.copyWith( + color: AppColor.white, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } + + Widget _buildSection({required String title, required String content}) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.borderLight), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 12), + Text( + content, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + ], + ), + ); + } + + void _copyVoucherCode(BuildContext context) { + // Implementation for copying voucher code + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Kode voucher berhasil disalin!', + style: AppStyle.md.copyWith(color: AppColor.white), + ), + backgroundColor: AppColor.success, + behavior: SnackBarBehavior.floating, + margin: EdgeInsets.all(16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ); + } + + void _useVoucher(BuildContext context) { + // Implementation for using voucher + // Navigate back to checkout or shopping cart + context.router.back(); // Return true to indicate voucher was selected + } + + String _getDefaultTermsAndConditions() { + return '''• Voucher hanya berlaku untuk pengguna baru +• Tidak dapat digabungkan dengan promo lain +• Berlaku untuk semua kategori produk +• Voucher tidak dapat diuangkan +• Voucher akan hangus jika tidak digunakan sebelum tanggal expired +• Satu voucher hanya berlaku untuk satu kali transaksi +• Voucher tidak berlaku untuk produk yang sudah didiskon +• Kebijakan voucher dapat berubah sewaktu-waktu'''; + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart new file mode 100644 index 0000000..32f0bad --- /dev/null +++ b/lib/presentation/router/app_router.dart @@ -0,0 +1,74 @@ +import 'package:auto_route/auto_route.dart'; +import 'app_router.gr.dart'; + +@AutoRouterConfig() +class AppRouter extends RootStackRouter { + @override + List get routes => [ + // Splash + AutoRoute(page: SplashRoute.page, initial: true), + + // Onboarding + AutoRoute(page: OnboardingRoute.page), + + // Auth + AutoRoute(page: LoginRoute.page), + AutoRoute(page: RegisterRoute.page), + AutoRoute(page: OtpRoute.page), + AutoRoute(page: PinRoute.page), + AutoRoute(page: CreatePasswordRoute.page), + AutoRoute(page: PasswordRoute.page), + + // Main + AutoRoute( + page: MainRoute.page, + children: [ + AutoRoute(page: HomeRoute.page), + AutoRoute(page: VoucherRoute.page), + AutoRoute(page: OrderRoute.page), + AutoRoute(page: ProfileRoute.page), + ], + ), + + // Merchant + AutoRoute(page: MerchantRoute.page), + AutoRoute(page: MerchantDetailRoute.page), + + // Point + AutoRoute(page: PoinRoute.page), + AutoRoute(page: PoinHistoryRoute.page), + AutoRoute(page: ProductRedeemRoute.page), + + // Draw + AutoRoute(page: DrawRoute.page), + AutoRoute( + page: DrawDetailRoute.page, + children: [ + AutoRoute(page: DrawTodayRoute.page), + AutoRoute(page: DrawMyNumberRoute.page), + AutoRoute(page: DrawWinnerRoute.page), + AutoRoute(page: DrawInfoRoute.page), + ], + ), + + // Voucher + AutoRoute(page: VoucherDetailRoute.page), + + // Notification + AutoRoute(page: NotificationRoute.page), + + // Order + AutoRoute(page: OrderDetailRoute.page), + + // Reward + AutoRoute(page: RewardRoute.page), + + // Account + AutoRoute(page: AccountMyRoute.page), + AutoRoute(page: AddressRoute.page), + AutoRoute(page: PaymentRoute.page), + + // Mini Games + AutoRoute(page: FerrisWheelRoute.page), + ]; +} diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart new file mode 100644 index 0000000..af27992 --- /dev/null +++ b/lib/presentation/router/app_router.gr.dart @@ -0,0 +1,802 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:auto_route/auto_route.dart' as _i33; +import 'package:enaklo/presentation/pages/account/account_my/account_my_page.dart' + as _i1; +import 'package:enaklo/presentation/pages/account/address/address_page.dart' + as _i2; +import 'package:enaklo/presentation/pages/account/payment/payment_page.dart' + as _i22; +import 'package:enaklo/presentation/pages/auth/create_password/create_password_page.dart' + as _i3; +import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i12; +import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i20; +import 'package:enaklo/presentation/pages/auth/password/password_page.dart' + as _i21; +import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i23; +import 'package:enaklo/presentation/pages/auth/register/register_page.dart' + as _i28; +import 'package:enaklo/presentation/pages/draw/draw_page.dart' as _i7; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/draw_detail_page.dart' + as _i4; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_info_page.dart' + as _i5; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_my_number_page.dart' + as _i6; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_today_page.dart' + as _i8; +import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart' + as _i9; +import 'package:enaklo/presentation/pages/main/main_page.dart' as _i13; +import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart' + as _i11; +import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' + as _i19; +import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' + as _i27; +import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' + as _i32; +import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i15; +import 'package:enaklo/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart' + as _i14; +import 'package:enaklo/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart' + as _i10; +import 'package:enaklo/presentation/pages/notification/notification_page.dart' + as _i16; +import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart' + as _i17; +import 'package:enaklo/presentation/pages/order/order_detail/order_detail_page.dart' + as _i18; +import 'package:enaklo/presentation/pages/poin/pages/poin_history_page.dart' + as _i24; +import 'package:enaklo/presentation/pages/poin/pages/product_redeem/product_redeem_page.dart' + as _i26; +import 'package:enaklo/presentation/pages/poin/poin_page.dart' as _i25; +import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i29; +import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i30; +import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' + as _i31; +import 'package:enaklo/sample/sample_data.dart' as _i35; +import 'package:flutter/material.dart' as _i34; + +/// generated route for +/// [_i1.AccountMyPage] +class AccountMyRoute extends _i33.PageRouteInfo { + const AccountMyRoute({List<_i33.PageRouteInfo>? children}) + : super(AccountMyRoute.name, initialChildren: children); + + static const String name = 'AccountMyRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i1.AccountMyPage(); + }, + ); +} + +/// generated route for +/// [_i2.AddressPage] +class AddressRoute extends _i33.PageRouteInfo { + const AddressRoute({List<_i33.PageRouteInfo>? children}) + : super(AddressRoute.name, initialChildren: children); + + static const String name = 'AddressRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i2.AddressPage(); + }, + ); +} + +/// generated route for +/// [_i3.CreatePasswordPage] +class CreatePasswordRoute extends _i33.PageRouteInfo { + CreatePasswordRoute({ + _i34.Key? key, + required String registrationToken, + List<_i33.PageRouteInfo>? children, + }) : super( + CreatePasswordRoute.name, + args: CreatePasswordRouteArgs( + key: key, + registrationToken: registrationToken, + ), + initialChildren: children, + ); + + static const String name = 'CreatePasswordRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i33.WrappedRoute( + child: _i3.CreatePasswordPage( + key: args.key, + registrationToken: args.registrationToken, + ), + ); + }, + ); +} + +class CreatePasswordRouteArgs { + const CreatePasswordRouteArgs({this.key, required this.registrationToken}); + + final _i34.Key? key; + + final String registrationToken; + + @override + String toString() { + return 'CreatePasswordRouteArgs{key: $key, registrationToken: $registrationToken}'; + } +} + +/// generated route for +/// [_i4.DrawDetailPage] +class DrawDetailRoute extends _i33.PageRouteInfo { + const DrawDetailRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawDetailRoute.name, initialChildren: children); + + static const String name = 'DrawDetailRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i4.DrawDetailPage(); + }, + ); +} + +/// generated route for +/// [_i5.DrawInfoPage] +class DrawInfoRoute extends _i33.PageRouteInfo { + const DrawInfoRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawInfoRoute.name, initialChildren: children); + + static const String name = 'DrawInfoRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i5.DrawInfoPage(); + }, + ); +} + +/// generated route for +/// [_i6.DrawMyNumberPage] +class DrawMyNumberRoute extends _i33.PageRouteInfo { + const DrawMyNumberRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawMyNumberRoute.name, initialChildren: children); + + static const String name = 'DrawMyNumberRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i6.DrawMyNumberPage(); + }, + ); +} + +/// generated route for +/// [_i7.DrawPage] +class DrawRoute extends _i33.PageRouteInfo { + const DrawRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawRoute.name, initialChildren: children); + + static const String name = 'DrawRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i7.DrawPage(); + }, + ); +} + +/// generated route for +/// [_i8.DrawTodayPage] +class DrawTodayRoute extends _i33.PageRouteInfo { + const DrawTodayRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawTodayRoute.name, initialChildren: children); + + static const String name = 'DrawTodayRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i8.DrawTodayPage(); + }, + ); +} + +/// generated route for +/// [_i9.DrawWinnerPage] +class DrawWinnerRoute extends _i33.PageRouteInfo { + const DrawWinnerRoute({List<_i33.PageRouteInfo>? children}) + : super(DrawWinnerRoute.name, initialChildren: children); + + static const String name = 'DrawWinnerRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i9.DrawWinnerPage(); + }, + ); +} + +/// generated route for +/// [_i10.FerrisWheelPage] +class FerrisWheelRoute extends _i33.PageRouteInfo { + const FerrisWheelRoute({List<_i33.PageRouteInfo>? children}) + : super(FerrisWheelRoute.name, initialChildren: children); + + static const String name = 'FerrisWheelRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return _i33.WrappedRoute(child: const _i10.FerrisWheelPage()); + }, + ); +} + +/// generated route for +/// [_i11.HomePage] +class HomeRoute extends _i33.PageRouteInfo { + const HomeRoute({List<_i33.PageRouteInfo>? children}) + : super(HomeRoute.name, initialChildren: children); + + static const String name = 'HomeRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i11.HomePage(); + }, + ); +} + +/// generated route for +/// [_i12.LoginPage] +class LoginRoute extends _i33.PageRouteInfo { + const LoginRoute({List<_i33.PageRouteInfo>? children}) + : super(LoginRoute.name, initialChildren: children); + + static const String name = 'LoginRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return _i33.WrappedRoute(child: const _i12.LoginPage()); + }, + ); +} + +/// generated route for +/// [_i13.MainPage] +class MainRoute extends _i33.PageRouteInfo { + const MainRoute({List<_i33.PageRouteInfo>? children}) + : super(MainRoute.name, initialChildren: children); + + static const String name = 'MainRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i13.MainPage(); + }, + ); +} + +/// generated route for +/// [_i14.MerchantDetailPage] +class MerchantDetailRoute extends _i33.PageRouteInfo { + MerchantDetailRoute({ + _i34.Key? key, + required _i35.MerchantModel merchant, + List<_i33.PageRouteInfo>? children, + }) : super( + MerchantDetailRoute.name, + args: MerchantDetailRouteArgs(key: key, merchant: merchant), + initialChildren: children, + ); + + static const String name = 'MerchantDetailRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i14.MerchantDetailPage(key: args.key, merchant: args.merchant); + }, + ); +} + +class MerchantDetailRouteArgs { + const MerchantDetailRouteArgs({this.key, required this.merchant}); + + final _i34.Key? key; + + final _i35.MerchantModel merchant; + + @override + String toString() { + return 'MerchantDetailRouteArgs{key: $key, merchant: $merchant}'; + } +} + +/// generated route for +/// [_i15.MerchantPage] +class MerchantRoute extends _i33.PageRouteInfo { + const MerchantRoute({List<_i33.PageRouteInfo>? children}) + : super(MerchantRoute.name, initialChildren: children); + + static const String name = 'MerchantRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i15.MerchantPage(); + }, + ); +} + +/// generated route for +/// [_i16.NotificationPage] +class NotificationRoute extends _i33.PageRouteInfo { + const NotificationRoute({List<_i33.PageRouteInfo>? children}) + : super(NotificationRoute.name, initialChildren: children); + + static const String name = 'NotificationRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i16.NotificationPage(); + }, + ); +} + +/// generated route for +/// [_i17.OnboardingPage] +class OnboardingRoute extends _i33.PageRouteInfo { + const OnboardingRoute({List<_i33.PageRouteInfo>? children}) + : super(OnboardingRoute.name, initialChildren: children); + + static const String name = 'OnboardingRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i17.OnboardingPage(); + }, + ); +} + +/// generated route for +/// [_i18.OrderDetailPage] +class OrderDetailRoute extends _i33.PageRouteInfo { + OrderDetailRoute({ + _i34.Key? key, + required _i19.Order order, + List<_i33.PageRouteInfo>? children, + }) : super( + OrderDetailRoute.name, + args: OrderDetailRouteArgs(key: key, order: order), + initialChildren: children, + ); + + static const String name = 'OrderDetailRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i18.OrderDetailPage(key: args.key, order: args.order); + }, + ); +} + +class OrderDetailRouteArgs { + const OrderDetailRouteArgs({this.key, required this.order}); + + final _i34.Key? key; + + final _i19.Order order; + + @override + String toString() { + return 'OrderDetailRouteArgs{key: $key, order: $order}'; + } +} + +/// generated route for +/// [_i19.OrderPage] +class OrderRoute extends _i33.PageRouteInfo { + const OrderRoute({List<_i33.PageRouteInfo>? children}) + : super(OrderRoute.name, initialChildren: children); + + static const String name = 'OrderRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i19.OrderPage(); + }, + ); +} + +/// generated route for +/// [_i20.OtpPage] +class OtpRoute extends _i33.PageRouteInfo { + OtpRoute({ + _i34.Key? key, + required String registrationToken, + required String phoneNumber, + List<_i33.PageRouteInfo>? children, + }) : super( + OtpRoute.name, + args: OtpRouteArgs( + key: key, + registrationToken: registrationToken, + phoneNumber: phoneNumber, + ), + initialChildren: children, + ); + + static const String name = 'OtpRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i33.WrappedRoute( + child: _i20.OtpPage( + key: args.key, + registrationToken: args.registrationToken, + phoneNumber: args.phoneNumber, + ), + ); + }, + ); +} + +class OtpRouteArgs { + const OtpRouteArgs({ + this.key, + required this.registrationToken, + required this.phoneNumber, + }); + + final _i34.Key? key; + + final String registrationToken; + + final String phoneNumber; + + @override + String toString() { + return 'OtpRouteArgs{key: $key, registrationToken: $registrationToken, phoneNumber: $phoneNumber}'; + } +} + +/// generated route for +/// [_i21.PasswordPage] +class PasswordRoute extends _i33.PageRouteInfo { + PasswordRoute({ + _i34.Key? key, + required String phoneNumber, + List<_i33.PageRouteInfo>? children, + }) : super( + PasswordRoute.name, + args: PasswordRouteArgs(key: key, phoneNumber: phoneNumber), + initialChildren: children, + ); + + static const String name = 'PasswordRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i33.WrappedRoute( + child: _i21.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), + ); + }, + ); +} + +class PasswordRouteArgs { + const PasswordRouteArgs({this.key, required this.phoneNumber}); + + final _i34.Key? key; + + final String phoneNumber; + + @override + String toString() { + return 'PasswordRouteArgs{key: $key, phoneNumber: $phoneNumber}'; + } +} + +/// generated route for +/// [_i22.PaymentPage] +class PaymentRoute extends _i33.PageRouteInfo { + const PaymentRoute({List<_i33.PageRouteInfo>? children}) + : super(PaymentRoute.name, initialChildren: children); + + static const String name = 'PaymentRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i22.PaymentPage(); + }, + ); +} + +/// generated route for +/// [_i23.PinPage] +class PinRoute extends _i33.PageRouteInfo { + PinRoute({ + _i34.Key? key, + bool isCreatePin = true, + String? title, + List<_i33.PageRouteInfo>? children, + }) : super( + PinRoute.name, + args: PinRouteArgs(key: key, isCreatePin: isCreatePin, title: title), + initialChildren: children, + ); + + static const String name = 'PinRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const PinRouteArgs(), + ); + return _i23.PinPage( + key: args.key, + isCreatePin: args.isCreatePin, + title: args.title, + ); + }, + ); +} + +class PinRouteArgs { + const PinRouteArgs({this.key, this.isCreatePin = true, this.title}); + + final _i34.Key? key; + + final bool isCreatePin; + + final String? title; + + @override + String toString() { + return 'PinRouteArgs{key: $key, isCreatePin: $isCreatePin, title: $title}'; + } +} + +/// generated route for +/// [_i24.PoinHistoryPage] +class PoinHistoryRoute extends _i33.PageRouteInfo { + const PoinHistoryRoute({List<_i33.PageRouteInfo>? children}) + : super(PoinHistoryRoute.name, initialChildren: children); + + static const String name = 'PoinHistoryRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i24.PoinHistoryPage(); + }, + ); +} + +/// generated route for +/// [_i25.PoinPage] +class PoinRoute extends _i33.PageRouteInfo { + const PoinRoute({List<_i33.PageRouteInfo>? children}) + : super(PoinRoute.name, initialChildren: children); + + static const String name = 'PoinRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i25.PoinPage(); + }, + ); +} + +/// generated route for +/// [_i26.ProductRedeemPage] +class ProductRedeemRoute extends _i33.PageRouteInfo { + ProductRedeemRoute({ + _i34.Key? key, + required _i25.Product product, + required _i25.PointCard pointCard, + List<_i33.PageRouteInfo>? children, + }) : super( + ProductRedeemRoute.name, + args: ProductRedeemRouteArgs( + key: key, + product: product, + pointCard: pointCard, + ), + initialChildren: children, + ); + + static const String name = 'ProductRedeemRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i26.ProductRedeemPage( + key: args.key, + product: args.product, + pointCard: args.pointCard, + ); + }, + ); +} + +class ProductRedeemRouteArgs { + const ProductRedeemRouteArgs({ + this.key, + required this.product, + required this.pointCard, + }); + + final _i34.Key? key; + + final _i25.Product product; + + final _i25.PointCard pointCard; + + @override + String toString() { + return 'ProductRedeemRouteArgs{key: $key, product: $product, pointCard: $pointCard}'; + } +} + +/// generated route for +/// [_i27.ProfilePage] +class ProfileRoute extends _i33.PageRouteInfo { + const ProfileRoute({List<_i33.PageRouteInfo>? children}) + : super(ProfileRoute.name, initialChildren: children); + + static const String name = 'ProfileRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return _i33.WrappedRoute(child: const _i27.ProfilePage()); + }, + ); +} + +/// generated route for +/// [_i28.RegisterPage] +class RegisterRoute extends _i33.PageRouteInfo { + RegisterRoute({ + _i34.Key? key, + required String phoneNumber, + List<_i33.PageRouteInfo>? children, + }) : super( + RegisterRoute.name, + args: RegisterRouteArgs(key: key, phoneNumber: phoneNumber), + initialChildren: children, + ); + + static const String name = 'RegisterRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i33.WrappedRoute( + child: _i28.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), + ); + }, + ); +} + +class RegisterRouteArgs { + const RegisterRouteArgs({this.key, required this.phoneNumber}); + + final _i34.Key? key; + + final String phoneNumber; + + @override + String toString() { + return 'RegisterRouteArgs{key: $key, phoneNumber: $phoneNumber}'; + } +} + +/// generated route for +/// [_i29.RewardPage] +class RewardRoute extends _i33.PageRouteInfo { + const RewardRoute({List<_i33.PageRouteInfo>? children}) + : super(RewardRoute.name, initialChildren: children); + + static const String name = 'RewardRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i29.RewardPage(); + }, + ); +} + +/// generated route for +/// [_i30.SplashPage] +class SplashRoute extends _i33.PageRouteInfo { + const SplashRoute({List<_i33.PageRouteInfo>? children}) + : super(SplashRoute.name, initialChildren: children); + + static const String name = 'SplashRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i30.SplashPage(); + }, + ); +} + +/// generated route for +/// [_i31.VoucherDetailPage] +class VoucherDetailRoute extends _i33.PageRouteInfo { + const VoucherDetailRoute({List<_i33.PageRouteInfo>? children}) + : super(VoucherDetailRoute.name, initialChildren: children); + + static const String name = 'VoucherDetailRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i31.VoucherDetailPage(); + }, + ); +} + +/// generated route for +/// [_i32.VoucherPage] +class VoucherRoute extends _i33.PageRouteInfo { + const VoucherRoute({List<_i33.PageRouteInfo>? children}) + : super(VoucherRoute.name, initialChildren: children); + + static const String name = 'VoucherRoute'; + + static _i33.PageInfo page = _i33.PageInfo( + name, + builder: (data) { + return const _i32.VoucherPage(); + }, + ); +} diff --git a/lib/presentation/router/app_router_observer.dart b/lib/presentation/router/app_router_observer.dart new file mode 100644 index 0000000..e3707f4 --- /dev/null +++ b/lib/presentation/router/app_router_observer.dart @@ -0,0 +1,21 @@ +import 'dart:developer'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +class AppRouteObserver extends AutoRouterObserver { + @override + void didPush(Route route, Route? previousRoute) { + log('New route pushed: ${route.settings.name}'); + } + + @override + void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) { + log('Tab route visited: ${route.name}'); + } + + @override + void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) { + log('Tab route re-visited: ${route.name}'); + } +} diff --git a/lib/sample/sample_data.dart b/lib/sample/sample_data.dart new file mode 100644 index 0000000..bba889e --- /dev/null +++ b/lib/sample/sample_data.dart @@ -0,0 +1,54 @@ +import '../presentation/components/assets/assets.gen.dart'; + +class MerchantModel { + final String id; + final String name; + final String category; + final double rating; + final bool isOpen; + final String imageUrl; + + MerchantModel({ + required this.id, + required this.name, + required this.category, + required this.rating, + required this.isOpen, + required this.imageUrl, + }); +} + +List merchants = [ + MerchantModel( + id: 'hsjdhaj12', + name: 'Bakso 343', + category: 'Restaurant', + rating: 5, + isOpen: true, + imageUrl: Assets.images.bakso343.path, + ), + MerchantModel( + id: 'ahjs7812', + name: 'Tumulu Coffee', + category: 'Coffe Shop', + rating: 5, + isOpen: true, + imageUrl: Assets.images.tumulu.path, + ), + MerchantModel( + id: 'ahjs7812', + name: 'Ramenmulu', + category: 'Ramen', + rating: 5, + isOpen: true, + imageUrl: Assets.images.ramenmulu.path, + ), + MerchantModel( + id: '178271bjas', + name: 'Woku Pedas', + category: 'Restaurant', + rating: 5, + isOpen: true, + imageUrl: Assets.images.woku.path, + ), +]; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 91ed861..1b34b4c 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "enaklo") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.enaklo") +set(APPLICATION_ID "com.appskel.enaklo") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..cc10c4d 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,14 @@ #include "generated_plugin_registrant.h" +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..8e2a190 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..cd7dfe0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,18 @@ import FlutterMacOS import Foundation +import audioplayers_darwin +import connectivity_plus +import path_provider_foundation +import shared_preferences_foundation +import sqflite_darwin +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..29c8eb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 162a045..fc5d148 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -385,7 +385,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/enaklo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/enaklo"; @@ -399,7 +399,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/enaklo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/enaklo"; @@ -413,7 +413,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/enaklo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/enaklo"; diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 2b56ebf..40318f8 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = enaklo // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.enaklo +PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/pubspec.lock b/pubspec.lock index eaa659f..7ee8410 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,46 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.dev" + source: hosted + version: "7.7.1" + another_flushbar: + dependency: "direct main" + description: + name: another_flushbar + sha256: "2b99671c010a7d5770acf5cb24c9f508b919c3a7948b6af9646e773e7da7b757" + url: "https://pub.dev" + source: hosted + version: "1.12.32" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -9,6 +49,94 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" + url: "https://pub.dev" + source: hosted + version: "6.5.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7" + url: "https://pub.dev" + source: hosted + version: "9.3.0+1" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46 + url: "https://pub.dev" + source: hosted + version: "9.3.1" + awesome_dio_interceptor: + dependency: "direct dev" + description: + name: awesome_dio_interceptor + sha256: "4aef4adfdd9d8fda159870277b898a97986c6624baaf42f8a986d3130860d007" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" + url: "https://pub.dev" + source: hosted + version: "9.0.0" boolean_selector: dependency: transitive description: @@ -17,6 +145,102 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb + url: "https://pub.dev" + source: hosted + version: "8.11.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: bcc61735345c9ab5cb81073896579e735f81e35fd588907a393143ea986be8ff + url: "https://pub.dev" + source: hosted + version: "5.1.1" characters: dependency: transitive description: @@ -25,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -33,6 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" collection: dependency: transitive description: @@ -41,6 +289,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + color: + dependency: transitive + description: + name: color + sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + colorize: + dependency: transitive + description: + name: colorize + sha256: "584746cd6ba1cba0633b6720f494fe6f9601c4170f0666c1579d2aa2a61071ba" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -49,6 +345,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + data_channel: + dependency: "direct main" + description: + name: data_channel + sha256: "9fd800a76e95031c9887acf88091f4a9188c9812e15d1f1c15d759d3fea16160" + url: "https://pub.dev" + source: hosted + version: "2.0.0+1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" fake_async: dependency: transitive description: @@ -57,11 +409,75 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + sha256: eda54fdc5de08e7eeea663eb8442aafc8660b5a13fda4e0c9e572c64e50195fb + url: "https://pub.dev" + source: hosted + version: "5.11.0" + flutter_gen_runner: + dependency: "direct dev" + description: + name: flutter_gen_runner + sha256: "669bf8b7a9b4acbdcb7fcc5e12bf638aca19acedf43341714cbca3bf3a219521" + url: "https://pub.dev" + source: hosted + version: "5.11.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -70,11 +486,184 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" + url: "https://pub.dev" + source: hosted + version: "5.2.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b + url: "https://pub.dev" + source: hosted + version: "8.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hashcodes: + dependency: transitive + description: + name: hashcodes + sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" + image_size_getter: + dependency: transitive + description: + name: image_size_getter + sha256: "7c26937e0ae341ca558b7556591fd0cc456fcc454583b7cb665d2f03e93e590f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + injectable: + dependency: "direct main" + description: + name: injectable + sha256: "1b86fab6a98c11a97e5c718afb00e628d47d183c2a2256392e995a4c561141c1" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + sha256: b04673a4c88b3a848c0c77bf58b8309f9b9e064d9fe1df5450c8ee1675eaea1a + url: "https://pub.dev" + source: hosted + version: "2.7.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + url: "https://pub.dev" + source: hosted + version: "6.9.5" leak_tracker: dependency: transitive description: @@ -107,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,19 +728,291 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" - path: + mime: dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + url: "https://pub.dev" + source: hosted + version: "2.2.18" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 + url: "https://pub.dev" + source: hosted + version: "2.4.12" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + url: "https://pub.dev" + source: hosted + version: "1.3.7" source_span: dependency: transitive description: @@ -152,6 +1021,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -168,6 +1085,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -176,6 +1101,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -192,6 +1125,126 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + time: + dependency: transitive + description: + name: time + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" + url: "https://pub.dev" + source: hosted + version: "6.3.18" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -208,6 +1261,62 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.8.1 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1ac0106..7afb99d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,89 +1,81 @@ name: enaklo description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. +publish_to: "none" + version: 1.0.0+1 environment: sdk: ^3.8.1 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + auto_route: ^9.3.0 + get_it: ^8.0.3 + injectable: ^2.5.0 + dio: ^5.8.0+1 + connectivity_plus: ^6.1.4 + data_channel: ^2.0.0+1 + path: ^1.9.1 + path_provider: ^2.1.5 + dartz: ^0.10.1 + intl: ^0.20.2 + flutter_svg: ^2.2.0 + freezed_annotation: ^2.4.1 + json_annotation: ^4.9.0 + shared_preferences: ^2.5.3 + carousel_slider: ^5.1.1 + url_launcher: ^6.3.2 + cached_network_image: ^3.4.1 + shimmer: ^3.0.0 + audioplayers: ^6.5.1 + flutter_bloc: ^9.1.1 + bloc: ^9.0.0 + another_flushbar: ^1.12.32 + flutter_spinkit: ^5.2.2 dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 + auto_route_generator: ^9.3.0 + build_runner: ^2.4.6 + freezed: ^2.4.5 + awesome_dio_interceptor: ^1.3.0 + injectable_generator: ^2.5.0 + flutter_gen_runner: ^5.11.0 + flutter_launcher_icons: ^0.14.4 + json_serializable: ^6.9.5 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. + generate: true uses-material-design: true + assets: + - assets/images/ + - assets/icons/ + - assets/fonts/ + - assets/json/ + - assets/audio/ - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + fonts: + - family: Quicksand + fonts: + - asset: assets/fonts/quicksand/Quicksand-Light.ttf + weight: 300 + - asset: assets/fonts/quicksand/Quicksand-Regular.ttf + weight: 400 + - asset: assets/fonts/quicksand/Quicksand-Medium.ttf + weight: 500 + - asset: assets/fonts/quicksand/Quicksand-SemiBold.ttf + weight: 600 + - asset: assets/fonts/quicksand/Quicksand-Bold.ttf + weight: 700 - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package +flutter_gen: + output: lib/presentation/components/assets/ + integrations: + flutter_svg: true diff --git a/test/widget_test.dart b/test/widget_test.dart index 00c5de8..9322cd7 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,12 +8,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:enaklo/main.dart'; - void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + // await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..8f8f4ef 100644 Binary files a/web/favicon.png and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..bddf826 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..f0145dc 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d7..bddf826 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c566..f0145dc 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ diff --git a/web/manifest.json b/web/manifest.json index ec8155f..58c717b 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -32,4 +32,4 @@ "purpose": "maskable" } ] -} +} \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..d836cf4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,15 @@ #include "generated_plugin_registrant.h" +#include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..5ac8838 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows + connectivity_plus + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..5bbe59e 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ