diff --git a/android/app/build.gradle b/android/app/build.gradle index f359571..86d39d5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,9 +24,10 @@ if (flutterVersionName == null) { apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +apply plugin: 'com.google.gms.google-services' android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,10 +40,11 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "us.minipos.flutter_wisetronic" - minSdkVersion 16 - targetSdkVersion 29 + minSdkVersion 19 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { @@ -60,4 +62,6 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:multidex:1.0.3' + implementation 'com.google.firebase:firebase-messaging:21.0.1' } diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..b0c44ec --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "972052001940", + "project_id": "wisetronicsite", + "storage_bucket": "wisetronicsite.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:972052001940:android:6c418edf0744da8d5c3081", + "android_client_info": { + "package_name": "us.minipos.flutter_wisetronic" + } + }, + "oauth_client": [ + { + "client_id": "972052001940-95is9u4av26k6qag4aaq6fgqi716s1ap.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCZoTVIxTKDaqoZz-aM0uVWKNMvc5nco0s" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "972052001940-95is9u4av26k6qag4aaq6fgqi716s1ap.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ca3b311..1eba08e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,20 @@ + + + android:icon="@mipmap/launcher_icon"> + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/Application.kt b/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/Application.kt new file mode 100644 index 0000000..bb0c919 --- /dev/null +++ b/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/Application.kt @@ -0,0 +1,18 @@ +package us.minipos.flutter_wisetronic + +import io.flutter.app.FlutterApplication +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin +import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService + +class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback { + override fun onCreate() { + super.onCreate() + FlutterFirebaseMessagingService.setPluginRegistrant(this) + } + + override fun registerWith(registry: PluginRegistry?) { +// GeneratedPluginRegistrant.registerWith(registry) + FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")); + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/MainActivity.kt b/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/MainActivity.kt index d1ff66c..6ce6980 100644 --- a/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/MainActivity.kt +++ b/android/app/src/main/kotlin/us/minipos/flutter_wisetronic/MainActivity.kt @@ -1,6 +1,12 @@ package us.minipos.flutter_wisetronic +import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine); + } } diff --git a/android/build.gradle b/android/build.gradle index 3100ad2..246bfed 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,8 +6,9 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.5' } } diff --git a/assets/images/apple_pay.png b/assets/images/apple_pay.png new file mode 100644 index 0000000..bf1add3 Binary files /dev/null and b/assets/images/apple_pay.png differ diff --git a/assets/images/denied.png b/assets/images/denied.png new file mode 100644 index 0000000..9a5fcc8 Binary files /dev/null and b/assets/images/denied.png differ diff --git a/assets/images/google_pay.png b/assets/images/google_pay.png new file mode 100644 index 0000000..1b013d9 Binary files /dev/null and b/assets/images/google_pay.png differ diff --git a/assets/images/group_number.png b/assets/images/group_number.png new file mode 100644 index 0000000..bd33b65 Binary files /dev/null and b/assets/images/group_number.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png new file mode 100644 index 0000000..b9752ec Binary files /dev/null and b/assets/images/icon.png differ diff --git a/assets/images/master.png b/assets/images/master.png new file mode 100644 index 0000000..e64587d Binary files /dev/null and b/assets/images/master.png differ diff --git a/assets/images/under_renovation.png b/assets/images/under_renovation.png new file mode 100644 index 0000000..7d9baba Binary files /dev/null and b/assets/images/under_renovation.png differ diff --git a/assets/images/visa.png b/assets/images/visa.png new file mode 100644 index 0000000..7d60b92 Binary files /dev/null and b/assets/images/visa.png differ diff --git a/assets/tessdata/OCRB.traineddata b/assets/tessdata/OCRB.traineddata new file mode 100644 index 0000000..842bd20 Binary files /dev/null and b/assets/tessdata/OCRB.traineddata differ diff --git a/assets/tessdata/chi_sim.traineddata b/assets/tessdata/chi_sim.traineddata new file mode 100644 index 0000000..eeb66cf Binary files /dev/null and b/assets/tessdata/chi_sim.traineddata differ diff --git a/assets/tessdata/chi_sim_vert.traineddata b/assets/tessdata/chi_sim_vert.traineddata new file mode 100644 index 0000000..851996c Binary files /dev/null and b/assets/tessdata/chi_sim_vert.traineddata differ diff --git a/assets/tessdata/chi_tra.traineddata b/assets/tessdata/chi_tra.traineddata new file mode 100644 index 0000000..5f1fe27 Binary files /dev/null and b/assets/tessdata/chi_tra.traineddata differ diff --git a/assets/tessdata/chi_tra_vert.traineddata b/assets/tessdata/chi_tra_vert.traineddata new file mode 100644 index 0000000..a794935 Binary files /dev/null and b/assets/tessdata/chi_tra_vert.traineddata differ diff --git a/assets/tessdata/eng.traineddata b/assets/tessdata/eng.traineddata new file mode 100644 index 0000000..f4744c2 Binary files /dev/null and b/assets/tessdata/eng.traineddata differ diff --git a/assets/tessdata/financial.traineddata b/assets/tessdata/financial.traineddata new file mode 100644 index 0000000..71b43ce Binary files /dev/null and b/assets/tessdata/financial.traineddata differ diff --git a/assets/tessdata/pdf.ttf b/assets/tessdata/pdf.ttf new file mode 100644 index 0000000..d1472b2 Binary files /dev/null and b/assets/tessdata/pdf.ttf differ diff --git a/assets/tessdata_config.json b/assets/tessdata_config.json new file mode 100644 index 0000000..997e8dc --- /dev/null +++ b/assets/tessdata_config.json @@ -0,0 +1,12 @@ +{ + "files": [ + "eng.traineddata", + "OCRB.traineddata", + "pdf.ttf", + "financial.traineddata", + "chi_sim.traineddata", + "chi_sim_vert.traineddata", + "chi_tra.traineddata", + "chi_tra_vert.traineddata" + ] +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b01b47e --- /dev/null +++ b/build.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +function build_all() { + echo "1. Build Web" + flutter build web --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true --web-renderer html + echo "2.1 Build Android APK" + flutter build apk --release --verbose + echo "2.2 Build Android APK" + flutter build apk --split-per-abi + echo "2.3 Build Android AppBundle" + flutter build appbundle + echo "3. Build iOS" + flutter build ios --release + echo "All done!" + exit +} + +function build_web() { + echo "<<< Build Web >>>" + flutter build web --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true --web-renderer html + echo "Done!" + exit +} + +function build_android() { + echo "1 Build Android APK" + flutter build apk --release --verbose + echo "2 Build Android APK" + flutter build apk --split-per-abi + echo "3 Build Android AppBundle" + flutter build appbundle + echo "Done!" + exit +} + +function build_ios() { + echo "<<< Build iOS >>>" + flutter build ios --release + echo "Done!" +} + +PS3='Please choice: ' +options=("Build All" "Build Web" "Build Android" "Build iOS") +select opt in "${options[@]}"; do + case $opt in + "Build All") + build_all + ;; + "Build Web") + build_web + ;; + "Build Android") + build_android + ;; + "Build iOS") + build_ios + ;; + *) echo "Invalid option $REPLY";; + esac +done \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index e8efba1..b2f5fae 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #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 399e934..88c2914 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e0a0df6..d3d7377 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -492,4 +492,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} +} \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..c36168d 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -7,6 +7,7 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + GMSServices.provideAPIKey("AIzaSyA8BezbDC1M9Em7uzEIW4NCxXq0JZNL4ko") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } 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..72fd2b8 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 28c6bf0..d13c00f 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 2ccbfd9..3a2d75e 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 f091b6b..85a356f 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 4cde121..0efd89b 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 d0ef06e..0fae04c 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 dcdc230..e94e903 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 2ccbfd9..3a2d75e 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 c8f9ed8..2eb2c3d 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 a6d6b86..916018a 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-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b86..916018a 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 75b2d16..a8267dc 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-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d..7dd20c7 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 6a84f41..d95b6dc 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 d0e1f58..62db198 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/ios/Runner/Info.plist b/ios/Runner/Info.plist index c13be3c..567ce94 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,5 +41,20 @@ UIViewControllerBasedStatusBarAppearance + io.flutter.embedded_views_preview + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + 3ds.stripesdk.io + CFBundleURLSchemes + + stripesdk + + + diff --git a/lib/constants.dart b/lib/constants.dart index 010e385..b0deeb1 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1,5 +1,8 @@ class Constants { + + static const bool ENABLE_NATIVE_PAY = false; + static const int FONT_GOOGLE = 0xe800; static const int FONT_ALEXA = 0xe801; static const int FONT_APPLE = 0xe802; @@ -16,8 +19,49 @@ class Constants { static const int FONT_TOPTONS = 0xe813; static const bool DEBUG = false; - static const BASE_API_URL = 'https://api.minipos.us/'; + static const int BUSINESS_ID = 310; //310, 680, 357(notjust), 651(kiosk); + static const String APP_TITLE = 'MiniPOS Ponit of Sale System'; + static const String BASE_API_URL = 'https://api.minipos.us/'; //'https://sandbox.minipos.us/', 'https://api.minipos.us/' + static const String TUTORIAL_URL = 'https://archive.wisetronic.com/wisetronic-products-documentation/'; static const String API_SECRET = 'pei326sami1223HellowWorldabcdEd'; static const KEY_USER_ID = 'user_id'; static const KEY_ACCESS_TOKEN = 'access_token'; + static const KEY_CARTINFOS = "cart_infos"; + static const ORDERS_PER_PAGE = 10; + + static const KEY_LAST_VISIT = 'last-visits'; + + static const int TICKET_PER_PAGE_MOBILE = 20; + static const int TICKET_PER_PAGE_DESKTOP = 40; + static const int BLOG_PER_PAGE_MOBILE = 20; + static const int BLOG_PER_PAGE_DESKTOP = 30; + + static const double BREADCRUMB_HEIGHT = 38; + + static const int HOT_SALE_ID = -1; + static const int FEATURED_PRODUCT_ID = -2; + + static const String PAYMENT_METHOD_PAY_ON_DELIVERY = 'POD'; + static const String PAYMENT_METHOD_CODE_CHASE = 'chase'; + static const String PAYMENT_METHOD_CODE_SQUARE = 'web-credit-card'; + static const String PAYMENT_METHOD_CODE_OTT_ALIPAY = 'ALIPAY'; + static const String PAYMENT_METHOD_CODE_OTT_WECHATPAY = 'WECHATPAY'; + static const String PAYMENT_METHOD_CODE_PAYPAL = 'paypal'; + static const String PAYMENT_METHOD_CODE_STRIPE = 'stripe'; + static const String PAYMENT_METHOD_CODE_POD = 'payondeliverypickup'; + static const String PAYMENT_METHOD_NATIVE_PAY = 'native_pay'; + + static const String STRIPE_STATUS_REQUIRES_CONFIRMATION = 'requires_confirmation'; + static const String STRIPE_CLIENT_SECRET = 'client_secret'; + static const String STRIPE_STATUS_SUCCEDED = 'succeeded'; + + static const int STATUS_PENDING = 0; + static const int STATUS_ACCEPT = 1; + static const int STATUS_PROCESSING = 2; + static const int STATUS_COMPLETE = 5; + static const int STATUS_CANCELLED = -1; + + static const String PAYMENT_STATUS_UNPAID = 'UNPAID'; + static const String PAYMENT_STATUS_PAID = 'PAID'; + static const String PAYMENT_STATUS_PARTIALPAID = 'PARTIALPAID'; } \ No newline at end of file diff --git a/lib/dialog/image_viewer.dart b/lib/dialog/image_viewer.dart new file mode 100644 index 0000000..a745584 --- /dev/null +++ b/lib/dialog/image_viewer.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; + +class ImageViewer extends StatefulWidget { + final Key key; + final ImageProvider imageProvider; + + ImageViewer(this.imageProvider, {this.key}) : + super(key: key); + + @override + State createState() { + return ImageViewState(); + } + +} + +class ImageViewState extends State { + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width - 100.0, + height: MediaQuery.of(context).size.height - 120.0, + child: PhotoView( + imageProvider: widget.imageProvider, + ), + ); + } + +} \ No newline at end of file diff --git a/lib/dialog/logout_dialog.dart b/lib/dialog/logout_dialog.dart new file mode 100644 index 0000000..c546594 --- /dev/null +++ b/lib/dialog/logout_dialog.dart @@ -0,0 +1,52 @@ + +import 'package:flutter/material.dart'; + +import '../constants.dart'; +import '../events/eventbus.dart'; +import '../events/events.dart'; +import '../generated/l10n.dart'; +import '../routes.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/utils.dart'; + +AlertDialog logoutDialog(BuildContext context) { + return AlertDialog( + title: Text(S + .of(context) + .warning), + content: Text(S + .of(context) + .are_you_sure_to_logout), + actions: [ + FlatButton( + child: Text(S + .of(context) + .cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S + .of(context) + .yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + Utils.getBox().then((box) { + box.delete(Constants.KEY_USER_ID); + box.delete( + Constants.KEY_ACCESS_TOKEN); + store.dispatch( + new UpdateCurrentUser(null)); + eventBus.fire( + new OnCurrentUserUpdated()); + Routes.router.navigateTo( + context, '/', replace: true, + clearStack: true); + }); + }, + ) + ], + ); +} \ No newline at end of file diff --git a/lib/events/events.dart b/lib/events/events.dart index 1bd8007..3241f25 100644 --- a/lib/events/events.dart +++ b/lib/events/events.dart @@ -1,4 +1,123 @@ +import 'package:flutter/material.dart'; + +import '../models/business.dart'; +import '../models/comment.dart'; +import '../models/position.dart'; +import '../models/product.dart'; + class OpenDrawer { +} + +class OnGetCurrentUserFailed { + dynamic error; + OnGetCurrentUserFailed(this.error); +} + +class OnCurrentUserUpdated { + OnCurrentUserUpdated(); +} + +class OnProgressEvent { + bool showProgress; + double progress; + OnProgressEvent(this.showProgress, this.progress); +} + +class OnUploadCommentImageProgressEvent { + bool showProgress; + double progress; + OnUploadCommentImageProgressEvent(this.showProgress, this.progress); +} + +class OnCommentUpdatedEvent { + Comment comment; + OnCommentUpdatedEvent(this.comment); +} + +class OnAddressesUpdated { + OnAddressesUpdated(); +} + +class OnTicketsUpdated { + OnTicketsUpdated(); +} + +class OnUpdateLocatedAddressSuccess { + OnUpdateLocatedAddressSuccess(); +} + +class OnGotDeepLinkUri { + Uri uri; + OnGotDeepLinkUri(this.uri); +} + +class OnUploadImageProgressEvent { + int id; + bool showProgress; + double progress; + OnUploadImageProgressEvent(this.id, this.showProgress, this.progress); +} + +class OnSubmitProgressEvent { + bool showProgress; + double progress; + OnSubmitProgressEvent(this.showProgress, this.progress); +} + +class OnCartInfoUpdated { + OnCartInfoUpdated(); +} + +class OnAttributePageChanged { + int index; + OnAttributePageChanged(this.index); +} + +class OnAttributeSelectionsChanged { + Map selections; + OnAttributeSelectionsChanged(this.selections); +} + +class OnProductWillAddToCart { + Product product; + Map selections; + double price; + String description; + Business business; + GlobalKey buttonKey; + OnProductWillAddToCart(this.product, this.selections, this.price, this.description, this.business, {this.buttonKey}); +} + +class OnProductWillRemoveFromCart { + Product product; + int productListIndex; + Business business; + OnProductWillRemoveFromCart(this.product, this.productListIndex, this.business); +} + +class OnOrderUpdated { + OnOrderUpdated(); +} + +class SubscribeToTopic { + String topic; + SubscribeToTopic(this.topic); +} + +class UnSubscribeToTopic { + String topic; + UnSubscribeToTopic(this.topic); +} + +class GetCurrentPositionSuccess { + Position position; + bool getAddressFromServer = true; + GetCurrentPositionSuccess(this.position, this.getAddressFromServer); +} + +class GetCurrentPositionFailed { + Exception exception; + GetCurrentPositionFailed(this.exception); } \ No newline at end of file diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 04bcfa0..d937362 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -19,41 +19,465 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'en'; - static m0(oss) => "Download at ${oss}"; + static m0(qty) => "and ${qty} items more"; + + static m1(value) => "Available for order over \$${value}"; + + static m2(hour) => "Order now and delivery start at ${hour}:00."; + + static m3(optionName) => "Multiple choice ${optionName}"; + + static m4(km, time) => "The delivery guy are ${km} away from the customer, and it will take about ${time} to deliver."; + + static m5(shippingfee) => "Delivery fee ${shippingfee}+"; + + static m6(length, width, height) => "Dimentions: ${length}(L)x${width}(W)x${height}(H)"; + + static m7(discount) => "-\$${discount}"; + + static m8(oss) => "Download at ${oss}"; + + static m9(expirationDate) => "Expires on ${expirationDate}"; + + static m10(mon, yer) => "Exp: ${mon}/${yer}"; + + static m11(name, rate) => "${name}(${rate}%)"; + + static m12(num) => "${num} follow-ups"; + + static m13(second) => "Retry after ${second}s"; + + static m14(hours) => "${Intl.plural(hours, one: '1 hr', other: '${hours} hrs')}"; + + static m15(minamount) => "\$${minamount}+"; + + static m16(shipfee) => "Delivery \$${shipfee}+"; + + static m17(minutes) => "${Intl.plural(minutes, one: '1 min', other: '${minutes} mins')}"; + + static m18(minprice) => "${minprice} more"; + + static m19(amount) => "Pay \$${amount} now"; + + static m20(method) => "Pay with ${method}"; + + static m21(mobile) => "Payment verification code has been sent to your mobile phone ${mobile}. Please enter the verification code below."; + + static m22(discount) => "${discount}%off"; + + static m23(amount, discount) => "-\$${amount}(${discount}%off)"; + + static m24(optionName) => "Please choice ${optionName}."; + + static m25(sold_qty) => " ${sold_qty} sold/mo"; + + static m26(subtotal) => "Subtotal: ${subtotal}"; + + static m27(num) => "Table#: ${num}"; + + static m28(num) => "Ticket #${num}"; + + static m29(time) => "Today ${time}"; + + static m30(time) => "Tomorrow ${time}"; + + static m31(weight) => "Weight: ${weight}"; final messages = _notInlinedMessages(_notInlinedMessages); static _notInlinedMessages(_) => { "about" : MessageLookupByLibrary.simpleMessage("About"), "about_us" : MessageLookupByLibrary.simpleMessage("About us"), + "account_binding" : MessageLookupByLibrary.simpleMessage("Account binding"), + "add_credit_card" : MessageLookupByLibrary.simpleMessage("Add credit card"), + "add_new_address" : MessageLookupByLibrary.simpleMessage("Add new address"), + "add_new_ticket" : MessageLookupByLibrary.simpleMessage("Create a new ticket"), + "add_new_ticket_desc" : MessageLookupByLibrary.simpleMessage("Please enter your question."), + "add_pictures" : MessageLookupByLibrary.simpleMessage("Add pictures"), + "add_to_basket" : MessageLookupByLibrary.simpleMessage("Add to basket"), + "address" : MessageLookupByLibrary.simpleMessage("Address"), + "after_renewed" : MessageLookupByLibrary.simpleMessage("After renewal"), + "alipay" : MessageLookupByLibrary.simpleMessage("Alipay"), + "amount_not_meet" : MessageLookupByLibrary.simpleMessage("The order amount does not meet the minimum requirements."), + "and_more_item_token" : m0, + "are_you_sure_to_cancel_the_order" : MessageLookupByLibrary.simpleMessage("Are you sure you want to cancel the order?"), + "are_you_sure_to_delete_the_address" : MessageLookupByLibrary.simpleMessage("Are you sure you want to delete the address?"), + "are_you_sure_to_empty_basket" : MessageLookupByLibrary.simpleMessage("Are you sure you want to empty the basket?"), + "are_you_sure_to_logout" : MessageLookupByLibrary.simpleMessage("Are you sure you want to logout?"), + "are_you_sure_to_remove_the_card" : MessageLookupByLibrary.simpleMessage("Are you srue you want to remove the credit card?"), + "are_you_sure_to_remove_the_item" : MessageLookupByLibrary.simpleMessage("Are you sure you want to remove the item?"), + "are_you_sure_to_remove_the_picture" : MessageLookupByLibrary.simpleMessage("Are you sure you want to remove the picture?"), + "attach_pictures" : MessageLookupByLibrary.simpleMessage("Attach pictures"), + "attach_pictures_desc" : MessageLookupByLibrary.simpleMessage("Upload pictures(screen shot) to make your question clearer."), + "available_for_order_over_token" : m1, + "avatar" : MessageLookupByLibrary.simpleMessage("Avatar"), + "back" : MessageLookupByLibrary.simpleMessage("Back"), + "basic_info" : MessageLookupByLibrary.simpleMessage("Basic info."), "blog" : MessageLookupByLibrary.simpleMessage("Blog"), + "book_now_delivery_later_token" : m2, + "business_card" : MessageLookupByLibrary.simpleMessage("Business card"), + "business_cooperation" : MessageLookupByLibrary.simpleMessage("Business cooperation"), + "by_email" : MessageLookupByLibrary.simpleMessage("By Emails"), + "by_phone" : MessageLookupByLibrary.simpleMessage("By phone"), + "camera" : MessageLookupByLibrary.simpleMessage("Camera"), + "canada_post" : MessageLookupByLibrary.simpleMessage("Canada Post"), + "canada_post_delivery" : MessageLookupByLibrary.simpleMessage("Canada Post delivery"), + "cancel" : MessageLookupByLibrary.simpleMessage("Cancel"), + "cancel_order" : MessageLookupByLibrary.simpleMessage("Cancel order"), + "change_email" : MessageLookupByLibrary.simpleMessage("Change email address"), + "change_email_desc" : MessageLookupByLibrary.simpleMessage("Enter your new Email, and then click \'Get code\' button to get the validation code."), + "change_mobile" : MessageLookupByLibrary.simpleMessage("Change mobile number"), + "change_mobile_desc" : MessageLookupByLibrary.simpleMessage("Enter your new mobile number, and then click \'Get code\' button to get the validation code."), + "change_nickname" : MessageLookupByLibrary.simpleMessage("Change nickname"), + "change_password" : MessageLookupByLibrary.simpleMessage("Change password"), + "change_password_desc" : MessageLookupByLibrary.simpleMessage("Please enter the old password and your desired new password."), + "check_option_is_optional" : MessageLookupByLibrary.simpleMessage("Optional. You can tap Next without selection."), + "check_option_is_required" : MessageLookupByLibrary.simpleMessage("Option is required. Select at lease an option then tap Next."), + "check_option_select_token" : m3, + "checkout" : MessageLookupByLibrary.simpleMessage("Check out"), + "checkout_no_deliver" : MessageLookupByLibrary.simpleMessage("We don\'t deliver at this time."), + "chinese_simplified" : MessageLookupByLibrary.simpleMessage("Chinese simplified"), + "chinese_traditional" : MessageLookupByLibrary.simpleMessage("Chinese tranditional"), + "choose_a_shipping_rate" : MessageLookupByLibrary.simpleMessage("Choose a shipping rate"), + "city" : MessageLookupByLibrary.simpleMessage("City"), + "city_is_required" : MessageLookupByLibrary.simpleMessage("City is required"), + "close" : MessageLookupByLibrary.simpleMessage("Close"), + "closed" : MessageLookupByLibrary.simpleMessage("Closed"), + "comment" : MessageLookupByLibrary.simpleMessage("Comment"), + "comment_empty" : MessageLookupByLibrary.simpleMessage("Please type your comment."), + "comments" : MessageLookupByLibrary.simpleMessage("Comments"), + "confirm_order" : MessageLookupByLibrary.simpleMessage("Order confirmation"), + "confirmation" : MessageLookupByLibrary.simpleMessage("Confirmation"), + "contact_name" : MessageLookupByLibrary.simpleMessage("Contact name"), + "contact_name_is_required" : MessageLookupByLibrary.simpleMessage("Contact name is required"), "contact_us" : MessageLookupByLibrary.simpleMessage("Contact us"), + "copy" : MessageLookupByLibrary.simpleMessage("Copy"), + "coupons" : MessageLookupByLibrary.simpleMessage("Coupons"), + "credit_card" : MessageLookupByLibrary.simpleMessage("Credit card"), + "credit_coupon" : MessageLookupByLibrary.simpleMessage("Credit/Coupon"), + "credit_debit_card" : MessageLookupByLibrary.simpleMessage("Credit or debit card"), + "current_password_is_required" : MessageLookupByLibrary.simpleMessage("Current password is required."), + "current_plan" : MessageLookupByLibrary.simpleMessage("Current plan"), + "customer" : MessageLookupByLibrary.simpleMessage("Customer"), + "delete" : MessageLookupByLibrary.simpleMessage("Delete"), + "delivery" : MessageLookupByLibrary.simpleMessage("Delivery"), + "delivery_address" : MessageLookupByLibrary.simpleMessage("Delivery address"), + "delivery_distance_token" : m4, + "delivery_fee" : m5, + "delivery_guy" : MessageLookupByLibrary.simpleMessage("Delivery guy"), + "delivery_info" : MessageLookupByLibrary.simpleMessage("Delivery Info."), + "delivery_method" : MessageLookupByLibrary.simpleMessage("Delivery method"), + "delivery_now" : MessageLookupByLibrary.simpleMessage("Delivery ASAP"), + "delivery_unavailable" : MessageLookupByLibrary.simpleMessage("Delivery time"), + "detail" : MessageLookupByLibrary.simpleMessage("Detail"), "developer_of" : MessageLookupByLibrary.simpleMessage("Developers of"), + "dimentions_token" : m6, + "discount_amount_token" : m7, + "document_langage" : MessageLookupByLibrary.simpleMessage("Document language"), + "document_type" : MessageLookupByLibrary.simpleMessage("Document type"), + "dont_use" : MessageLookupByLibrary.simpleMessage("Do not redeem"), "download" : MessageLookupByLibrary.simpleMessage("Download"), - "download_with_token" : m0, + "download_with_token" : m8, + "downloads" : MessageLookupByLibrary.simpleMessage("Downloads"), + "edit_address" : MessageLookupByLibrary.simpleMessage("Edit address"), + "email" : MessageLookupByLibrary.simpleMessage("Email"), + "email_is_not_valid" : MessageLookupByLibrary.simpleMessage("Email is not valid"), + "email_is_required" : MessageLookupByLibrary.simpleMessage("Email is required"), + "email_needed" : MessageLookupByLibrary.simpleMessage("Email needed"), + "empty_address_change_keyword" : MessageLookupByLibrary.simpleMessage("No address found. Tap here to skip address lookup."), + "empty_basket" : MessageLookupByLibrary.simpleMessage("Empty basket"), + "empty_result_change_keyword" : MessageLookupByLibrary.simpleMessage("No data found, please change keyword and try again."), + "end_of_the_list" : MessageLookupByLibrary.simpleMessage("End of the list"), + "english" : MessageLookupByLibrary.simpleMessage("English"), + "enter_coupon_code" : MessageLookupByLibrary.simpleMessage("Enter coupon code"), + "enter_delivery_address" : MessageLookupByLibrary.simpleMessage("Enter delivery address"), + "enter_mobile_or_email" : MessageLookupByLibrary.simpleMessage("Enter mobile or email"), + "enter_new_nickname" : MessageLookupByLibrary.simpleMessage("Enter new nickname"), + "enter_product_keyword" : MessageLookupByLibrary.simpleMessage("Enter product keyword"), + "error" : MessageLookupByLibrary.simpleMessage("Error"), + "error_read_file" : MessageLookupByLibrary.simpleMessage("Error occurred while reading the file."), + "expiration_date" : MessageLookupByLibrary.simpleMessage("Expiration date"), + "expiration_date_token" : m9, + "expire_token" : m10, + "expired_at" : MessageLookupByLibrary.simpleMessage("Expired at"), + "extra_fee_token" : m11, + "fax" : MessageLookupByLibrary.simpleMessage("Fax"), + "feature_not_available_web" : MessageLookupByLibrary.simpleMessage("This feature is not available on the web. Please install the App version."), + "featured_product" : MessageLookupByLibrary.simpleMessage("Featured"), + "finish" : MessageLookupByLibrary.simpleMessage("Finish"), + "follow_ups" : MessageLookupByLibrary.simpleMessage("Follow ups"), + "followups_token" : m12, + "forgot_password" : MessageLookupByLibrary.simpleMessage("Forgot password"), + "forgot_password_description" : MessageLookupByLibrary.simpleMessage("Enter the Email or mobile number you used when registering, and then click the \'Get code\' button to get the validation code."), + "forgot_password_question" : MessageLookupByLibrary.simpleMessage("Forgot password?"), + "fri" : MessageLookupByLibrary.simpleMessage("Fri"), + "friday" : MessageLookupByLibrary.simpleMessage("Friday"), + "from_camera" : MessageLookupByLibrary.simpleMessage("From camera"), + "from_gallery" : MessageLookupByLibrary.simpleMessage("From gallery"), + "gallery" : MessageLookupByLibrary.simpleMessage("Gallery"), + "general_coupon" : MessageLookupByLibrary.simpleMessage("General coupon"), + "get_code" : MessageLookupByLibrary.simpleMessage("Get code"), + "get_code_again" : MessageLookupByLibrary.simpleMessage("Get code again"), + "get_code_token" : m13, + "get_coupon" : MessageLookupByLibrary.simpleMessage("Get coupon"), + "get_picture" : MessageLookupByLibrary.simpleMessage("Get picture"), + "get_picture_from" : MessageLookupByLibrary.simpleMessage("Get picture from..."), + "group_license_renewal" : MessageLookupByLibrary.simpleMessage("Group Lisence Renewal"), + "group_number" : MessageLookupByLibrary.simpleMessage("Group number"), + "group_number_can_be_found" : MessageLookupByLibrary.simpleMessage("Group number can be found..."), "home" : MessageLookupByLibrary.simpleMessage("Home"), + "hot_sale" : MessageLookupByLibrary.simpleMessage("Hot sale"), + "hour_token" : m14, + "igoshow" : MessageLookupByLibrary.simpleMessage("iGoShow"), + "includes" : MessageLookupByLibrary.simpleMessage("Includes"), "information" : MessageLookupByLibrary.simpleMessage("Information"), + "input_your_comment" : MessageLookupByLibrary.simpleMessage("Input your comment"), "install_in_store" : MessageLookupByLibrary.simpleMessage("Install in store"), "learn_more" : MessageLookupByLibrary.simpleMessage("Learn more..."), + "learn_more_about_igoshow" : MessageLookupByLibrary.simpleMessage("Learn more about iGoShow"), + "learn_more_about_minipos" : MessageLookupByLibrary.simpleMessage("Learn more about MiniPOS"), "license_agreement" : MessageLookupByLibrary.simpleMessage("License agreement"), + "light_tase" : MessageLookupByLibrary.simpleMessage("Light tase"), + "load_failed_retry" : MessageLookupByLibrary.simpleMessage("Load failed, please retry"), + "loading" : MessageLookupByLibrary.simpleMessage("Loading..."), "loading_please_wait" : MessageLookupByLibrary.simpleMessage("Loading, please wait..."), "login" : MessageLookupByLibrary.simpleMessage("Login"), + "login_instruction" : MessageLookupByLibrary.simpleMessage("If you have already registered an account, please use the following form to login.\nIf you are a MiniOffice user, you can directly enter your MiniOffice username and password to login."), "logout" : MessageLookupByLibrary.simpleMessage("Logout"), "main_content_1" : MessageLookupByLibrary.simpleMessage("Since 1999, we have been committed to developing a complete and powerful sales system, helping thousands of small businesses handle sales smoothly. Currently we have two main products."), + "me" : MessageLookupByLibrary.simpleMessage("Me"), + "min_order_amount_token" : m15, + "min_shipping_fee" : m16, "minipos" : MessageLookupByLibrary.simpleMessage("MiniPOS"), + "minute_token" : m17, + "mobile_email_username" : MessageLookupByLibrary.simpleMessage("Mobile, Email or MiniOffice username"), + "mobile_is_required" : MessageLookupByLibrary.simpleMessage("Mobile is required"), + "mobile_number" : MessageLookupByLibrary.simpleMessage("Mobile number"), + "mobile_or_email" : MessageLookupByLibrary.simpleMessage("Mobile or email"), + "mobile_or_email_is_required" : MessageLookupByLibrary.simpleMessage("Mobile or email is required"), + "mobile_phone_number" : MessageLookupByLibrary.simpleMessage("Mobile number"), + "mobile_phone_number_is_required" : MessageLookupByLibrary.simpleMessage("Mobile number is required"), + "mon" : MessageLookupByLibrary.simpleMessage("Mon"), + "monday" : MessageLookupByLibrary.simpleMessage("Monday"), + "mr" : MessageLookupByLibrary.simpleMessage("Mr."), + "ms" : MessageLookupByLibrary.simpleMessage("Ms."), + "my_addresses" : MessageLookupByLibrary.simpleMessage("My addresses"), + "my_cards" : MessageLookupByLibrary.simpleMessage("My cards"), + "my_favorites" : MessageLookupByLibrary.simpleMessage("My favorites"), + "my_orders" : MessageLookupByLibrary.simpleMessage("My orders"), + "my_support" : MessageLookupByLibrary.simpleMessage("My support"), "navigation" : MessageLookupByLibrary.simpleMessage("Navigation"), + "new_address" : MessageLookupByLibrary.simpleMessage("New address"), + "new_comment" : MessageLookupByLibrary.simpleMessage("New comment"), + "new_ticket" : MessageLookupByLibrary.simpleMessage("New ticket"), + "new_user_question" : MessageLookupByLibrary.simpleMessage("New user?"), + "next" : MessageLookupByLibrary.simpleMessage("Next"), + "nick_name" : MessageLookupByLibrary.simpleMessage("Nick name"), + "nickname_is_required" : MessageLookupByLibrary.simpleMessage("Nickname is required"), + "no" : MessageLookupByLibrary.simpleMessage("No"), + "no_address_yet" : MessageLookupByLibrary.simpleMessage("You have not entered any address."), + "no_blog_yet" : MessageLookupByLibrary.simpleMessage("There is no blog to show yet."), + "no_comments_yet" : MessageLookupByLibrary.simpleMessage("No comments yet"), + "no_coupon_available" : MessageLookupByLibrary.simpleMessage("No coupon available"), + "no_delivery_method" : MessageLookupByLibrary.simpleMessage("No shipping method available."), + "no_expiration" : MessageLookupByLibrary.simpleMessage("No expiration"), + "no_instance_delivery_desc" : MessageLookupByLibrary.simpleMessage("Tracking information will be provided when available."), + "no_more_record" : MessageLookupByLibrary.simpleMessage("No more record"), + "no_onion" : MessageLookupByLibrary.simpleMessage("No onion"), + "no_pickle" : MessageLookupByLibrary.simpleMessage("No pickle"), + "no_restriction" : MessageLookupByLibrary.simpleMessage("No restriction"), + "no_spicy" : MessageLookupByLibrary.simpleMessage("No spicy"), + "no_ticket_yet" : MessageLookupByLibrary.simpleMessage("There is no ticket yet. If you have any question/issue please tap the plus icon above to create a ticket."), + "not_binding" : MessageLookupByLibrary.simpleMessage("Not binding"), + "number_of_people" : MessageLookupByLibrary.simpleMessage("Number of people"), + "ocr_scan" : MessageLookupByLibrary.simpleMessage("OCR scan"), + "ok" : MessageLookupByLibrary.simpleMessage("OK"), + "old_password" : MessageLookupByLibrary.simpleMessage("Old password"), + "online_payment" : MessageLookupByLibrary.simpleMessage("Online payment"), + "optional_information" : MessageLookupByLibrary.simpleMessage("Optional Info."), + "order_acceipt" : MessageLookupByLibrary.simpleMessage("Accept"), + "order_again" : MessageLookupByLibrary.simpleMessage("Order again"), + "order_cancelled" : MessageLookupByLibrary.simpleMessage("Cancelled"), + "order_complete" : MessageLookupByLibrary.simpleMessage("Complete"), + "order_datetime" : MessageLookupByLibrary.simpleMessage("Order datetime"), + "order_detail" : MessageLookupByLibrary.simpleMessage("Order detail"), + "order_fulfillment" : MessageLookupByLibrary.simpleMessage("Order fulfillment"), + "order_info" : MessageLookupByLibrary.simpleMessage("Order info."), + "order_more" : m18, + "order_number" : MessageLookupByLibrary.simpleMessage("Order No."), + "order_number_copied_to_clipboard" : MessageLookupByLibrary.simpleMessage("Order number copied to clipboard"), + "order_processing" : MessageLookupByLibrary.simpleMessage("Processing"), + "order_remark" : MessageLookupByLibrary.simpleMessage("Order remark"), + "out_of_stock" : MessageLookupByLibrary.simpleMessage("Out of stock"), + "over_delivery_distance" : MessageLookupByLibrary.simpleMessage("The address is beyond the scope of delivery."), + "paid" : MessageLookupByLibrary.simpleMessage("Paid"), + "password" : MessageLookupByLibrary.simpleMessage("Password"), + "password_again" : MessageLookupByLibrary.simpleMessage("Password again"), + "password_has_been_changed" : MessageLookupByLibrary.simpleMessage("Password has been changed"), + "password_is_not_match_password_again" : MessageLookupByLibrary.simpleMessage("Password and confirm password does not match."), + "password_is_required" : MessageLookupByLibrary.simpleMessage("Password is required."), + "pay" : MessageLookupByLibrary.simpleMessage("Pay"), + "pay_after_meal" : MessageLookupByLibrary.simpleMessage("Pay after meal"), + "pay_amount_token" : m19, + "pay_later" : MessageLookupByLibrary.simpleMessage("Pay later"), + "pay_now" : MessageLookupByLibrary.simpleMessage("Pay now"), + "pay_on_deliery" : MessageLookupByLibrary.simpleMessage("Pay on delivery"), + "pay_on_deliery_pickup" : MessageLookupByLibrary.simpleMessage("Pay on delivery or pickup"), + "pay_with" : MessageLookupByLibrary.simpleMessage("Pay with"), + "pay_with_existing_cards" : MessageLookupByLibrary.simpleMessage("Pay with existing cards"), + "pay_with_token" : m20, + "payment_amount" : MessageLookupByLibrary.simpleMessage("Payment amount"), + "payment_method" : MessageLookupByLibrary.simpleMessage("Payment method"), + "payment_method_not_set" : MessageLookupByLibrary.simpleMessage("No payment method specified. Please pay on delivery or pickup."), + "payment_status" : MessageLookupByLibrary.simpleMessage("Payment status"), + "payment_verification" : MessageLookupByLibrary.simpleMessage("Payment verification"), + "payment_verification_sent" : m21, + "paypal" : MessageLookupByLibrary.simpleMessage("Paypal"), + "pending" : MessageLookupByLibrary.simpleMessage("Pending"), + "percent_discount" : MessageLookupByLibrary.simpleMessage("%OFF"), + "percentage_discount_token" : m22, + "percentage_discount_token2" : m23, + "pick_a_coupon" : MessageLookupByLibrary.simpleMessage("Pick a coupon"), + "pickup" : MessageLookupByLibrary.simpleMessage("Pickup"), + "pickup_address" : MessageLookupByLibrary.simpleMessage("Pickup address"), + "pickup_at" : MessageLookupByLibrary.simpleMessage("Please pickup at"), + "pickup_discount" : MessageLookupByLibrary.simpleMessage("Pickup discount"), + "please_enter_coupon_code" : MessageLookupByLibrary.simpleMessage("Please enter a coupon code."), + "please_enter_group_number" : MessageLookupByLibrary.simpleMessage("Please enter your group number."), + "please_login" : MessageLookupByLibrary.simpleMessage("Please login"), + "please_provide_shipping_address" : MessageLookupByLibrary.simpleMessage("Please provide shipping address."), + "please_select" : MessageLookupByLibrary.simpleMessage("Please select"), + "please_select_an_image" : MessageLookupByLibrary.simpleMessage("Please select an image"), + "point" : MessageLookupByLibrary.simpleMessage("Points"), "point_of_sale_system_solution" : MessageLookupByLibrary.simpleMessage("Point of sale system solution"), + "postal_code" : MessageLookupByLibrary.simpleMessage("Postal code"), + "postal_code_is_required" : MessageLookupByLibrary.simpleMessage("Postal code is required"), + "previous" : MessageLookupByLibrary.simpleMessage("Previous"), + "price" : MessageLookupByLibrary.simpleMessage("Price"), "privacy_policy" : MessageLookupByLibrary.simpleMessage("Privacy policy"), + "product_insufficient" : MessageLookupByLibrary.simpleMessage("The product quantity is insufficient."), + "products" : MessageLookupByLibrary.simpleMessage("Products"), + "profile" : MessageLookupByLibrary.simpleMessage("Profile"), + "promotions" : MessageLookupByLibrary.simpleMessage("Promotions"), + "province" : MessageLookupByLibrary.simpleMessage("Province"), + "pull_up_to_load_more" : MessageLookupByLibrary.simpleMessage("Pull up to load more"), + "purchase_renew_service" : MessageLookupByLibrary.simpleMessage("Purchase/Renew service"), + "quick_input" : MessageLookupByLibrary.simpleMessage("Quick input"), + "radio_option_is_optional" : MessageLookupByLibrary.simpleMessage("Optional. You can tap Next without selection."), + "radio_option_is_required" : MessageLookupByLibrary.simpleMessage("Option is required. Select an option then tap Next."), + "radio_option_select_token" : m24, "recalculating" : MessageLookupByLibrary.simpleMessage("Recalculating..."), + "red_coupon" : MessageLookupByLibrary.simpleMessage("Coupon"), + "redeem_coupon" : MessageLookupByLibrary.simpleMessage("Redeem"), + "register" : MessageLookupByLibrary.simpleMessage("Register"), + "registration" : MessageLookupByLibrary.simpleMessage("Registration"), + "release_to_load_more" : MessageLookupByLibrary.simpleMessage("Release to load more"), "renew_license" : MessageLookupByLibrary.simpleMessage("Renew license"), + "renewal_fee" : MessageLookupByLibrary.simpleMessage("Renewal fee"), + "reply" : MessageLookupByLibrary.simpleMessage("Reply"), + "reset_password" : MessageLookupByLibrary.simpleMessage("Reset password"), + "reset_password_desc" : MessageLookupByLibrary.simpleMessage("Enter the new password you desired twice."), + "reset_password_success" : MessageLookupByLibrary.simpleMessage("Reset password success"), + "response_from_store" : MessageLookupByLibrary.simpleMessage("Response from store"), "return_policy" : MessageLookupByLibrary.simpleMessage("Return policy"), + "revise_user_profile" : MessageLookupByLibrary.simpleMessage("Revise user profile"), + "sat" : MessageLookupByLibrary.simpleMessage("Sat"), + "saturday" : MessageLookupByLibrary.simpleMessage("Saturday"), + "save" : MessageLookupByLibrary.simpleMessage("Save"), + "schedule_delivery" : MessageLookupByLibrary.simpleMessage("Schedule delivery"), + "search_place" : MessageLookupByLibrary.simpleMessage("Search place"), + "search_product" : MessageLookupByLibrary.simpleMessage("Search product"), + "search_products" : MessageLookupByLibrary.simpleMessage("Search products"), + "select" : MessageLookupByLibrary.simpleMessage("Select"), + "select_a_payment_method" : MessageLookupByLibrary.simpleMessage("Select a payment method"), + "select_a_plan" : MessageLookupByLibrary.simpleMessage("Select a plan"), + "select_canada_post_shipping_rate" : MessageLookupByLibrary.simpleMessage("Please select Canada Post shipping rate."), + "select_delivery_time" : MessageLookupByLibrary.simpleMessage("Select delivery time"), + "select_document_lanuage" : MessageLookupByLibrary.simpleMessage("Select a language"), + "select_document_type" : MessageLookupByLibrary.simpleMessage("Select a document type"), + "select_options" : MessageLookupByLibrary.simpleMessage("Select options"), + "service_descritpion" : MessageLookupByLibrary.simpleMessage("Service description"), + "service_fee" : MessageLookupByLibrary.simpleMessage("Service fee"), "service_policy" : MessageLookupByLibrary.simpleMessage("Service policy"), + "set_password" : MessageLookupByLibrary.simpleMessage("Set password"), + "set_password_desc" : MessageLookupByLibrary.simpleMessage("Enter the password you desired twice."), + "shipping_time_will_schedule" : MessageLookupByLibrary.simpleMessage("Delivery time cannot be determined. Usually we will send it out as soon as possible. If you wish to deliver at a certain time, please state in the remarks."), "shop" : MessageLookupByLibrary.simpleMessage("Shop"), + "show_less" : MessageLookupByLibrary.simpleMessage("Show less"), + "show_more" : MessageLookupByLibrary.simpleMessage("Show more"), + "sold_per_month_token" : m25, + "specification" : MessageLookupByLibrary.simpleMessage("Specification"), + "store" : MessageLookupByLibrary.simpleMessage("Store"), + "store_closed" : MessageLookupByLibrary.simpleMessage("Store closed"), + "store_delivery" : MessageLookupByLibrary.simpleMessage("Store delivery"), + "store_introduction" : MessageLookupByLibrary.simpleMessage("Store introduction"), + "store_policy" : MessageLookupByLibrary.simpleMessage("Store policy"), + "street_line_1" : MessageLookupByLibrary.simpleMessage("Street line 1"), + "street_line_1_is_required" : MessageLookupByLibrary.simpleMessage("Street line 1 is required"), + "street_line_2" : MessageLookupByLibrary.simpleMessage("Street line 2"), + "submit" : MessageLookupByLibrary.simpleMessage("Submit"), + "submit_to_change" : MessageLookupByLibrary.simpleMessage("Submit to change"), + "submit_to_generate" : MessageLookupByLibrary.simpleMessage("Submit to generate"), "submitting" : MessageLookupByLibrary.simpleMessage("Submitting..."), "submitting_please_wait" : MessageLookupByLibrary.simpleMessage("Submitting, please wait..."), + "subtotal" : MessageLookupByLibrary.simpleMessage("Subtotal"), + "subtotal_token" : m26, + "success" : MessageLookupByLibrary.simpleMessage("Success"), + "sun" : MessageLookupByLibrary.simpleMessage("Sun"), + "sunday" : MessageLookupByLibrary.simpleMessage("Sunday"), "support" : MessageLookupByLibrary.simpleMessage("Support"), "support_ticket" : MessageLookupByLibrary.simpleMessage("Support ticket"), + "table_token" : m27, "tap_back_again_to_exit" : MessageLookupByLibrary.simpleMessage("Tap back again to exit"), + "tax" : MessageLookupByLibrary.simpleMessage("Tax"), + "thank_you_for_your_comment" : MessageLookupByLibrary.simpleMessage("Thank you for your comment."), + "the_address_has_been_deleted" : MessageLookupByLibrary.simpleMessage("The address has been deleted."), + "the_email_is_same_as_current" : MessageLookupByLibrary.simpleMessage("The email is the same as the current one."), + "the_mobile_number_is_same_as_current" : MessageLookupByLibrary.simpleMessage("The mobile number is the same as the current one."), + "the_ticket_is_closed_desc" : MessageLookupByLibrary.simpleMessage("The ticket has been closed. If you still have questions, please create a new ticket."), + "this_credit_card_is_invalid" : MessageLookupByLibrary.simpleMessage("This credit card is invalid"), + "this_field_is_required" : MessageLookupByLibrary.simpleMessage("This field is required."), + "thu" : MessageLookupByLibrary.simpleMessage("Thu"), + "thursday" : MessageLookupByLibrary.simpleMessage("Thursday"), + "ticket_created_success" : MessageLookupByLibrary.simpleMessage("Ticket has been created successfully."), + "ticket_number_token" : m28, + "today" : MessageLookupByLibrary.simpleMessage("Today"), + "today_with_time" : m29, + "toll_free" : MessageLookupByLibrary.simpleMessage("Toll free: "), + "tomorrow" : MessageLookupByLibrary.simpleMessage("Tomorrow"), + "tomorrow_with_time" : m30, + "total" : MessageLookupByLibrary.simpleMessage("Total"), + "tue" : MessageLookupByLibrary.simpleMessage("Tue"), + "tuesday" : MessageLookupByLibrary.simpleMessage("Tuesday"), "tutorials" : MessageLookupByLibrary.simpleMessage("Tutorials"), - "wiki" : MessageLookupByLibrary.simpleMessage("Wiki") + "type_your_order_remark" : MessageLookupByLibrary.simpleMessage("Type your order remark"), + "ubereats_receipt" : MessageLookupByLibrary.simpleMessage("UberEats receipt"), + "under_renovation" : MessageLookupByLibrary.simpleMessage("The store is under renovation"), + "unpaid" : MessageLookupByLibrary.simpleMessage("Unpaid"), + "update_success" : MessageLookupByLibrary.simpleMessage("Updated success"), + "user_account_created_success" : MessageLookupByLibrary.simpleMessage("User account created success"), + "user_registration" : MessageLookupByLibrary.simpleMessage("User registration"), + "user_registration_desc" : MessageLookupByLibrary.simpleMessage("Please enter your mobile phone number then click the \'Get code\' button to get the validation code."), + "verification_code" : MessageLookupByLibrary.simpleMessage("Verification code"), + "verification_code_is_required" : MessageLookupByLibrary.simpleMessage("Verification code is required"), + "verification_code_sent" : MessageLookupByLibrary.simpleMessage("Verification code has been sent."), + "verify" : MessageLookupByLibrary.simpleMessage("Verify"), + "view_blog" : MessageLookupByLibrary.simpleMessage("View blog"), + "view_on_google_map" : MessageLookupByLibrary.simpleMessage("View on Google maps"), + "view_ticket" : MessageLookupByLibrary.simpleMessage("View ticket"), + "wallet" : MessageLookupByLibrary.simpleMessage("Wallet"), + "warning" : MessageLookupByLibrary.simpleMessage("Warning"), + "wechatpay" : MessageLookupByLibrary.simpleMessage("Wechat pay"), + "wed" : MessageLookupByLibrary.simpleMessage("Wed"), + "wednesday" : MessageLookupByLibrary.simpleMessage("Wednesday"), + "weight_token" : m31, + "wiki" : MessageLookupByLibrary.simpleMessage("Wiki"), + "wrong_payment_verification_code" : MessageLookupByLibrary.simpleMessage("Wrong payment verification code. Please check and enter again."), + "yes" : MessageLookupByLibrary.simpleMessage("Yes"), + "yes_i_am_sure" : MessageLookupByLibrary.simpleMessage("Yes! I am sure."), + "you_have_no_orders_yet" : MessageLookupByLibrary.simpleMessage("You have no orders yet"), + "your_basket_is_empty" : MessageLookupByLibrary.simpleMessage("Your basket is empty."), + "your_group" : MessageLookupByLibrary.simpleMessage("Your group"), + "your_question_issue" : MessageLookupByLibrary.simpleMessage("Your question/issue"), + "your_reply" : MessageLookupByLibrary.simpleMessage("Your reply") }; } diff --git a/lib/generated/intl/messages_zh_CN.dart b/lib/generated/intl/messages_zh_CN.dart index 721a261..b9e1e94 100644 --- a/lib/generated/intl/messages_zh_CN.dart +++ b/lib/generated/intl/messages_zh_CN.dart @@ -19,41 +19,461 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'zh_CN'; - static m0(oss) => "在${oss}下载"; + static m0(qty) => "等${qty}件"; + + static m1(value) => "订单满\$${value}可用"; + + static m2(hour) => "现在预定,${hour}:00后开始配送。"; + + static m3(optionName) => "多项选择${optionName}"; + + static m4(km, time) => "配送员离您${km},大概需时${time}送达。"; + + static m5(shippingfee) => "配送费${shippingfee}起"; + + static m6(length, width, height) => "尺寸:${length}(L)x${width}(W)x${height}(H)"; + + static m7(discount) => "-\$${discount}"; + + static m8(oss) => "在${oss}下载"; + + static m9(expirationDate) => "${expirationDate}到期"; + + static m10(mon, yer) => "到期:${mon}/${yer}"; + + static m11(name, rate) => "${name}(${rate}%)"; + + static m12(num) => "${num}条回复"; + + static m13(second) => "${second}秒后重试"; + + static m14(hours) => "${Intl.plural(hours, one: '1小时', other: '${hours}小时')}"; + + static m15(minamount) => "\$${minamount}起送"; + + static m16(shipfee) => "配送费\$${shipfee}起"; + + static m17(minutes) => "${Intl.plural(minutes, one: '1分钟', other: '${minutes}分钟')}"; + + static m18(minprice) => "还差${minprice}起送"; + + static m19(amount) => "马上付款 \$${amount}"; + + static m20(method) => "使用${method}付款"; + + static m21(mobile) => "付款码已经发送到您的手机${mobile}。请在下面输入付款码。"; + + static m22(discount) => "${discount}%折扣"; + + static m23(amount, discount) => "-\$${amount}(${discount}%折扣)"; + + static m24(optionName) => "请选择${optionName}"; + + static m25(sold_qty) => " 月售${sold_qty}单"; + + static m26(subtotal) => "小计:${subtotal}"; + + static m27(num) => "桌号:${num}"; + + static m28(num) => "票号 #${num}"; + + static m29(time) => "今天${time}"; + + static m30(time) => "明天${time}"; + + static m31(weight) => "重量:${weight}"; final messages = _notInlinedMessages(_notInlinedMessages); static _notInlinedMessages(_) => { "about" : MessageLookupByLibrary.simpleMessage("关于"), "about_us" : MessageLookupByLibrary.simpleMessage("关于我们"), + "account_binding" : MessageLookupByLibrary.simpleMessage("账号绑定"), + "add_new_address" : MessageLookupByLibrary.simpleMessage("添加新地址"), + "add_new_ticket" : MessageLookupByLibrary.simpleMessage("创建一个新的支持票"), + "add_new_ticket_desc" : MessageLookupByLibrary.simpleMessage("请输入您的问题。您还可以上传文件或图片来使您的提问更清楚。"), + "add_pictures" : MessageLookupByLibrary.simpleMessage("晒照片"), + "add_to_basket" : MessageLookupByLibrary.simpleMessage("放入购物篮"), + "address" : MessageLookupByLibrary.simpleMessage("地址"), + "after_renewed" : MessageLookupByLibrary.simpleMessage("续约后"), + "alipay" : MessageLookupByLibrary.simpleMessage("支付宝"), + "amount_not_meet" : MessageLookupByLibrary.simpleMessage("订单金额没有达到最低要求。"), + "and_more_item_token" : m0, + "are_you_sure_to_cancel_the_order" : MessageLookupByLibrary.simpleMessage("您确定要取消该订单吗?"), + "are_you_sure_to_delete_the_address" : MessageLookupByLibrary.simpleMessage("您确定要删除该地址吗?"), + "are_you_sure_to_empty_basket" : MessageLookupByLibrary.simpleMessage("您确定要清空购物篮吗?"), + "are_you_sure_to_logout" : MessageLookupByLibrary.simpleMessage("您确定要登出吗?"), + "are_you_sure_to_remove_the_card" : MessageLookupByLibrary.simpleMessage("您确定要移除该信用卡吗?"), + "are_you_sure_to_remove_the_item" : MessageLookupByLibrary.simpleMessage("您确定要移除该物品吗?"), + "are_you_sure_to_remove_the_picture" : MessageLookupByLibrary.simpleMessage("您确定要移除该图片吗?"), + "attach_pictures" : MessageLookupByLibrary.simpleMessage("图片附件"), + "attach_pictures_desc" : MessageLookupByLibrary.simpleMessage("建议附上图片(屏幕截图)让问题更清楚。"), + "available_for_order_over_token" : m1, + "avatar" : MessageLookupByLibrary.simpleMessage("头像"), + "back" : MessageLookupByLibrary.simpleMessage("返回"), + "basic_info" : MessageLookupByLibrary.simpleMessage("基本信息"), "blog" : MessageLookupByLibrary.simpleMessage("博客"), + "book_now_delivery_later_token" : m2, + "business_card" : MessageLookupByLibrary.simpleMessage("名片"), + "business_cooperation" : MessageLookupByLibrary.simpleMessage("商业合作"), + "by_email" : MessageLookupByLibrary.simpleMessage("电子邮箱"), + "by_phone" : MessageLookupByLibrary.simpleMessage("联系电话"), + "camera" : MessageLookupByLibrary.simpleMessage("相机"), + "canada_post" : MessageLookupByLibrary.simpleMessage("加拿大邮政"), + "canada_post_delivery" : MessageLookupByLibrary.simpleMessage("加拿大邮政配送"), + "cancel" : MessageLookupByLibrary.simpleMessage("取消"), + "cancel_order" : MessageLookupByLibrary.simpleMessage("取消订单"), + "change_email" : MessageLookupByLibrary.simpleMessage("修改电子邮箱"), + "change_email_desc" : MessageLookupByLibrary.simpleMessage("输入新的Email,然后点击\'获取验证码\'按钮。"), + "change_mobile" : MessageLookupByLibrary.simpleMessage("修改手机号码"), + "change_mobile_desc" : MessageLookupByLibrary.simpleMessage("输入新的手机号码,然后点击\'获取验证码\'按钮。"), + "change_nickname" : MessageLookupByLibrary.simpleMessage("修改昵称"), + "change_password" : MessageLookupByLibrary.simpleMessage("修改密码"), + "change_password_desc" : MessageLookupByLibrary.simpleMessage("请输入旧密码和希望的新密码。"), + "check_option_is_optional" : MessageLookupByLibrary.simpleMessage("可选选项。如果不选择可以直接点下一步。"), + "check_option_is_required" : MessageLookupByLibrary.simpleMessage("选项必选。选择至少一项再点击下一步。"), + "check_option_select_token" : m3, + "checkout" : MessageLookupByLibrary.simpleMessage("去结算"), + "checkout_no_deliver" : MessageLookupByLibrary.simpleMessage("现在这时间我们不派送。"), + "chinese_simplified" : MessageLookupByLibrary.simpleMessage("简体中文"), + "chinese_traditional" : MessageLookupByLibrary.simpleMessage("繁体中文"), + "choose_a_shipping_rate" : MessageLookupByLibrary.simpleMessage("选择运费"), + "city" : MessageLookupByLibrary.simpleMessage("城市"), + "city_is_required" : MessageLookupByLibrary.simpleMessage("城市必填"), + "close" : MessageLookupByLibrary.simpleMessage("关闭"), + "comment" : MessageLookupByLibrary.simpleMessage("评价"), + "comment_empty" : MessageLookupByLibrary.simpleMessage("请输入您的评价。"), + "comments" : MessageLookupByLibrary.simpleMessage("评价"), + "confirm_order" : MessageLookupByLibrary.simpleMessage("订单确定"), + "confirmation" : MessageLookupByLibrary.simpleMessage("请确定"), + "contact_name" : MessageLookupByLibrary.simpleMessage("联系人姓名"), + "contact_name_is_required" : MessageLookupByLibrary.simpleMessage("联系人姓名必填"), "contact_us" : MessageLookupByLibrary.simpleMessage("联系我们"), + "copy" : MessageLookupByLibrary.simpleMessage("复制"), + "coupons" : MessageLookupByLibrary.simpleMessage("优惠券"), + "credit_card" : MessageLookupByLibrary.simpleMessage("信用卡"), + "credit_coupon" : MessageLookupByLibrary.simpleMessage("红包/抵用券"), + "credit_debit_card" : MessageLookupByLibrary.simpleMessage("信用卡或银行卡"), + "current_password_is_required" : MessageLookupByLibrary.simpleMessage("需要当前密码"), + "current_plan" : MessageLookupByLibrary.simpleMessage("当前计划"), + "customer" : MessageLookupByLibrary.simpleMessage("客户"), + "delete" : MessageLookupByLibrary.simpleMessage("删除"), + "delivery" : MessageLookupByLibrary.simpleMessage("外卖配送"), + "delivery_address" : MessageLookupByLibrary.simpleMessage("收货地址"), + "delivery_distance_token" : m4, + "delivery_fee" : m5, + "delivery_guy" : MessageLookupByLibrary.simpleMessage("配送员"), + "delivery_info" : MessageLookupByLibrary.simpleMessage("配送信息"), + "delivery_method" : MessageLookupByLibrary.simpleMessage("配送方式"), + "delivery_now" : MessageLookupByLibrary.simpleMessage("立刻配送"), + "delivery_unavailable" : MessageLookupByLibrary.simpleMessage("配送时间"), + "detail" : MessageLookupByLibrary.simpleMessage("详细描述"), "developer_of" : MessageLookupByLibrary.simpleMessage("开发者"), + "dimentions_token" : m6, + "discount_amount_token" : m7, + "document_langage" : MessageLookupByLibrary.simpleMessage("文档语言"), + "document_type" : MessageLookupByLibrary.simpleMessage("文件类型"), + "dont_use" : MessageLookupByLibrary.simpleMessage("不使用"), "download" : MessageLookupByLibrary.simpleMessage("下载"), - "download_with_token" : m0, + "download_with_token" : m8, + "downloads" : MessageLookupByLibrary.simpleMessage("下载"), + "edit_address" : MessageLookupByLibrary.simpleMessage("修改地址"), + "email" : MessageLookupByLibrary.simpleMessage("电子邮箱"), + "email_is_not_valid" : MessageLookupByLibrary.simpleMessage("电子邮箱无效"), + "email_is_required" : MessageLookupByLibrary.simpleMessage("电子邮箱必填"), + "email_needed" : MessageLookupByLibrary.simpleMessage("需要电子邮箱"), + "empty_address_change_keyword" : MessageLookupByLibrary.simpleMessage("找不到地址。点击这里跳过地址搜索。"), + "empty_basket" : MessageLookupByLibrary.simpleMessage("清空购物篮"), + "empty_result_change_keyword" : MessageLookupByLibrary.simpleMessage("找不到您需要的数据,请改变关键字再试。"), + "end_of_the_list" : MessageLookupByLibrary.simpleMessage("全部加载"), + "english" : MessageLookupByLibrary.simpleMessage("英文"), + "enter_coupon_code" : MessageLookupByLibrary.simpleMessage("输入红包号码"), + "enter_delivery_address" : MessageLookupByLibrary.simpleMessage("输入送货地址"), + "enter_mobile_or_email" : MessageLookupByLibrary.simpleMessage("输入手机号码或电子邮箱地址"), + "enter_new_nickname" : MessageLookupByLibrary.simpleMessage("输入新昵称"), + "enter_product_keyword" : MessageLookupByLibrary.simpleMessage("输入产品关键字"), + "error" : MessageLookupByLibrary.simpleMessage("错误"), + "error_read_file" : MessageLookupByLibrary.simpleMessage("读取文件过程中出错。"), + "expiration_date" : MessageLookupByLibrary.simpleMessage("到期日期"), + "expiration_date_token" : m9, + "expire_token" : m10, + "expired_at" : MessageLookupByLibrary.simpleMessage("到期日期"), + "extra_fee_token" : m11, + "fax" : MessageLookupByLibrary.simpleMessage("传真"), + "feature_not_available_web" : MessageLookupByLibrary.simpleMessage("该功能在网页版上不能用。请下载App版本。"), + "featured_product" : MessageLookupByLibrary.simpleMessage("特色产品"), + "finish" : MessageLookupByLibrary.simpleMessage("完成"), + "follow_ups" : MessageLookupByLibrary.simpleMessage("跟进"), + "followups_token" : m12, + "forgot_password" : MessageLookupByLibrary.simpleMessage("忘记密码"), + "forgot_password_description" : MessageLookupByLibrary.simpleMessage("输入您注册时用的Email或手机号码,然后点击‘获取验证码’ 按钮。"), + "forgot_password_question" : MessageLookupByLibrary.simpleMessage("忘记密码?"), + "fri" : MessageLookupByLibrary.simpleMessage("周五"), + "friday" : MessageLookupByLibrary.simpleMessage("星期五"), + "from_camera" : MessageLookupByLibrary.simpleMessage("手机照相机"), + "from_gallery" : MessageLookupByLibrary.simpleMessage("手机图库"), + "gallery" : MessageLookupByLibrary.simpleMessage("图库"), + "general_coupon" : MessageLookupByLibrary.simpleMessage("通用优惠券"), + "get_code" : MessageLookupByLibrary.simpleMessage("获取验证码"), + "get_code_again" : MessageLookupByLibrary.simpleMessage("重新获取验证码"), + "get_code_token" : m13, + "get_coupon" : MessageLookupByLibrary.simpleMessage("获取红包"), + "get_picture" : MessageLookupByLibrary.simpleMessage("获取图片"), + "get_picture_from" : MessageLookupByLibrary.simpleMessage("从...获取图片"), + "group_license_renewal" : MessageLookupByLibrary.simpleMessage("集团许可续订"), + "group_number" : MessageLookupByLibrary.simpleMessage("集团号码"), + "group_number_can_be_found" : MessageLookupByLibrary.simpleMessage("按如下图找到集团号码..."), "home" : MessageLookupByLibrary.simpleMessage("首页"), + "hot_sale" : MessageLookupByLibrary.simpleMessage("热卖"), + "hour_token" : m14, + "igoshow" : MessageLookupByLibrary.simpleMessage("iGoShow"), + "includes" : MessageLookupByLibrary.simpleMessage("包含"), "information" : MessageLookupByLibrary.simpleMessage("信息"), + "input_your_comment" : MessageLookupByLibrary.simpleMessage("写下您的评价"), "install_in_store" : MessageLookupByLibrary.simpleMessage("店内安装"), "learn_more" : MessageLookupByLibrary.simpleMessage("了解更多..."), + "learn_more_about_igoshow" : MessageLookupByLibrary.simpleMessage("详细了解iGoShow"), + "learn_more_about_minipos" : MessageLookupByLibrary.simpleMessage("详细了解MiniPOS"), "license_agreement" : MessageLookupByLibrary.simpleMessage("许可协议"), + "light_tase" : MessageLookupByLibrary.simpleMessage("清淡口味"), + "load_failed_retry" : MessageLookupByLibrary.simpleMessage("载入失败,请重试。"), + "loading" : MessageLookupByLibrary.simpleMessage("装载中..."), "loading_please_wait" : MessageLookupByLibrary.simpleMessage("装载中,清稍候..."), "login" : MessageLookupByLibrary.simpleMessage("登入"), + "login_instruction" : MessageLookupByLibrary.simpleMessage("如果您已经注册了账号,请使用如下表单登陆。\n如果您是MiniOffice的用户,您可直接输入您的MiniOffice用户名和密码登陆。"), "logout" : MessageLookupByLibrary.simpleMessage("登出"), "main_content_1" : MessageLookupByLibrary.simpleMessage("自从1999年来,我们一直致力于开发完整的强大的销售系统,帮助了上千小企业平滑的处理销售业务。当前我们有两个主要的产品。"), + "me" : MessageLookupByLibrary.simpleMessage("我的"), + "min_order_amount_token" : m15, + "min_shipping_fee" : m16, "minipos" : MessageLookupByLibrary.simpleMessage("MiniPOS"), + "minute_token" : m17, + "mobile_email_username" : MessageLookupByLibrary.simpleMessage("手机号码,Email或MiniOffice用户名"), + "mobile_is_required" : MessageLookupByLibrary.simpleMessage("手机号码必填"), + "mobile_number" : MessageLookupByLibrary.simpleMessage("手机号码"), + "mobile_or_email" : MessageLookupByLibrary.simpleMessage("手机号码或电子邮箱"), + "mobile_or_email_is_required" : MessageLookupByLibrary.simpleMessage("必须输入手机号码或电子邮箱地址"), + "mobile_phone_number" : MessageLookupByLibrary.simpleMessage("手机号码"), + "mobile_phone_number_is_required" : MessageLookupByLibrary.simpleMessage("手机号码必填"), + "mon" : MessageLookupByLibrary.simpleMessage("周一"), + "monday" : MessageLookupByLibrary.simpleMessage("星期一"), + "mr" : MessageLookupByLibrary.simpleMessage("先生"), + "ms" : MessageLookupByLibrary.simpleMessage("女士"), + "my_addresses" : MessageLookupByLibrary.simpleMessage("我的地址"), + "my_cards" : MessageLookupByLibrary.simpleMessage("我的卡"), + "my_favorites" : MessageLookupByLibrary.simpleMessage("我的收藏"), + "my_orders" : MessageLookupByLibrary.simpleMessage("我的订单"), + "my_support" : MessageLookupByLibrary.simpleMessage("支持"), "navigation" : MessageLookupByLibrary.simpleMessage("导航"), + "new_address" : MessageLookupByLibrary.simpleMessage("添加新地址"), + "new_comment" : MessageLookupByLibrary.simpleMessage("新的评价"), + "new_ticket" : MessageLookupByLibrary.simpleMessage("新的支持票"), + "new_user_question" : MessageLookupByLibrary.simpleMessage("新用户?"), + "next" : MessageLookupByLibrary.simpleMessage("下一步"), + "nick_name" : MessageLookupByLibrary.simpleMessage("昵称"), + "nickname_is_required" : MessageLookupByLibrary.simpleMessage("昵称不能为空"), + "no" : MessageLookupByLibrary.simpleMessage("不是"), + "no_address_yet" : MessageLookupByLibrary.simpleMessage("您还没有输入任何地址。"), + "no_blog_yet" : MessageLookupByLibrary.simpleMessage("尚无要显示的博客。"), + "no_comments_yet" : MessageLookupByLibrary.simpleMessage("还没评价"), + "no_coupon_available" : MessageLookupByLibrary.simpleMessage("无可用红包"), + "no_delivery_method" : MessageLookupByLibrary.simpleMessage("没有可用的送货方式"), + "no_instance_delivery_desc" : MessageLookupByLibrary.simpleMessage("跟踪信息将在可用时提供。"), + "no_more_record" : MessageLookupByLibrary.simpleMessage("没有更多记录了"), + "no_onion" : MessageLookupByLibrary.simpleMessage("不要洋葱"), + "no_pickle" : MessageLookupByLibrary.simpleMessage("不要泡菜"), + "no_restriction" : MessageLookupByLibrary.simpleMessage("无门槛"), + "no_spicy" : MessageLookupByLibrary.simpleMessage("不要辣"), + "no_ticket_yet" : MessageLookupByLibrary.simpleMessage("还没有支持票。 如果您有任何疑问/问题,请点击上方的加号图标以创建支持票。"), + "not_binding" : MessageLookupByLibrary.simpleMessage("未绑定"), + "number_of_people" : MessageLookupByLibrary.simpleMessage("人数"), + "ocr_scan" : MessageLookupByLibrary.simpleMessage("OCR扫描"), + "ok" : MessageLookupByLibrary.simpleMessage("确定"), + "old_password" : MessageLookupByLibrary.simpleMessage("旧密码"), + "online_payment" : MessageLookupByLibrary.simpleMessage("在线付款"), + "optional_information" : MessageLookupByLibrary.simpleMessage("可选填信息"), + "order_acceipt" : MessageLookupByLibrary.simpleMessage("已接单"), + "order_again" : MessageLookupByLibrary.simpleMessage("再来一单"), + "order_cancelled" : MessageLookupByLibrary.simpleMessage("已取消"), + "order_complete" : MessageLookupByLibrary.simpleMessage("已完成"), + "order_datetime" : MessageLookupByLibrary.simpleMessage("下单时间"), + "order_detail" : MessageLookupByLibrary.simpleMessage("订单详情"), + "order_fulfillment" : MessageLookupByLibrary.simpleMessage("订单完成度"), + "order_info" : MessageLookupByLibrary.simpleMessage("订单信息"), + "order_more" : m18, + "order_number" : MessageLookupByLibrary.simpleMessage("订单号码"), + "order_number_copied_to_clipboard" : MessageLookupByLibrary.simpleMessage("订单号码复制到剪切板"), + "order_processing" : MessageLookupByLibrary.simpleMessage("准备中"), + "order_remark" : MessageLookupByLibrary.simpleMessage("订单备注"), + "out_of_stock" : MessageLookupByLibrary.simpleMessage("脱销"), + "over_delivery_distance" : MessageLookupByLibrary.simpleMessage("地址超出配送范围"), + "paid" : MessageLookupByLibrary.simpleMessage("已付款"), + "password" : MessageLookupByLibrary.simpleMessage("密码"), + "password_again" : MessageLookupByLibrary.simpleMessage("再输入密码"), + "password_has_been_changed" : MessageLookupByLibrary.simpleMessage("密码修改成功"), + "password_is_not_match_password_again" : MessageLookupByLibrary.simpleMessage("两次输入密码不一致。"), + "password_is_required" : MessageLookupByLibrary.simpleMessage("密码必填。"), + "pay" : MessageLookupByLibrary.simpleMessage("付款"), + "pay_after_meal" : MessageLookupByLibrary.simpleMessage("餐后付款"), + "pay_amount_token" : m19, + "pay_later" : MessageLookupByLibrary.simpleMessage("稍后付款"), + "pay_now" : MessageLookupByLibrary.simpleMessage("去支付"), + "pay_on_deliery" : MessageLookupByLibrary.simpleMessage("到货付款"), + "pay_on_deliery_pickup" : MessageLookupByLibrary.simpleMessage("自提或到货付款"), + "pay_with" : MessageLookupByLibrary.simpleMessage("使用"), + "pay_with_existing_cards" : MessageLookupByLibrary.simpleMessage("用现有的卡付款"), + "pay_with_token" : m20, + "payment_amount" : MessageLookupByLibrary.simpleMessage("付款金额"), + "payment_method" : MessageLookupByLibrary.simpleMessage("付款方式"), + "payment_method_not_set" : MessageLookupByLibrary.simpleMessage("没有指定付款方式,请货到付款或自提付款。"), + "payment_status" : MessageLookupByLibrary.simpleMessage("付款状态"), + "payment_verification" : MessageLookupByLibrary.simpleMessage("付款验证"), + "payment_verification_sent" : m21, + "paypal" : MessageLookupByLibrary.simpleMessage("Paypal"), + "pending" : MessageLookupByLibrary.simpleMessage("待接单"), + "percent_discount" : MessageLookupByLibrary.simpleMessage("%折扣"), + "percentage_discount_token" : m22, + "percentage_discount_token2" : m23, + "pick_a_coupon" : MessageLookupByLibrary.simpleMessage("使用红包"), + "pickup" : MessageLookupByLibrary.simpleMessage("到店自提"), + "pickup_address" : MessageLookupByLibrary.simpleMessage("自提地址"), + "pickup_at" : MessageLookupByLibrary.simpleMessage("自提地址"), + "pickup_discount" : MessageLookupByLibrary.simpleMessage("自提折扣"), + "please_enter_coupon_code" : MessageLookupByLibrary.simpleMessage("请输入红包号码。"), + "please_enter_group_number" : MessageLookupByLibrary.simpleMessage("请输入您的集团号码。"), + "please_login" : MessageLookupByLibrary.simpleMessage("请登陆"), + "please_provide_shipping_address" : MessageLookupByLibrary.simpleMessage("请提供收货地址"), + "please_select" : MessageLookupByLibrary.simpleMessage("请选取"), + "please_select_an_image" : MessageLookupByLibrary.simpleMessage("请选取一张图片"), + "point" : MessageLookupByLibrary.simpleMessage("积分"), "point_of_sale_system_solution" : MessageLookupByLibrary.simpleMessage("收款系统方案"), + "postal_code" : MessageLookupByLibrary.simpleMessage("邮政编码"), + "postal_code_is_required" : MessageLookupByLibrary.simpleMessage("邮政编码必填"), + "previous" : MessageLookupByLibrary.simpleMessage("上一步"), + "price" : MessageLookupByLibrary.simpleMessage("价格"), "privacy_policy" : MessageLookupByLibrary.simpleMessage("私隐条款"), + "product_insufficient" : MessageLookupByLibrary.simpleMessage("该产品数量不足。"), + "products" : MessageLookupByLibrary.simpleMessage("产品"), + "profile" : MessageLookupByLibrary.simpleMessage("个人资料"), + "promotions" : MessageLookupByLibrary.simpleMessage("推广"), + "province" : MessageLookupByLibrary.simpleMessage("省份"), + "pull_up_to_load_more" : MessageLookupByLibrary.simpleMessage("上拉加载更多"), + "purchase_renew_service" : MessageLookupByLibrary.simpleMessage("购买/续期服务"), + "quick_input" : MessageLookupByLibrary.simpleMessage("快速输入"), + "radio_option_is_optional" : MessageLookupByLibrary.simpleMessage("可选选项。如果不选择可以直接点下一步。"), + "radio_option_is_required" : MessageLookupByLibrary.simpleMessage("选项必选。选择一个选项后点下一步。"), + "radio_option_select_token" : m24, "recalculating" : MessageLookupByLibrary.simpleMessage("运算中..."), + "red_coupon" : MessageLookupByLibrary.simpleMessage("红包"), + "redeem_coupon" : MessageLookupByLibrary.simpleMessage("去使用"), + "register" : MessageLookupByLibrary.simpleMessage("注册"), + "registration" : MessageLookupByLibrary.simpleMessage("注册"), + "release_to_load_more" : MessageLookupByLibrary.simpleMessage("松开载入"), "renew_license" : MessageLookupByLibrary.simpleMessage("证书续期"), + "renewal_fee" : MessageLookupByLibrary.simpleMessage("续订费用"), + "reply" : MessageLookupByLibrary.simpleMessage("回复"), + "reset_password" : MessageLookupByLibrary.simpleMessage("重置密码"), + "reset_password_desc" : MessageLookupByLibrary.simpleMessage("输入您想要的新密码两次。"), + "reset_password_success" : MessageLookupByLibrary.simpleMessage("重置密码成功"), + "response_from_store" : MessageLookupByLibrary.simpleMessage("店家回复"), "return_policy" : MessageLookupByLibrary.simpleMessage("退货条款"), + "revise_user_profile" : MessageLookupByLibrary.simpleMessage("修改用户信息"), + "sat" : MessageLookupByLibrary.simpleMessage("周六"), + "saturday" : MessageLookupByLibrary.simpleMessage("星期六"), + "save" : MessageLookupByLibrary.simpleMessage("保存"), + "schedule_delivery" : MessageLookupByLibrary.simpleMessage("预约送货时间"), + "search_place" : MessageLookupByLibrary.simpleMessage("搜索地址"), + "search_product" : MessageLookupByLibrary.simpleMessage("搜索产品"), + "search_products" : MessageLookupByLibrary.simpleMessage("搜索产品"), + "select" : MessageLookupByLibrary.simpleMessage("选取"), + "select_a_payment_method" : MessageLookupByLibrary.simpleMessage("选择付款方式"), + "select_a_plan" : MessageLookupByLibrary.simpleMessage("选择一个计划"), + "select_canada_post_shipping_rate" : MessageLookupByLibrary.simpleMessage("请选择加拿大邮政运费。"), + "select_delivery_time" : MessageLookupByLibrary.simpleMessage("选择配送时间"), + "select_document_lanuage" : MessageLookupByLibrary.simpleMessage("选择一种语言"), + "select_document_type" : MessageLookupByLibrary.simpleMessage("选择一种文件类型"), + "select_options" : MessageLookupByLibrary.simpleMessage("选择选项"), + "service_descritpion" : MessageLookupByLibrary.simpleMessage("服务描述"), + "service_fee" : MessageLookupByLibrary.simpleMessage("服务费"), "service_policy" : MessageLookupByLibrary.simpleMessage("服务条款"), + "set_password" : MessageLookupByLibrary.simpleMessage("设置密码"), + "set_password_desc" : MessageLookupByLibrary.simpleMessage("输入您想要的密码两次。"), + "shipping_time_will_schedule" : MessageLookupByLibrary.simpleMessage("配送时间无法确定。通常我们会尽快送出。如果您希望在某时配送,请在备注里注明。"), "shop" : MessageLookupByLibrary.simpleMessage("线上购买"), + "show_less" : MessageLookupByLibrary.simpleMessage("显示更少"), + "show_more" : MessageLookupByLibrary.simpleMessage("显示更多"), + "sold_per_month_token" : m25, + "specification" : MessageLookupByLibrary.simpleMessage("规格"), + "store" : MessageLookupByLibrary.simpleMessage("店家"), + "store_closed" : MessageLookupByLibrary.simpleMessage("商店已关"), + "store_delivery" : MessageLookupByLibrary.simpleMessage("商家配送"), + "store_introduction" : MessageLookupByLibrary.simpleMessage("店家简介"), + "store_policy" : MessageLookupByLibrary.simpleMessage("商家政策"), + "street_line_1" : MessageLookupByLibrary.simpleMessage("街道名称第一行"), + "street_line_1_is_required" : MessageLookupByLibrary.simpleMessage("街道名称第一行必填"), + "street_line_2" : MessageLookupByLibrary.simpleMessage("街道名称第二行"), + "submit" : MessageLookupByLibrary.simpleMessage("提交"), + "submit_to_change" : MessageLookupByLibrary.simpleMessage("提交修改"), + "submit_to_generate" : MessageLookupByLibrary.simpleMessage("提交生成"), "submitting" : MessageLookupByLibrary.simpleMessage("提交中..."), "submitting_please_wait" : MessageLookupByLibrary.simpleMessage("提交中,清稍候..."), + "subtotal" : MessageLookupByLibrary.simpleMessage("小计"), + "subtotal_token" : m26, + "success" : MessageLookupByLibrary.simpleMessage("成功"), + "sun" : MessageLookupByLibrary.simpleMessage("周日"), + "sunday" : MessageLookupByLibrary.simpleMessage("星期日"), "support" : MessageLookupByLibrary.simpleMessage("技术支持"), "support_ticket" : MessageLookupByLibrary.simpleMessage("客户提问"), + "table_token" : m27, "tap_back_again_to_exit" : MessageLookupByLibrary.simpleMessage("再次点击返回退出"), + "tax" : MessageLookupByLibrary.simpleMessage("税费"), + "thank_you_for_your_comment" : MessageLookupByLibrary.simpleMessage("谢谢您的评价。"), + "the_address_has_been_deleted" : MessageLookupByLibrary.simpleMessage("地址成功删除"), + "the_email_is_same_as_current" : MessageLookupByLibrary.simpleMessage("电子邮箱和当前的是一样的。"), + "the_mobile_number_is_same_as_current" : MessageLookupByLibrary.simpleMessage("手机号码和当前的是一样的。"), + "the_ticket_is_closed_desc" : MessageLookupByLibrary.simpleMessage("该支持票已经关闭。如果您还有问题,请创建新的支持票。"), + "this_field_is_required" : MessageLookupByLibrary.simpleMessage("该字段必填。"), + "thu" : MessageLookupByLibrary.simpleMessage("周四"), + "thursday" : MessageLookupByLibrary.simpleMessage("星期四"), + "ticket_created_success" : MessageLookupByLibrary.simpleMessage("支持票成功创建。"), + "ticket_number_token" : m28, + "today" : MessageLookupByLibrary.simpleMessage("今天"), + "today_with_time" : m29, + "toll_free" : MessageLookupByLibrary.simpleMessage("无费号码:"), + "tomorrow" : MessageLookupByLibrary.simpleMessage("明天"), + "tomorrow_with_time" : m30, + "total" : MessageLookupByLibrary.simpleMessage("总计"), + "tue" : MessageLookupByLibrary.simpleMessage("周二"), + "tuesday" : MessageLookupByLibrary.simpleMessage("星期二"), "tutorials" : MessageLookupByLibrary.simpleMessage("教程"), - "wiki" : MessageLookupByLibrary.simpleMessage("维基") + "type_your_order_remark" : MessageLookupByLibrary.simpleMessage("输入订单备注"), + "ubereats_receipt" : MessageLookupByLibrary.simpleMessage("UberEats收据"), + "under_renovation" : MessageLookupByLibrary.simpleMessage("本店还在装修"), + "unpaid" : MessageLookupByLibrary.simpleMessage("未付款"), + "update_success" : MessageLookupByLibrary.simpleMessage("更新成功"), + "user_account_created_success" : MessageLookupByLibrary.simpleMessage("用户账号成功创建"), + "user_registration" : MessageLookupByLibrary.simpleMessage("用户注册"), + "user_registration_desc" : MessageLookupByLibrary.simpleMessage("请输入您的手机号码,然后单击“获取验证码”按钮。"), + "verification_code" : MessageLookupByLibrary.simpleMessage("验证码"), + "verification_code_is_required" : MessageLookupByLibrary.simpleMessage("必须输入验证码"), + "verification_code_sent" : MessageLookupByLibrary.simpleMessage("验证码已经发送。"), + "verify" : MessageLookupByLibrary.simpleMessage("验证"), + "view_blog" : MessageLookupByLibrary.simpleMessage("查看博客"), + "view_on_google_map" : MessageLookupByLibrary.simpleMessage("在谷歌地图打开"), + "view_ticket" : MessageLookupByLibrary.simpleMessage("查看支持票"), + "wallet" : MessageLookupByLibrary.simpleMessage("钱包"), + "warning" : MessageLookupByLibrary.simpleMessage("注意"), + "wechatpay" : MessageLookupByLibrary.simpleMessage("微信支付"), + "wed" : MessageLookupByLibrary.simpleMessage("周三"), + "wednesday" : MessageLookupByLibrary.simpleMessage("星期三"), + "weight_token" : m31, + "wiki" : MessageLookupByLibrary.simpleMessage("维基"), + "wrong_payment_verification_code" : MessageLookupByLibrary.simpleMessage("错误的付款码,请查证后再输入。"), + "yes" : MessageLookupByLibrary.simpleMessage("是的"), + "yes_i_am_sure" : MessageLookupByLibrary.simpleMessage("是的!我确定。"), + "you_have_no_orders_yet" : MessageLookupByLibrary.simpleMessage("您还没有订单"), + "your_basket_is_empty" : MessageLookupByLibrary.simpleMessage("您的购物篮是空的"), + "your_group" : MessageLookupByLibrary.simpleMessage("您的集团"), + "your_question_issue" : MessageLookupByLibrary.simpleMessage("您的问题"), + "your_reply" : MessageLookupByLibrary.simpleMessage("您的回复") }; } diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index d47c366..f328a0b 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -354,6 +354,3630 @@ class S { args: [], ); } + + /// `iGoShow` + String get igoshow { + return Intl.message( + 'iGoShow', + name: 'igoshow', + desc: '', + args: [], + ); + } + + /// `Mobile, Email or MiniOffice username` + String get mobile_email_username { + return Intl.message( + 'Mobile, Email or MiniOffice username', + name: 'mobile_email_username', + desc: '', + args: [], + ); + } + + /// `This field is required.` + String get this_field_is_required { + return Intl.message( + 'This field is required.', + name: 'this_field_is_required', + desc: '', + args: [], + ); + } + + /// `Password` + String get password { + return Intl.message( + 'Password', + name: 'password', + desc: '', + args: [], + ); + } + + /// `Password is required.` + String get password_is_required { + return Intl.message( + 'Password is required.', + name: 'password_is_required', + desc: '', + args: [], + ); + } + + /// `New user?` + String get new_user_question { + return Intl.message( + 'New user?', + name: 'new_user_question', + desc: '', + args: [], + ); + } + + /// `Forgot password?` + String get forgot_password_question { + return Intl.message( + 'Forgot password?', + name: 'forgot_password_question', + desc: '', + args: [], + ); + } + + /// `Error` + String get error { + return Intl.message( + 'Error', + name: 'error', + desc: '', + args: [], + ); + } + + /// `OK` + String get ok { + return Intl.message( + 'OK', + name: 'ok', + desc: '', + args: [], + ); + } + + /// `Please login` + String get please_login { + return Intl.message( + 'Please login', + name: 'please_login', + desc: '', + args: [], + ); + } + + /// `If you have already registered an account, please use the following form to login.\nIf you are a MiniOffice user, you can directly enter your MiniOffice username and password to login.` + String get login_instruction { + return Intl.message( + 'If you have already registered an account, please use the following form to login.\nIf you are a MiniOffice user, you can directly enter your MiniOffice username and password to login.', + name: 'login_instruction', + desc: '', + args: [], + ); + } + + /// `Me` + String get me { + return Intl.message( + 'Me', + name: 'me', + desc: '', + args: [], + ); + } + + /// `Wallet` + String get wallet { + return Intl.message( + 'Wallet', + name: 'wallet', + desc: '', + args: [], + ); + } + + /// `Coupon` + String get red_coupon { + return Intl.message( + 'Coupon', + name: 'red_coupon', + desc: '', + args: [], + ); + } + + /// `Points` + String get point { + return Intl.message( + 'Points', + name: 'point', + desc: '', + args: [], + ); + } + + /// `My favorites` + String get my_favorites { + return Intl.message( + 'My favorites', + name: 'my_favorites', + desc: '', + args: [], + ); + } + + /// `My addresses` + String get my_addresses { + return Intl.message( + 'My addresses', + name: 'my_addresses', + desc: '', + args: [], + ); + } + + /// `My cards` + String get my_cards { + return Intl.message( + 'My cards', + name: 'my_cards', + desc: '', + args: [], + ); + } + + /// `My support` + String get my_support { + return Intl.message( + 'My support', + name: 'my_support', + desc: '', + args: [], + ); + } + + /// `Warning` + String get warning { + return Intl.message( + 'Warning', + name: 'warning', + desc: '', + args: [], + ); + } + + /// `Email needed` + String get email_needed { + return Intl.message( + 'Email needed', + name: 'email_needed', + desc: '', + args: [], + ); + } + + /// `Cancel` + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel', + desc: '', + args: [], + ); + } + + /// `Business cooperation` + String get business_cooperation { + return Intl.message( + 'Business cooperation', + name: 'business_cooperation', + desc: '', + args: [], + ); + } + + /// `Change password` + String get change_password { + return Intl.message( + 'Change password', + name: 'change_password', + desc: '', + args: [], + ); + } + + /// `Old password` + String get old_password { + return Intl.message( + 'Old password', + name: 'old_password', + desc: '', + args: [], + ); + } + + /// `Current password is required.` + String get current_password_is_required { + return Intl.message( + 'Current password is required.', + name: 'current_password_is_required', + desc: '', + args: [], + ); + } + + /// `Password again` + String get password_again { + return Intl.message( + 'Password again', + name: 'password_again', + desc: '', + args: [], + ); + } + + /// `Password and confirm password does not match.` + String get password_is_not_match_password_again { + return Intl.message( + 'Password and confirm password does not match.', + name: 'password_is_not_match_password_again', + desc: '', + args: [], + ); + } + + /// `Submit` + String get submit { + return Intl.message( + 'Submit', + name: 'submit', + desc: '', + args: [], + ); + } + + /// `Success` + String get success { + return Intl.message( + 'Success', + name: 'success', + desc: '', + args: [], + ); + } + + /// `Password has been changed` + String get password_has_been_changed { + return Intl.message( + 'Password has been changed', + name: 'password_has_been_changed', + desc: '', + args: [], + ); + } + + /// `Profile` + String get profile { + return Intl.message( + 'Profile', + name: 'profile', + desc: '', + args: [], + ); + } + + /// `Basic info.` + String get basic_info { + return Intl.message( + 'Basic info.', + name: 'basic_info', + desc: '', + args: [], + ); + } + + /// `Avatar` + String get avatar { + return Intl.message( + 'Avatar', + name: 'avatar', + desc: '', + args: [], + ); + } + + /// `Nick name` + String get nick_name { + return Intl.message( + 'Nick name', + name: 'nick_name', + desc: '', + args: [], + ); + } + + /// `Account binding` + String get account_binding { + return Intl.message( + 'Account binding', + name: 'account_binding', + desc: '', + args: [], + ); + } + + /// `Mobile number` + String get mobile_number { + return Intl.message( + 'Mobile number', + name: 'mobile_number', + desc: '', + args: [], + ); + } + + /// `Not binding` + String get not_binding { + return Intl.message( + 'Not binding', + name: 'not_binding', + desc: '', + args: [], + ); + } + + /// `Email` + String get email { + return Intl.message( + 'Email', + name: 'email', + desc: '', + args: [], + ); + } + + /// `Get picture` + String get get_picture { + return Intl.message( + 'Get picture', + name: 'get_picture', + desc: '', + args: [], + ); + } + + /// `Get picture from...` + String get get_picture_from { + return Intl.message( + 'Get picture from...', + name: 'get_picture_from', + desc: '', + args: [], + ); + } + + /// `Gallery` + String get gallery { + return Intl.message( + 'Gallery', + name: 'gallery', + desc: '', + args: [], + ); + } + + /// `Camera` + String get camera { + return Intl.message( + 'Camera', + name: 'camera', + desc: '', + args: [], + ); + } + + /// `Please select` + String get please_select { + return Intl.message( + 'Please select', + name: 'please_select', + desc: '', + args: [], + ); + } + + /// `Please select an image` + String get please_select_an_image { + return Intl.message( + 'Please select an image', + name: 'please_select_an_image', + desc: '', + args: [], + ); + } + + /// `Select` + String get select { + return Intl.message( + 'Select', + name: 'select', + desc: '', + args: [], + ); + } + + /// `Error occurred while reading the file.` + String get error_read_file { + return Intl.message( + 'Error occurred while reading the file.', + name: 'error_read_file', + desc: '', + args: [], + ); + } + + /// `Change nickname` + String get change_nickname { + return Intl.message( + 'Change nickname', + name: 'change_nickname', + desc: '', + args: [], + ); + } + + /// `Enter new nickname` + String get enter_new_nickname { + return Intl.message( + 'Enter new nickname', + name: 'enter_new_nickname', + desc: '', + args: [], + ); + } + + /// `Nickname is required` + String get nickname_is_required { + return Intl.message( + 'Nickname is required', + name: 'nickname_is_required', + desc: '', + args: [], + ); + } + + /// `Submit to change` + String get submit_to_change { + return Intl.message( + 'Submit to change', + name: 'submit_to_change', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to logout?` + String get are_you_sure_to_logout { + return Intl.message( + 'Are you sure you want to logout?', + name: 'are_you_sure_to_logout', + desc: '', + args: [], + ); + } + + /// `Yes! I am sure.` + String get yes_i_am_sure { + return Intl.message( + 'Yes! I am sure.', + name: 'yes_i_am_sure', + desc: '', + args: [], + ); + } + + /// `Registration` + String get registration { + return Intl.message( + 'Registration', + name: 'registration', + desc: '', + args: [], + ); + } + + /// `User registration` + String get user_registration { + return Intl.message( + 'User registration', + name: 'user_registration', + desc: '', + args: [], + ); + } + + /// `Mobile or email` + String get mobile_or_email { + return Intl.message( + 'Mobile or email', + name: 'mobile_or_email', + desc: '', + args: [], + ); + } + + /// `Mobile or email is required` + String get mobile_or_email_is_required { + return Intl.message( + 'Mobile or email is required', + name: 'mobile_or_email_is_required', + desc: '', + args: [], + ); + } + + /// `Verification code` + String get verification_code { + return Intl.message( + 'Verification code', + name: 'verification_code', + desc: '', + args: [], + ); + } + + /// `Verification code is required` + String get verification_code_is_required { + return Intl.message( + 'Verification code is required', + name: 'verification_code_is_required', + desc: '', + args: [], + ); + } + + /// `Register` + String get register { + return Intl.message( + 'Register', + name: 'register', + desc: '', + args: [], + ); + } + + /// `Get code` + String get get_code { + return Intl.message( + 'Get code', + name: 'get_code', + desc: '', + args: [], + ); + } + + /// `Verification code has been sent.` + String get verification_code_sent { + return Intl.message( + 'Verification code has been sent.', + name: 'verification_code_sent', + desc: '', + args: [], + ); + } + + /// `Enter mobile or email` + String get enter_mobile_or_email { + return Intl.message( + 'Enter mobile or email', + name: 'enter_mobile_or_email', + desc: '', + args: [], + ); + } + + /// `Retry after {second}s` + String get_code_token(Object second) { + return Intl.message( + 'Retry after ${second}s', + name: 'get_code_token', + desc: '', + args: [second], + ); + } + + /// `Get code again` + String get get_code_again { + return Intl.message( + 'Get code again', + name: 'get_code_again', + desc: '', + args: [], + ); + } + + /// `Credit or debit card` + String get credit_debit_card { + return Intl.message( + 'Credit or debit card', + name: 'credit_debit_card', + desc: '', + args: [], + ); + } + + /// `Credit card` + String get credit_card { + return Intl.message( + 'Credit card', + name: 'credit_card', + desc: '', + args: [], + ); + } + + /// `Alipay` + String get alipay { + return Intl.message( + 'Alipay', + name: 'alipay', + desc: '', + args: [], + ); + } + + /// `Wechat pay` + String get wechatpay { + return Intl.message( + 'Wechat pay', + name: 'wechatpay', + desc: '', + args: [], + ); + } + + /// `Paypal` + String get paypal { + return Intl.message( + 'Paypal', + name: 'paypal', + desc: '', + args: [], + ); + } + + /// `Pay on delivery or pickup` + String get pay_on_deliery_pickup { + return Intl.message( + 'Pay on delivery or pickup', + name: 'pay_on_deliery_pickup', + desc: '', + args: [], + ); + } + + /// `Pay on delivery` + String get pay_on_deliery { + return Intl.message( + 'Pay on delivery', + name: 'pay_on_deliery', + desc: '', + args: [], + ); + } + + /// `Set password` + String get set_password { + return Intl.message( + 'Set password', + name: 'set_password', + desc: '', + args: [], + ); + } + + /// `User account created success` + String get user_account_created_success { + return Intl.message( + 'User account created success', + name: 'user_account_created_success', + desc: '', + args: [], + ); + } + + /// `Forgot password` + String get forgot_password { + return Intl.message( + 'Forgot password', + name: 'forgot_password', + desc: '', + args: [], + ); + } + + /// `Reset password` + String get reset_password { + return Intl.message( + 'Reset password', + name: 'reset_password', + desc: '', + args: [], + ); + } + + /// `Verify` + String get verify { + return Intl.message( + 'Verify', + name: 'verify', + desc: '', + args: [], + ); + } + + /// `Reset password success` + String get reset_password_success { + return Intl.message( + 'Reset password success', + name: 'reset_password_success', + desc: '', + args: [], + ); + } + + /// `Change mobile number` + String get change_mobile { + return Intl.message( + 'Change mobile number', + name: 'change_mobile', + desc: '', + args: [], + ); + } + + /// `Change email address` + String get change_email { + return Intl.message( + 'Change email address', + name: 'change_email', + desc: '', + args: [], + ); + } + + /// `Mobile is required` + String get mobile_is_required { + return Intl.message( + 'Mobile is required', + name: 'mobile_is_required', + desc: '', + args: [], + ); + } + + /// `Email is required` + String get email_is_required { + return Intl.message( + 'Email is required', + name: 'email_is_required', + desc: '', + args: [], + ); + } + + /// `The mobile number is the same as the current one.` + String get the_mobile_number_is_same_as_current { + return Intl.message( + 'The mobile number is the same as the current one.', + name: 'the_mobile_number_is_same_as_current', + desc: '', + args: [], + ); + } + + /// `The email is the same as the current one.` + String get the_email_is_same_as_current { + return Intl.message( + 'The email is the same as the current one.', + name: 'the_email_is_same_as_current', + desc: '', + args: [], + ); + } + + /// `Updated success` + String get update_success { + return Intl.message( + 'Updated success', + name: 'update_success', + desc: '', + args: [], + ); + } + + /// `Edit address` + String get edit_address { + return Intl.message( + 'Edit address', + name: 'edit_address', + desc: '', + args: [], + ); + } + + /// `You have not entered any address.` + String get no_address_yet { + return Intl.message( + 'You have not entered any address.', + name: 'no_address_yet', + desc: '', + args: [], + ); + } + + /// `Contact name` + String get contact_name { + return Intl.message( + 'Contact name', + name: 'contact_name', + desc: '', + args: [], + ); + } + + /// `Contact name is required` + String get contact_name_is_required { + return Intl.message( + 'Contact name is required', + name: 'contact_name_is_required', + desc: '', + args: [], + ); + } + + /// `Mr.` + String get mr { + return Intl.message( + 'Mr.', + name: 'mr', + desc: '', + args: [], + ); + } + + /// `Ms.` + String get ms { + return Intl.message( + 'Ms.', + name: 'ms', + desc: '', + args: [], + ); + } + + /// `Mobile number` + String get mobile_phone_number { + return Intl.message( + 'Mobile number', + name: 'mobile_phone_number', + desc: '', + args: [], + ); + } + + /// `Mobile number is required` + String get mobile_phone_number_is_required { + return Intl.message( + 'Mobile number is required', + name: 'mobile_phone_number_is_required', + desc: '', + args: [], + ); + } + + /// `Street line 1` + String get street_line_1 { + return Intl.message( + 'Street line 1', + name: 'street_line_1', + desc: '', + args: [], + ); + } + + /// `Street line 1 is required` + String get street_line_1_is_required { + return Intl.message( + 'Street line 1 is required', + name: 'street_line_1_is_required', + desc: '', + args: [], + ); + } + + /// `Street line 2` + String get street_line_2 { + return Intl.message( + 'Street line 2', + name: 'street_line_2', + desc: '', + args: [], + ); + } + + /// `City` + String get city { + return Intl.message( + 'City', + name: 'city', + desc: '', + args: [], + ); + } + + /// `City is required` + String get city_is_required { + return Intl.message( + 'City is required', + name: 'city_is_required', + desc: '', + args: [], + ); + } + + /// `Province` + String get province { + return Intl.message( + 'Province', + name: 'province', + desc: '', + args: [], + ); + } + + /// `Postal code` + String get postal_code { + return Intl.message( + 'Postal code', + name: 'postal_code', + desc: '', + args: [], + ); + } + + /// `Postal code is required` + String get postal_code_is_required { + return Intl.message( + 'Postal code is required', + name: 'postal_code_is_required', + desc: '', + args: [], + ); + } + + /// `Optional Info.` + String get optional_information { + return Intl.message( + 'Optional Info.', + name: 'optional_information', + desc: '', + args: [], + ); + } + + /// `Email is not valid` + String get email_is_not_valid { + return Intl.message( + 'Email is not valid', + name: 'email_is_not_valid', + desc: '', + args: [], + ); + } + + /// `Fax` + String get fax { + return Intl.message( + 'Fax', + name: 'fax', + desc: '', + args: [], + ); + } + + /// `Delete` + String get delete { + return Intl.message( + 'Delete', + name: 'delete', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to delete the address?` + String get are_you_sure_to_delete_the_address { + return Intl.message( + 'Are you sure you want to delete the address?', + name: 'are_you_sure_to_delete_the_address', + desc: '', + args: [], + ); + } + + /// `Save` + String get save { + return Intl.message( + 'Save', + name: 'save', + desc: '', + args: [], + ); + } + + /// `The address has been deleted.` + String get the_address_has_been_deleted { + return Intl.message( + 'The address has been deleted.', + name: 'the_address_has_been_deleted', + desc: '', + args: [], + ); + } + + /// `Search place` + String get search_place { + return Intl.message( + 'Search place', + name: 'search_place', + desc: '', + args: [], + ); + } + + /// `Enter delivery address` + String get enter_delivery_address { + return Intl.message( + 'Enter delivery address', + name: 'enter_delivery_address', + desc: '', + args: [], + ); + } + + /// `No address found. Tap here to skip address lookup.` + String get empty_address_change_keyword { + return Intl.message( + 'No address found. Tap here to skip address lookup.', + name: 'empty_address_change_keyword', + desc: '', + args: [], + ); + } + + /// `No data found, please change keyword and try again.` + String get empty_result_change_keyword { + return Intl.message( + 'No data found, please change keyword and try again.', + name: 'empty_result_change_keyword', + desc: '', + args: [], + ); + } + + /// `New address` + String get new_address { + return Intl.message( + 'New address', + name: 'new_address', + desc: '', + args: [], + ); + } + + /// `Please enter the old password and your desired new password.` + String get change_password_desc { + return Intl.message( + 'Please enter the old password and your desired new password.', + name: 'change_password_desc', + desc: '', + args: [], + ); + } + + /// `Back` + String get back { + return Intl.message( + 'Back', + name: 'back', + desc: '', + args: [], + ); + } + + /// `Revise user profile` + String get revise_user_profile { + return Intl.message( + 'Revise user profile', + name: 'revise_user_profile', + desc: '', + args: [], + ); + } + + /// `Learn more about MiniPOS` + String get learn_more_about_minipos { + return Intl.message( + 'Learn more about MiniPOS', + name: 'learn_more_about_minipos', + desc: '', + args: [], + ); + } + + /// `Learn more about iGoShow` + String get learn_more_about_igoshow { + return Intl.message( + 'Learn more about iGoShow', + name: 'learn_more_about_igoshow', + desc: '', + args: [], + ); + } + + /// `Downloads` + String get downloads { + return Intl.message( + 'Downloads', + name: 'downloads', + desc: '', + args: [], + ); + } + + /// `Please enter your mobile phone number then click the 'Get code' button to get the validation code.` + String get user_registration_desc { + return Intl.message( + 'Please enter your mobile phone number then click the \'Get code\' button to get the validation code.', + name: 'user_registration_desc', + desc: '', + args: [], + ); + } + + /// `Enter the Email or mobile number you used when registering, and then click the 'Get code' button to get the validation code.` + String get forgot_password_description { + return Intl.message( + 'Enter the Email or mobile number you used when registering, and then click the \'Get code\' button to get the validation code.', + name: 'forgot_password_description', + desc: '', + args: [], + ); + } + + /// `Enter your new mobile number, and then click 'Get code' button to get the validation code.` + String get change_mobile_desc { + return Intl.message( + 'Enter your new mobile number, and then click \'Get code\' button to get the validation code.', + name: 'change_mobile_desc', + desc: '', + args: [], + ); + } + + /// `Enter your new Email, and then click 'Get code' button to get the validation code.` + String get change_email_desc { + return Intl.message( + 'Enter your new Email, and then click \'Get code\' button to get the validation code.', + name: 'change_email_desc', + desc: '', + args: [], + ); + } + + /// `Add new address` + String get add_new_address { + return Intl.message( + 'Add new address', + name: 'add_new_address', + desc: '', + args: [], + ); + } + + /// `There is no ticket yet. If you have any question/issue please tap the plus icon above to create a ticket.` + String get no_ticket_yet { + return Intl.message( + 'There is no ticket yet. If you have any question/issue please tap the plus icon above to create a ticket.', + name: 'no_ticket_yet', + desc: '', + args: [], + ); + } + + /// `Today {time}` + String today_with_time(Object time) { + return Intl.message( + 'Today $time', + name: 'today_with_time', + desc: '', + args: [time], + ); + } + + /// `Tomorrow {time}` + String tomorrow_with_time(Object time) { + return Intl.message( + 'Tomorrow $time', + name: 'tomorrow_with_time', + desc: '', + args: [time], + ); + } + + /// `{num} follow-ups` + String followups_token(Object num) { + return Intl.message( + '$num follow-ups', + name: 'followups_token', + desc: '', + args: [num], + ); + } + + /// `Pull up to load more` + String get pull_up_to_load_more { + return Intl.message( + 'Pull up to load more', + name: 'pull_up_to_load_more', + desc: '', + args: [], + ); + } + + /// `Load failed, please retry` + String get load_failed_retry { + return Intl.message( + 'Load failed, please retry', + name: 'load_failed_retry', + desc: '', + args: [], + ); + } + + /// `Release to load more` + String get release_to_load_more { + return Intl.message( + 'Release to load more', + name: 'release_to_load_more', + desc: '', + args: [], + ); + } + + /// `No more record` + String get no_more_record { + return Intl.message( + 'No more record', + name: 'no_more_record', + desc: '', + args: [], + ); + } + + /// `Create a new ticket` + String get add_new_ticket { + return Intl.message( + 'Create a new ticket', + name: 'add_new_ticket', + desc: '', + args: [], + ); + } + + /// `New ticket` + String get new_ticket { + return Intl.message( + 'New ticket', + name: 'new_ticket', + desc: '', + args: [], + ); + } + + /// `Please enter your question.` + String get add_new_ticket_desc { + return Intl.message( + 'Please enter your question.', + name: 'add_new_ticket_desc', + desc: '', + args: [], + ); + } + + /// `Your question/issue` + String get your_question_issue { + return Intl.message( + 'Your question/issue', + name: 'your_question_issue', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to remove the picture?` + String get are_you_sure_to_remove_the_picture { + return Intl.message( + 'Are you sure you want to remove the picture?', + name: 'are_you_sure_to_remove_the_picture', + desc: '', + args: [], + ); + } + + /// `Attach pictures` + String get attach_pictures { + return Intl.message( + 'Attach pictures', + name: 'attach_pictures', + desc: '', + args: [], + ); + } + + /// `Upload pictures(screen shot) to make your question clearer.` + String get attach_pictures_desc { + return Intl.message( + 'Upload pictures(screen shot) to make your question clearer.', + name: 'attach_pictures_desc', + desc: '', + args: [], + ); + } + + /// `Ticket has been created successfully.` + String get ticket_created_success { + return Intl.message( + 'Ticket has been created successfully.', + name: 'ticket_created_success', + desc: '', + args: [], + ); + } + + /// `View ticket` + String get view_ticket { + return Intl.message( + 'View ticket', + name: 'view_ticket', + desc: '', + args: [], + ); + } + + /// `Ticket #{num}` + String ticket_number_token(Object num) { + return Intl.message( + 'Ticket #$num', + name: 'ticket_number_token', + desc: '', + args: [num], + ); + } + + /// `Close` + String get close { + return Intl.message( + 'Close', + name: 'close', + desc: '', + args: [], + ); + } + + /// `Follow ups` + String get follow_ups { + return Intl.message( + 'Follow ups', + name: 'follow_ups', + desc: '', + args: [], + ); + } + + /// `Reply` + String get reply { + return Intl.message( + 'Reply', + name: 'reply', + desc: '', + args: [], + ); + } + + /// `The ticket has been closed. If you still have questions, please create a new ticket.` + String get the_ticket_is_closed_desc { + return Intl.message( + 'The ticket has been closed. If you still have questions, please create a new ticket.', + name: 'the_ticket_is_closed_desc', + desc: '', + args: [], + ); + } + + /// `Your reply` + String get your_reply { + return Intl.message( + 'Your reply', + name: 'your_reply', + desc: '', + args: [], + ); + } + + /// `There is no blog to show yet.` + String get no_blog_yet { + return Intl.message( + 'There is no blog to show yet.', + name: 'no_blog_yet', + desc: '', + args: [], + ); + } + + /// `View blog` + String get view_blog { + return Intl.message( + 'View blog', + name: 'view_blog', + desc: '', + args: [], + ); + } + + /// `Enter the new password you desired twice.` + String get reset_password_desc { + return Intl.message( + 'Enter the new password you desired twice.', + name: 'reset_password_desc', + desc: '', + args: [], + ); + } + + /// `Enter the password you desired twice.` + String get set_password_desc { + return Intl.message( + 'Enter the password you desired twice.', + name: 'set_password_desc', + desc: '', + args: [], + ); + } + + /// `{minprice} more` + String order_more(Object minprice) { + return Intl.message( + '$minprice more', + name: 'order_more', + desc: '', + args: [minprice], + ); + } + + /// `Select options` + String get select_options { + return Intl.message( + 'Select options', + name: 'select_options', + desc: '', + args: [], + ); + } + + /// `Previous` + String get previous { + return Intl.message( + 'Previous', + name: 'previous', + desc: '', + args: [], + ); + } + + /// `Next` + String get next { + return Intl.message( + 'Next', + name: 'next', + desc: '', + args: [], + ); + } + + /// `Finish` + String get finish { + return Intl.message( + 'Finish', + name: 'finish', + desc: '', + args: [], + ); + } + + /// `Please choice {optionName}.` + String radio_option_select_token(Object optionName) { + return Intl.message( + 'Please choice $optionName.', + name: 'radio_option_select_token', + desc: '', + args: [optionName], + ); + } + + /// `Option is required. Select an option then tap Next.` + String get radio_option_is_required { + return Intl.message( + 'Option is required. Select an option then tap Next.', + name: 'radio_option_is_required', + desc: '', + args: [], + ); + } + + /// `Optional. You can tap Next without selection.` + String get radio_option_is_optional { + return Intl.message( + 'Optional. You can tap Next without selection.', + name: 'radio_option_is_optional', + desc: '', + args: [], + ); + } + + /// `Multiple choice {optionName}` + String check_option_select_token(Object optionName) { + return Intl.message( + 'Multiple choice $optionName', + name: 'check_option_select_token', + desc: '', + args: [optionName], + ); + } + + /// `Option is required. Select at lease an option then tap Next.` + String get check_option_is_required { + return Intl.message( + 'Option is required. Select at lease an option then tap Next.', + name: 'check_option_is_required', + desc: '', + args: [], + ); + } + + /// `Optional. You can tap Next without selection.` + String get check_option_is_optional { + return Intl.message( + 'Optional. You can tap Next without selection.', + name: 'check_option_is_optional', + desc: '', + args: [], + ); + } + + /// `Check out` + String get checkout { + return Intl.message( + 'Check out', + name: 'checkout', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to remove the item?` + String get are_you_sure_to_remove_the_item { + return Intl.message( + 'Are you sure you want to remove the item?', + name: 'are_you_sure_to_remove_the_item', + desc: '', + args: [], + ); + } + + /// `Out of stock` + String get out_of_stock { + return Intl.message( + 'Out of stock', + name: 'out_of_stock', + desc: '', + args: [], + ); + } + + /// `The product quantity is insufficient.` + String get product_insufficient { + return Intl.message( + 'The product quantity is insufficient.', + name: 'product_insufficient', + desc: '', + args: [], + ); + } + + /// `Your basket is empty.` + String get your_basket_is_empty { + return Intl.message( + 'Your basket is empty.', + name: 'your_basket_is_empty', + desc: '', + args: [], + ); + } + + /// `Empty basket` + String get empty_basket { + return Intl.message( + 'Empty basket', + name: 'empty_basket', + desc: '', + args: [], + ); + } + + /// `Delivery fee {shippingfee}+` + String delivery_fee(Object shippingfee) { + return Intl.message( + 'Delivery fee $shippingfee+', + name: 'delivery_fee', + desc: '', + args: [shippingfee], + ); + } + + /// `Are you sure you want to empty the basket?` + String get are_you_sure_to_empty_basket { + return Intl.message( + 'Are you sure you want to empty the basket?', + name: 'are_you_sure_to_empty_basket', + desc: '', + args: [], + ); + } + + /// `Search product` + String get search_product { + return Intl.message( + 'Search product', + name: 'search_product', + desc: '', + args: [], + ); + } + + /// `Search products` + String get search_products { + return Intl.message( + 'Search products', + name: 'search_products', + desc: '', + args: [], + ); + } + + /// `Enter product keyword` + String get enter_product_keyword { + return Intl.message( + 'Enter product keyword', + name: 'enter_product_keyword', + desc: '', + args: [], + ); + } + + /// `Promotions` + String get promotions { + return Intl.message( + 'Promotions', + name: 'promotions', + desc: '', + args: [], + ); + } + + /// `Products` + String get products { + return Intl.message( + 'Products', + name: 'products', + desc: '', + args: [], + ); + } + + /// `Comments` + String get comments { + return Intl.message( + 'Comments', + name: 'comments', + desc: '', + args: [], + ); + } + + /// `No comments yet` + String get no_comments_yet { + return Intl.message( + 'No comments yet', + name: 'no_comments_yet', + desc: '', + args: [], + ); + } + + /// `Response from store` + String get response_from_store { + return Intl.message( + 'Response from store', + name: 'response_from_store', + desc: '', + args: [], + ); + } + + /// `Show more` + String get show_more { + return Intl.message( + 'Show more', + name: 'show_more', + desc: '', + args: [], + ); + } + + /// `Show less` + String get show_less { + return Intl.message( + 'Show less', + name: 'show_less', + desc: '', + args: [], + ); + } + + /// `{hours, plural, one{1 hr} other{{hours} hrs}}` + String hour_token(num hours) { + return Intl.plural( + hours, + one: '1 hr', + other: '$hours hrs', + name: 'hour_token', + desc: '', + args: [hours], + ); + } + + /// `{minutes, plural, one{1 min} other{{minutes} mins}}` + String minute_token(num minutes) { + return Intl.plural( + minutes, + one: '1 min', + other: '$minutes mins', + name: 'minute_token', + desc: '', + args: [minutes], + ); + } + + /// `${minamount}+` + String min_order_amount_token(Object minamount) { + return Intl.message( + '\$$minamount+', + name: 'min_order_amount_token', + desc: '', + args: [minamount], + ); + } + + /// `Delivery ${shipfee}+` + String min_shipping_fee(Object shipfee) { + return Intl.message( + 'Delivery \$$shipfee+', + name: 'min_shipping_fee', + desc: '', + args: [shipfee], + ); + } + + /// `Store introduction` + String get store_introduction { + return Intl.message( + 'Store introduction', + name: 'store_introduction', + desc: '', + args: [], + ); + } + + /// `Store policy` + String get store_policy { + return Intl.message( + 'Store policy', + name: 'store_policy', + desc: '', + args: [], + ); + } + + /// ` {sold_qty} sold/mo` + String sold_per_month_token(Object sold_qty) { + return Intl.message( + ' $sold_qty sold/mo', + name: 'sold_per_month_token', + desc: '', + args: [sold_qty], + ); + } + + /// `Detail` + String get detail { + return Intl.message( + 'Detail', + name: 'detail', + desc: '', + args: [], + ); + } + + /// `Specification` + String get specification { + return Intl.message( + 'Specification', + name: 'specification', + desc: '', + args: [], + ); + } + + /// `Weight: {weight}` + String weight_token(Object weight) { + return Intl.message( + 'Weight: $weight', + name: 'weight_token', + desc: '', + args: [weight], + ); + } + + /// `Dimentions: {length}(L)x{width}(W)x{height}(H)` + String dimentions_token(Object length, Object width, Object height) { + return Intl.message( + 'Dimentions: $length(L)x$width(W)x$height(H)', + name: 'dimentions_token', + desc: '', + args: [length, width, height], + ); + } + + /// `Loading...` + String get loading { + return Intl.message( + 'Loading...', + name: 'loading', + desc: '', + args: [], + ); + } + + /// `Store closed` + String get store_closed { + return Intl.message( + 'Store closed', + name: 'store_closed', + desc: '', + args: [], + ); + } + + /// `Closed` + String get closed { + return Intl.message( + 'Closed', + name: 'closed', + desc: '', + args: [], + ); + } + + /// `Order now and delivery start at {hour}:00.` + String book_now_delivery_later_token(Object hour) { + return Intl.message( + 'Order now and delivery start at $hour:00.', + name: 'book_now_delivery_later_token', + desc: '', + args: [hour], + ); + } + + /// `Order confirmation` + String get confirm_order { + return Intl.message( + 'Order confirmation', + name: 'confirm_order', + desc: '', + args: [], + ); + } + + /// `Delivery` + String get delivery { + return Intl.message( + 'Delivery', + name: 'delivery', + desc: '', + args: [], + ); + } + + /// `Pickup` + String get pickup { + return Intl.message( + 'Pickup', + name: 'pickup', + desc: '', + args: [], + ); + } + + /// `Delivery ASAP` + String get delivery_now { + return Intl.message( + 'Delivery ASAP', + name: 'delivery_now', + desc: '', + args: [], + ); + } + + /// `End of the list` + String get end_of_the_list { + return Intl.message( + 'End of the list', + name: 'end_of_the_list', + desc: '', + args: [], + ); + } + + /// `Hot sale` + String get hot_sale { + return Intl.message( + 'Hot sale', + name: 'hot_sale', + desc: '', + args: [], + ); + } + + /// `Featured` + String get featured_product { + return Intl.message( + 'Featured', + name: 'featured_product', + desc: '', + args: [], + ); + } + + /// `OCR scan` + String get ocr_scan { + return Intl.message( + 'OCR scan', + name: 'ocr_scan', + desc: '', + args: [], + ); + } + + /// `From camera` + String get from_camera { + return Intl.message( + 'From camera', + name: 'from_camera', + desc: '', + args: [], + ); + } + + /// `From gallery` + String get from_gallery { + return Intl.message( + 'From gallery', + name: 'from_gallery', + desc: '', + args: [], + ); + } + + /// `This feature is not available on the web. Please install the App version.` + String get feature_not_available_web { + return Intl.message( + 'This feature is not available on the web. Please install the App version.', + name: 'feature_not_available_web', + desc: '', + args: [], + ); + } + + /// `Document type` + String get document_type { + return Intl.message( + 'Document type', + name: 'document_type', + desc: '', + args: [], + ); + } + + /// `UberEats receipt` + String get ubereats_receipt { + return Intl.message( + 'UberEats receipt', + name: 'ubereats_receipt', + desc: '', + args: [], + ); + } + + /// `Business card` + String get business_card { + return Intl.message( + 'Business card', + name: 'business_card', + desc: '', + args: [], + ); + } + + /// `Select a document type` + String get select_document_type { + return Intl.message( + 'Select a document type', + name: 'select_document_type', + desc: '', + args: [], + ); + } + + /// `English` + String get english { + return Intl.message( + 'English', + name: 'english', + desc: '', + args: [], + ); + } + + /// `Chinese simplified` + String get chinese_simplified { + return Intl.message( + 'Chinese simplified', + name: 'chinese_simplified', + desc: '', + args: [], + ); + } + + /// `Chinese tranditional` + String get chinese_traditional { + return Intl.message( + 'Chinese tranditional', + name: 'chinese_traditional', + desc: '', + args: [], + ); + } + + /// `Document language` + String get document_langage { + return Intl.message( + 'Document language', + name: 'document_langage', + desc: '', + args: [], + ); + } + + /// `Select a language` + String get select_document_lanuage { + return Intl.message( + 'Select a language', + name: 'select_document_lanuage', + desc: '', + args: [], + ); + } + + /// `Submit to generate` + String get submit_to_generate { + return Intl.message( + 'Submit to generate', + name: 'submit_to_generate', + desc: '', + args: [], + ); + } + + /// `Add to basket` + String get add_to_basket { + return Intl.message( + 'Add to basket', + name: 'add_to_basket', + desc: '', + args: [], + ); + } + + /// `No shipping method available.` + String get no_delivery_method { + return Intl.message( + 'No shipping method available.', + name: 'no_delivery_method', + desc: '', + args: [], + ); + } + + /// `The store is under renovation` + String get under_renovation { + return Intl.message( + 'The store is under renovation', + name: 'under_renovation', + desc: '', + args: [], + ); + } + + /// `Pay now` + String get pay_now { + return Intl.message( + 'Pay now', + name: 'pay_now', + desc: '', + args: [], + ); + } + + /// `Canada Post` + String get canada_post { + return Intl.message( + 'Canada Post', + name: 'canada_post', + desc: '', + args: [], + ); + } + + /// `Canada Post delivery` + String get canada_post_delivery { + return Intl.message( + 'Canada Post delivery', + name: 'canada_post_delivery', + desc: '', + args: [], + ); + } + + /// `Table#: {num}` + String table_token(Object num) { + return Intl.message( + 'Table#: $num', + name: 'table_token', + desc: '', + args: [num], + ); + } + + /// `Number of people` + String get number_of_people { + return Intl.message( + 'Number of people', + name: 'number_of_people', + desc: '', + args: [], + ); + } + + /// `Please pickup at` + String get pickup_at { + return Intl.message( + 'Please pickup at', + name: 'pickup_at', + desc: '', + args: [], + ); + } + + /// `Delivery time` + String get delivery_unavailable { + return Intl.message( + 'Delivery time', + name: 'delivery_unavailable', + desc: '', + args: [], + ); + } + + /// `Select delivery time` + String get select_delivery_time { + return Intl.message( + 'Select delivery time', + name: 'select_delivery_time', + desc: '', + args: [], + ); + } + + /// `Credit/Coupon` + String get credit_coupon { + return Intl.message( + 'Credit/Coupon', + name: 'credit_coupon', + desc: '', + args: [], + ); + } + + /// `Subtotal: {subtotal}` + String subtotal_token(Object subtotal) { + return Intl.message( + 'Subtotal: $subtotal', + name: 'subtotal_token', + desc: '', + args: [subtotal], + ); + } + + /// `Subtotal` + String get subtotal { + return Intl.message( + 'Subtotal', + name: 'subtotal', + desc: '', + args: [], + ); + } + + /// `{name}({rate}%)` + String extra_fee_token(Object name, Object rate) { + return Intl.message( + '$name($rate%)', + name: 'extra_fee_token', + desc: '', + args: [name, rate], + ); + } + + /// `Total` + String get total { + return Intl.message( + 'Total', + name: 'total', + desc: '', + args: [], + ); + } + + /// `We don't deliver at this time.` + String get checkout_no_deliver { + return Intl.message( + 'We don\'t deliver at this time.', + name: 'checkout_no_deliver', + desc: '', + args: [], + ); + } + + /// `Payment method` + String get payment_method { + return Intl.message( + 'Payment method', + name: 'payment_method', + desc: '', + args: [], + ); + } + + /// `Order remark` + String get order_remark { + return Intl.message( + 'Order remark', + name: 'order_remark', + desc: '', + args: [], + ); + } + + /// `Please provide shipping address.` + String get please_provide_shipping_address { + return Intl.message( + 'Please provide shipping address.', + name: 'please_provide_shipping_address', + desc: '', + args: [], + ); + } + + /// `The address is beyond the scope of delivery.` + String get over_delivery_distance { + return Intl.message( + 'The address is beyond the scope of delivery.', + name: 'over_delivery_distance', + desc: '', + args: [], + ); + } + + /// `Pickup discount` + String get pickup_discount { + return Intl.message( + 'Pickup discount', + name: 'pickup_discount', + desc: '', + args: [], + ); + } + + /// `Service fee` + String get service_fee { + return Intl.message( + 'Service fee', + name: 'service_fee', + desc: '', + args: [], + ); + } + + /// `Pending` + String get pending { + return Intl.message( + 'Pending', + name: 'pending', + desc: '', + args: [], + ); + } + + /// `Accept` + String get order_acceipt { + return Intl.message( + 'Accept', + name: 'order_acceipt', + desc: '', + args: [], + ); + } + + /// `Processing` + String get order_processing { + return Intl.message( + 'Processing', + name: 'order_processing', + desc: '', + args: [], + ); + } + + /// `Complete` + String get order_complete { + return Intl.message( + 'Complete', + name: 'order_complete', + desc: '', + args: [], + ); + } + + /// `Cancelled` + String get order_cancelled { + return Intl.message( + 'Cancelled', + name: 'order_cancelled', + desc: '', + args: [], + ); + } + + /// `My orders` + String get my_orders { + return Intl.message( + 'My orders', + name: 'my_orders', + desc: '', + args: [], + ); + } + + /// `and {qty} items more` + String and_more_item_token(Object qty) { + return Intl.message( + 'and $qty items more', + name: 'and_more_item_token', + desc: '', + args: [qty], + ); + } + + /// `Order again` + String get order_again { + return Intl.message( + 'Order again', + name: 'order_again', + desc: '', + args: [], + ); + } + + /// `Order detail` + String get order_detail { + return Intl.message( + 'Order detail', + name: 'order_detail', + desc: '', + args: [], + ); + } + + /// `Delivery Info.` + String get delivery_info { + return Intl.message( + 'Delivery Info.', + name: 'delivery_info', + desc: '', + args: [], + ); + } + + /// `Delivery address` + String get delivery_address { + return Intl.message( + 'Delivery address', + name: 'delivery_address', + desc: '', + args: [], + ); + } + + /// `Pickup address` + String get pickup_address { + return Intl.message( + 'Pickup address', + name: 'pickup_address', + desc: '', + args: [], + ); + } + + /// `Schedule delivery` + String get schedule_delivery { + return Intl.message( + 'Schedule delivery', + name: 'schedule_delivery', + desc: '', + args: [], + ); + } + + /// `Delivery method` + String get delivery_method { + return Intl.message( + 'Delivery method', + name: 'delivery_method', + desc: '', + args: [], + ); + } + + /// `Store delivery` + String get store_delivery { + return Intl.message( + 'Store delivery', + name: 'store_delivery', + desc: '', + args: [], + ); + } + + /// `Order info.` + String get order_info { + return Intl.message( + 'Order info.', + name: 'order_info', + desc: '', + args: [], + ); + } + + /// `Order No.` + String get order_number { + return Intl.message( + 'Order No.', + name: 'order_number', + desc: '', + args: [], + ); + } + + /// `The order amount does not meet the minimum requirements.` + String get amount_not_meet { + return Intl.message( + 'The order amount does not meet the minimum requirements.', + name: 'amount_not_meet', + desc: '', + args: [], + ); + } + + /// `Please select Canada Post shipping rate.` + String get select_canada_post_shipping_rate { + return Intl.message( + 'Please select Canada Post shipping rate.', + name: 'select_canada_post_shipping_rate', + desc: '', + args: [], + ); + } + + /// `Mon` + String get mon { + return Intl.message( + 'Mon', + name: 'mon', + desc: '', + args: [], + ); + } + + /// `Monday` + String get monday { + return Intl.message( + 'Monday', + name: 'monday', + desc: '', + args: [], + ); + } + + /// `Tue` + String get tue { + return Intl.message( + 'Tue', + name: 'tue', + desc: '', + args: [], + ); + } + + /// `Tuesday` + String get tuesday { + return Intl.message( + 'Tuesday', + name: 'tuesday', + desc: '', + args: [], + ); + } + + /// `Wed` + String get wed { + return Intl.message( + 'Wed', + name: 'wed', + desc: '', + args: [], + ); + } + + /// `Wednesday` + String get wednesday { + return Intl.message( + 'Wednesday', + name: 'wednesday', + desc: '', + args: [], + ); + } + + /// `Thu` + String get thu { + return Intl.message( + 'Thu', + name: 'thu', + desc: '', + args: [], + ); + } + + /// `Thursday` + String get thursday { + return Intl.message( + 'Thursday', + name: 'thursday', + desc: '', + args: [], + ); + } + + /// `Fri` + String get fri { + return Intl.message( + 'Fri', + name: 'fri', + desc: '', + args: [], + ); + } + + /// `Friday` + String get friday { + return Intl.message( + 'Friday', + name: 'friday', + desc: '', + args: [], + ); + } + + /// `Sat` + String get sat { + return Intl.message( + 'Sat', + name: 'sat', + desc: '', + args: [], + ); + } + + /// `Saturday` + String get saturday { + return Intl.message( + 'Saturday', + name: 'saturday', + desc: '', + args: [], + ); + } + + /// `Sun` + String get sun { + return Intl.message( + 'Sun', + name: 'sun', + desc: '', + args: [], + ); + } + + /// `Sunday` + String get sunday { + return Intl.message( + 'Sunday', + name: 'sunday', + desc: '', + args: [], + ); + } + + /// `Today` + String get today { + return Intl.message( + 'Today', + name: 'today', + desc: '', + args: [], + ); + } + + /// `Tomorrow` + String get tomorrow { + return Intl.message( + 'Tomorrow', + name: 'tomorrow', + desc: '', + args: [], + ); + } + + /// `Delivery time cannot be determined. Usually we will send it out as soon as possible. If you wish to deliver at a certain time, please state in the remarks.` + String get shipping_time_will_schedule { + return Intl.message( + 'Delivery time cannot be determined. Usually we will send it out as soon as possible. If you wish to deliver at a certain time, please state in the remarks.', + name: 'shipping_time_will_schedule', + desc: '', + args: [], + ); + } + + /// `Select a payment method` + String get select_a_payment_method { + return Intl.message( + 'Select a payment method', + name: 'select_a_payment_method', + desc: '', + args: [], + ); + } + + /// `No payment method specified. Please pay on delivery or pickup.` + String get payment_method_not_set { + return Intl.message( + 'No payment method specified. Please pay on delivery or pickup.', + name: 'payment_method_not_set', + desc: '', + args: [], + ); + } + + /// `No coupon available` + String get no_coupon_available { + return Intl.message( + 'No coupon available', + name: 'no_coupon_available', + desc: '', + args: [], + ); + } + + /// `{discount}%off` + String percentage_discount_token(Object discount) { + return Intl.message( + '$discount%off', + name: 'percentage_discount_token', + desc: '', + args: [discount], + ); + } + + /// `-${discount}` + String discount_amount_token(Object discount) { + return Intl.message( + '-\$$discount', + name: 'discount_amount_token', + desc: '', + args: [discount], + ); + } + + /// `Pick a coupon` + String get pick_a_coupon { + return Intl.message( + 'Pick a coupon', + name: 'pick_a_coupon', + desc: '', + args: [], + ); + } + + /// `Do not redeem` + String get dont_use { + return Intl.message( + 'Do not redeem', + name: 'dont_use', + desc: '', + args: [], + ); + } + + /// `-${amount}({discount}%off)` + String percentage_discount_token2(Object amount, Object discount) { + return Intl.message( + '-\$$amount($discount%off)', + name: 'percentage_discount_token2', + desc: '', + args: [amount, discount], + ); + } + + /// `Type your order remark` + String get type_your_order_remark { + return Intl.message( + 'Type your order remark', + name: 'type_your_order_remark', + desc: '', + args: [], + ); + } + + /// `Quick input` + String get quick_input { + return Intl.message( + 'Quick input', + name: 'quick_input', + desc: '', + args: [], + ); + } + + /// `Light tase` + String get light_tase { + return Intl.message( + 'Light tase', + name: 'light_tase', + desc: '', + args: [], + ); + } + + /// `No spicy` + String get no_spicy { + return Intl.message( + 'No spicy', + name: 'no_spicy', + desc: '', + args: [], + ); + } + + /// `No onion` + String get no_onion { + return Intl.message( + 'No onion', + name: 'no_onion', + desc: '', + args: [], + ); + } + + /// `No pickle` + String get no_pickle { + return Intl.message( + 'No pickle', + name: 'no_pickle', + desc: '', + args: [], + ); + } + + /// `Enter coupon code` + String get enter_coupon_code { + return Intl.message( + 'Enter coupon code', + name: 'enter_coupon_code', + desc: '', + args: [], + ); + } + + /// `Please enter a coupon code.` + String get please_enter_coupon_code { + return Intl.message( + 'Please enter a coupon code.', + name: 'please_enter_coupon_code', + desc: '', + args: [], + ); + } + + /// `Get coupon` + String get get_coupon { + return Intl.message( + 'Get coupon', + name: 'get_coupon', + desc: '', + args: [], + ); + } + + /// `%OFF` + String get percent_discount { + return Intl.message( + '%OFF', + name: 'percent_discount', + desc: '', + args: [], + ); + } + + /// `Expires on {expirationDate}` + String expiration_date_token(Object expirationDate) { + return Intl.message( + 'Expires on $expirationDate', + name: 'expiration_date_token', + desc: '', + args: [expirationDate], + ); + } + + /// `No restriction` + String get no_restriction { + return Intl.message( + 'No restriction', + name: 'no_restriction', + desc: '', + args: [], + ); + } + + /// `No expiration` + String get no_expiration { + return Intl.message( + 'No expiration', + name: 'no_expiration', + desc: '', + args: [], + ); + } + + /// `Redeem` + String get redeem_coupon { + return Intl.message( + 'Redeem', + name: 'redeem_coupon', + desc: '', + args: [], + ); + } + + /// `Choose a shipping rate` + String get choose_a_shipping_rate { + return Intl.message( + 'Choose a shipping rate', + name: 'choose_a_shipping_rate', + desc: '', + args: [], + ); + } + + /// `Payment amount` + String get payment_amount { + return Intl.message( + 'Payment amount', + name: 'payment_amount', + desc: '', + args: [], + ); + } + + /// `Pay later` + String get pay_later { + return Intl.message( + 'Pay later', + name: 'pay_later', + desc: '', + args: [], + ); + } + + /// `Pay after meal` + String get pay_after_meal { + return Intl.message( + 'Pay after meal', + name: 'pay_after_meal', + desc: '', + args: [], + ); + } + + /// `Pay with existing cards` + String get pay_with_existing_cards { + return Intl.message( + 'Pay with existing cards', + name: 'pay_with_existing_cards', + desc: '', + args: [], + ); + } + + /// `Add credit card` + String get add_credit_card { + return Intl.message( + 'Add credit card', + name: 'add_credit_card', + desc: '', + args: [], + ); + } + + /// `This credit card is invalid` + String get this_credit_card_is_invalid { + return Intl.message( + 'This credit card is invalid', + name: 'this_credit_card_is_invalid', + desc: '', + args: [], + ); + } + + /// `Exp: {mon}/{yer}` + String expire_token(Object mon, Object yer) { + return Intl.message( + 'Exp: $mon/$yer', + name: 'expire_token', + desc: '', + args: [mon, yer], + ); + } + + /// `Confirmation` + String get confirmation { + return Intl.message( + 'Confirmation', + name: 'confirmation', + desc: '', + args: [], + ); + } + + /// `Are you srue you want to remove the credit card?` + String get are_you_sure_to_remove_the_card { + return Intl.message( + 'Are you srue you want to remove the credit card?', + name: 'are_you_sure_to_remove_the_card', + desc: '', + args: [], + ); + } + + /// `Payment verification` + String get payment_verification { + return Intl.message( + 'Payment verification', + name: 'payment_verification', + desc: '', + args: [], + ); + } + + /// `Payment verification code has been sent to your mobile phone {mobile}. Please enter the verification code below.` + String payment_verification_sent(Object mobile) { + return Intl.message( + 'Payment verification code has been sent to your mobile phone $mobile. Please enter the verification code below.', + name: 'payment_verification_sent', + desc: '', + args: [mobile], + ); + } + + /// `Wrong payment verification code. Please check and enter again.` + String get wrong_payment_verification_code { + return Intl.message( + 'Wrong payment verification code. Please check and enter again.', + name: 'wrong_payment_verification_code', + desc: '', + args: [], + ); + } + + /// `Pay with` + String get pay_with { + return Intl.message( + 'Pay with', + name: 'pay_with', + desc: '', + args: [], + ); + } + + /// `Pay with {method}` + String pay_with_token(Object method) { + return Intl.message( + 'Pay with $method', + name: 'pay_with_token', + desc: '', + args: [method], + ); + } + + /// `You have no orders yet` + String get you_have_no_orders_yet { + return Intl.message( + 'You have no orders yet', + name: 'you_have_no_orders_yet', + desc: '', + args: [], + ); + } + + /// `Comment` + String get comment { + return Intl.message( + 'Comment', + name: 'comment', + desc: '', + args: [], + ); + } + + /// `Delivery guy` + String get delivery_guy { + return Intl.message( + 'Delivery guy', + name: 'delivery_guy', + desc: '', + args: [], + ); + } + + /// `The delivery guy are {km} away from the customer, and it will take about {time} to deliver.` + String delivery_distance_token(Object km, Object time) { + return Intl.message( + 'The delivery guy are $km away from the customer, and it will take about $time to deliver.', + name: 'delivery_distance_token', + desc: '', + args: [km, time], + ); + } + + /// `Copy` + String get copy { + return Intl.message( + 'Copy', + name: 'copy', + desc: '', + args: [], + ); + } + + /// `Order number copied to clipboard` + String get order_number_copied_to_clipboard { + return Intl.message( + 'Order number copied to clipboard', + name: 'order_number_copied_to_clipboard', + desc: '', + args: [], + ); + } + + /// `Online payment` + String get online_payment { + return Intl.message( + 'Online payment', + name: 'online_payment', + desc: '', + args: [], + ); + } + + /// `Order datetime` + String get order_datetime { + return Intl.message( + 'Order datetime', + name: 'order_datetime', + desc: '', + args: [], + ); + } + + /// `Payment status` + String get payment_status { + return Intl.message( + 'Payment status', + name: 'payment_status', + desc: '', + args: [], + ); + } + + /// `Paid` + String get paid { + return Intl.message( + 'Paid', + name: 'paid', + desc: '', + args: [], + ); + } + + /// `Unpaid` + String get unpaid { + return Intl.message( + 'Unpaid', + name: 'unpaid', + desc: '', + args: [], + ); + } + + /// `Order fulfillment` + String get order_fulfillment { + return Intl.message( + 'Order fulfillment', + name: 'order_fulfillment', + desc: '', + args: [], + ); + } + + /// `Cancel order` + String get cancel_order { + return Intl.message( + 'Cancel order', + name: 'cancel_order', + desc: '', + args: [], + ); + } + + /// `Store` + String get store { + return Intl.message( + 'Store', + name: 'store', + desc: '', + args: [], + ); + } + + /// `Customer` + String get customer { + return Intl.message( + 'Customer', + name: 'customer', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to cancel the order?` + String get are_you_sure_to_cancel_the_order { + return Intl.message( + 'Are you sure you want to cancel the order?', + name: 'are_you_sure_to_cancel_the_order', + desc: '', + args: [], + ); + } + + /// `No` + String get no { + return Intl.message( + 'No', + name: 'no', + desc: '', + args: [], + ); + } + + /// `Yes` + String get yes { + return Intl.message( + 'Yes', + name: 'yes', + desc: '', + args: [], + ); + } + + /// `Pay` + String get pay { + return Intl.message( + 'Pay', + name: 'pay', + desc: '', + args: [], + ); + } + + /// `Tracking information will be provided when available.` + String get no_instance_delivery_desc { + return Intl.message( + 'Tracking information will be provided when available.', + name: 'no_instance_delivery_desc', + desc: '', + args: [], + ); + } + + /// `Please type your comment.` + String get comment_empty { + return Intl.message( + 'Please type your comment.', + name: 'comment_empty', + desc: '', + args: [], + ); + } + + /// `Thank you for your comment.` + String get thank_you_for_your_comment { + return Intl.message( + 'Thank you for your comment.', + name: 'thank_you_for_your_comment', + desc: '', + args: [], + ); + } + + /// `Add pictures` + String get add_pictures { + return Intl.message( + 'Add pictures', + name: 'add_pictures', + desc: '', + args: [], + ); + } + + /// `Input your comment` + String get input_your_comment { + return Intl.message( + 'Input your comment', + name: 'input_your_comment', + desc: '', + args: [], + ); + } + + /// `New comment` + String get new_comment { + return Intl.message( + 'New comment', + name: 'new_comment', + desc: '', + args: [], + ); + } + + /// `Coupons` + String get coupons { + return Intl.message( + 'Coupons', + name: 'coupons', + desc: '', + args: [], + ); + } + + /// `General coupon` + String get general_coupon { + return Intl.message( + 'General coupon', + name: 'general_coupon', + desc: '', + args: [], + ); + } + + /// `Available for order over ${value}` + String available_for_order_over_token(Object value) { + return Intl.message( + 'Available for order over \$$value', + name: 'available_for_order_over_token', + desc: '', + args: [value], + ); + } + + /// `Includes` + String get includes { + return Intl.message( + 'Includes', + name: 'includes', + desc: '', + args: [], + ); + } + + /// `By Emails` + String get by_email { + return Intl.message( + 'By Emails', + name: 'by_email', + desc: '', + args: [], + ); + } + + /// `By phone` + String get by_phone { + return Intl.message( + 'By phone', + name: 'by_phone', + desc: '', + args: [], + ); + } + + /// `Address` + String get address { + return Intl.message( + 'Address', + name: 'address', + desc: '', + args: [], + ); + } + + /// `View on Google maps` + String get view_on_google_map { + return Intl.message( + 'View on Google maps', + name: 'view_on_google_map', + desc: '', + args: [], + ); + } + + /// `Toll free: ` + String get toll_free { + return Intl.message( + 'Toll free: ', + name: 'toll_free', + desc: '', + args: [], + ); + } + + /// `Group number can be found...` + String get group_number_can_be_found { + return Intl.message( + 'Group number can be found...', + name: 'group_number_can_be_found', + desc: '', + args: [], + ); + } + + /// `Group number` + String get group_number { + return Intl.message( + 'Group number', + name: 'group_number', + desc: '', + args: [], + ); + } + + /// `Please enter your group number.` + String get please_enter_group_number { + return Intl.message( + 'Please enter your group number.', + name: 'please_enter_group_number', + desc: '', + args: [], + ); + } + + /// `Group Lisence Renewal` + String get group_license_renewal { + return Intl.message( + 'Group Lisence Renewal', + name: 'group_license_renewal', + desc: '', + args: [], + ); + } + + /// `Renewal fee` + String get renewal_fee { + return Intl.message( + 'Renewal fee', + name: 'renewal_fee', + desc: '', + args: [], + ); + } + + /// `Tax` + String get tax { + return Intl.message( + 'Tax', + name: 'tax', + desc: '', + args: [], + ); + } + + /// `Pay ${amount} now` + String pay_amount_token(Object amount) { + return Intl.message( + 'Pay \$$amount now', + name: 'pay_amount_token', + desc: '', + args: [amount], + ); + } + + /// `Expired at` + String get expired_at { + return Intl.message( + 'Expired at', + name: 'expired_at', + desc: '', + args: [], + ); + } + + /// `After renewal` + String get after_renewed { + return Intl.message( + 'After renewal', + name: 'after_renewed', + desc: '', + args: [], + ); + } + + /// `Expiration date` + String get expiration_date { + return Intl.message( + 'Expiration date', + name: 'expiration_date', + desc: '', + args: [], + ); + } + + /// `Purchase/Renew service` + String get purchase_renew_service { + return Intl.message( + 'Purchase/Renew service', + name: 'purchase_renew_service', + desc: '', + args: [], + ); + } + + /// `Service description` + String get service_descritpion { + return Intl.message( + 'Service description', + name: 'service_descritpion', + desc: '', + args: [], + ); + } + + /// `Your group` + String get your_group { + return Intl.message( + 'Your group', + name: 'your_group', + desc: '', + args: [], + ); + } + + /// `Current plan` + String get current_plan { + return Intl.message( + 'Current plan', + name: 'current_plan', + desc: '', + args: [], + ); + } + + /// `Select a plan` + String get select_a_plan { + return Intl.message( + 'Select a plan', + name: 'select_a_plan', + desc: '', + args: [], + ); + } + + /// `Price` + String get price { + return Intl.message( + 'Price', + name: 'price', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index d915650..d21b2ac 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -30,5 +30,367 @@ "login": "Login", "logout": "Logout", "download_with_token": "Download at {oss}", - "install_in_store": "Install in store" + "install_in_store": "Install in store", + "igoshow": "iGoShow", + "mobile_email_username": "Mobile, Email or MiniOffice username", + "this_field_is_required": "This field is required.", + "password": "Password", + "password_is_required": "Password is required.", + "new_user_question": "New user?", + "forgot_password_question": "Forgot password?", + "error": "Error", + "ok": "OK", + "please_login": "Please login", + "login_instruction": "If you have already registered an account, please use the following form to login.\nIf you are a MiniOffice user, you can directly enter your MiniOffice username and password to login.", + "me": "Me", + "wallet": "Wallet", + "red_coupon": "Coupon", + "point": "Points", + "my_favorites": "My favorites", + "my_addresses": "My addresses", + "my_cards": "My cards", + "my_support": "My support", + "warning": "Warning", + "email_needed": "Email needed", + "cancel": "Cancel", + "business_cooperation": "Business cooperation", + "change_password": "Change password", + "old_password": "Old password", + "current_password_is_required": "Current password is required.", + "password_again": "Password again", + "password_is_not_match_password_again": "Password and confirm password does not match.", + "submit": "Submit", + "success": "Success", + "password_has_been_changed": "Password has been changed", + "profile": "Profile", + "basic_info": "Basic info.", + "avatar": "Avatar", + "nick_name": "Nick name", + "account_binding": "Account binding", + "mobile_number": "Mobile number", + "not_binding": "Not binding", + "email": "Email", + "get_picture": "Get picture", + "get_picture_from": "Get picture from...", + "gallery": "Gallery", + "camera": "Camera", + "please_select": "Please select", + "please_select_an_image": "Please select an image", + "select": "Select", + "error_read_file": "Error occurred while reading the file.", + "change_nickname": "Change nickname", + "enter_new_nickname": "Enter new nickname", + "nickname_is_required": "Nickname is required", + "submit_to_change": "Submit to change", + "are_you_sure_to_logout": "Are you sure you want to logout?", + "yes_i_am_sure": "Yes! I am sure.", + "registration": "Registration", + "user_registration": "User registration", + "mobile_or_email": "Mobile or email", + "mobile_or_email_is_required": "Mobile or email is required", + "verification_code": "Verification code", + "verification_code_is_required": "Verification code is required", + "register": "Register", + "get_code": "Get code", + "verification_code_sent": "Verification code has been sent.", + "enter_mobile_or_email": "Enter mobile or email", + "get_code_token": "Retry after {second}s", + "get_code_again": "Get code again", + "credit_debit_card": "Credit or debit card", + "credit_card": "Credit card", + "alipay": "Alipay", + "wechatpay": "Wechat pay", + "paypal": "Paypal", + "pay_on_deliery_pickup": "Pay on delivery or pickup", + "pay_on_deliery": "Pay on delivery", + "set_password": "Set password", + "user_account_created_success": "User account created success", + "forgot_password": "Forgot password", + "reset_password": "Reset password", + "verify": "Verify", + "reset_password_success": "Reset password success", + "change_mobile": "Change mobile number", + "change_email": "Change email address", + "mobile_is_required": "Mobile is required", + "email_is_required": "Email is required", + "the_mobile_number_is_same_as_current": "The mobile number is the same as the current one.", + "the_email_is_same_as_current": "The email is the same as the current one.", + "update_success": "Updated success", + "edit_address": "Edit address", + "no_address_yet": "You have not entered any address.", + "contact_name": "Contact name", + "contact_name_is_required": "Contact name is required", + "mr": "Mr.", + "ms": "Ms.", + "mobile_phone_number": "Mobile number", + "mobile_phone_number_is_required": "Mobile number is required", + "street_line_1": "Street line 1", + "street_line_1_is_required": "Street line 1 is required", + "street_line_2": "Street line 2", + "city": "City", + "city_is_required": "City is required", + "province": "Province", + "postal_code": "Postal code", + "postal_code_is_required": "Postal code is required", + "optional_information": "Optional Info.", + "email_is_not_valid": "Email is not valid", + "fax": "Fax", + "delete": "Delete", + "are_you_sure_to_delete_the_address": "Are you sure you want to delete the address?", + "save": "Save", + "the_address_has_been_deleted": "The address has been deleted.", + "search_place": "Search place", + "enter_delivery_address": "Enter delivery address", + "empty_address_change_keyword": "No address found. Tap here to skip address lookup.", + "empty_result_change_keyword": "No data found, please change keyword and try again.", + "new_address": "New address", + "change_password_desc": "Please enter the old password and your desired new password.", + "back": "Back", + "revise_user_profile": "Revise user profile", + "learn_more_about_minipos": "Learn more about MiniPOS", + "learn_more_about_igoshow": "Learn more about iGoShow", + "downloads": "Downloads", + "user_registration_desc": "Please enter your mobile phone number then click the 'Get code' button to get the validation code.", + "forgot_password_description": "Enter the Email or mobile number you used when registering, and then click the 'Get code' button to get the validation code.", + "change_mobile_desc": "Enter your new mobile number, and then click 'Get code' button to get the validation code.", + "change_email_desc": "Enter your new Email, and then click 'Get code' button to get the validation code.", + "add_new_address": "Add new address", + "no_ticket_yet": "There is no ticket yet. If you have any question/issue please tap the plus icon above to create a ticket.", + "today_with_time": "Today {time}", + "tomorrow_with_time": "Tomorrow {time}", + "followups_token": "{num} follow-ups", + "pull_up_to_load_more": "Pull up to load more", + "load_failed_retry": "Load failed, please retry", + "release_to_load_more": "Release to load more", + "no_more_record": "No more record", + "add_new_ticket": "Create a new ticket", + "new_ticket": "New ticket", + "add_new_ticket_desc": "Please enter your question.", + "your_question_issue": "Your question/issue", + "are_you_sure_to_remove_the_picture": "Are you sure you want to remove the picture?", + "attach_pictures": "Attach pictures", + "attach_pictures_desc": "Upload pictures(screen shot) to make your question clearer.", + "ticket_created_success": "Ticket has been created successfully.", + "view_ticket": "View ticket", + "ticket_number_token": "Ticket #{num}", + "close": "Close", + "follow_ups": "Follow ups", + "reply": "Reply", + "the_ticket_is_closed_desc": "The ticket has been closed. If you still have questions, please create a new ticket.", + "your_reply": "Your reply", + "no_blog_yet": "There is no blog to show yet.", + "view_blog": "View blog", + "reset_password_desc": "Enter the new password you desired twice.", + "set_password_desc": "Enter the password you desired twice.", + "order_more": "{minprice} more", + "select_options": "Select options", + "previous": "Previous", + "next": "Next", + "finish": "Finish", + "radio_option_select_token": "Please choice {optionName}.", + "radio_option_is_required": "Option is required. Select an option then tap Next.", + "radio_option_is_optional": "Optional. You can tap Next without selection.", + "check_option_select_token": "Multiple choice {optionName}", + "check_option_is_required": "Option is required. Select at lease an option then tap Next.", + "check_option_is_optional": "Optional. You can tap Next without selection.", + "checkout": "Check out", + "are_you_sure_to_remove_the_item": "Are you sure you want to remove the item?", + "out_of_stock": "Out of stock", + "product_insufficient": "The product quantity is insufficient.", + "your_basket_is_empty": "Your basket is empty.", + "empty_basket": "Empty basket", + "delivery_fee": "Delivery fee {shippingfee}+", + "are_you_sure_to_empty_basket": "Are you sure you want to empty the basket?", + "search_product": "Search product", + "search_products": "Search products", + "enter_product_keyword": "Enter product keyword", + "promotions": "Promotions", + "products": "Products", + "comments": "Comments", + "no_comments_yet": "No comments yet", + "response_from_store": "Response from store", + "show_more": "Show more", + "show_less": "Show less", + "hour_token": "{hours, plural, one{1 hr} other{{hours} hrs}}", + "minute_token": "{minutes, plural, one{1 min} other{{minutes} mins}}", + "min_order_amount_token": "${minamount}+", + "min_shipping_fee": "Delivery ${shipfee}+", + "store_introduction": "Store introduction", + "store_policy": "Store policy", + "sold_per_month_token": " {sold_qty} sold/mo", + "detail": "Detail", + "specification": "Specification", + "weight_token": "Weight: {weight}", + "dimentions_token": "Dimentions: {length}(L)x{width}(W)x{height}(H)", + "loading": "Loading...", + "store_closed": "Store closed", + "closed": "Closed", + "book_now_delivery_later_token": "Order now and delivery start at {hour}:00.", + "confirm_order": "Order confirmation", + "delivery": "Delivery", + "pickup": "Pickup", + "delivery_now": "Delivery ASAP", + "end_of_the_list": "End of the list", + "hot_sale": "Hot sale", + "featured_product": "Featured", + "ocr_scan": "OCR scan", + "from_camera": "From camera", + "from_gallery": "From gallery", + "feature_not_available_web": "This feature is not available on the web. Please install the App version.", + "document_type": "Document type", + "ubereats_receipt": "UberEats receipt", + "business_card": "Business card", + "select_document_type": "Select a document type", + "english": "English", + "chinese_simplified": "Chinese simplified", + "chinese_traditional": "Chinese tranditional", + "document_langage": "Document language", + "select_document_lanuage": "Select a language", + "submit_to_generate": "Submit to generate", + "add_to_basket": "Add to basket", + "no_delivery_method": "No shipping method available.", + "under_renovation": "The store is under renovation", + "pay_now": "Pay now", + "canada_post": "Canada Post", + "canada_post_delivery": "Canada Post delivery", + "table_token": "Table#: {num}", + "number_of_people": "Number of people", + "pickup_at": "Please pickup at", + "delivery_unavailable": "Delivery time", + "select_delivery_time": "Select delivery time", + "credit_coupon": "Credit/Coupon", + "subtotal_token": "Subtotal: {subtotal}", + "subtotal": "Subtotal", + "extra_fee_token": "{name}({rate}%)", + "total": "Total", + "checkout_no_deliver": "We don't deliver at this time.", + "payment_method": "Payment method", + "order_remark": "Order remark", + "please_provide_shipping_address": "Please provide shipping address.", + "over_delivery_distance": "The address is beyond the scope of delivery.", + "pickup_discount": "Pickup discount", + "service_fee": "Service fee", + "pending": "Pending", + "order_acceipt": "Accept", + "order_processing": "Processing", + "order_complete": "Complete", + "order_cancelled": "Cancelled", + "my_orders": "My orders", + "and_more_item_token": "and {qty} items more", + "order_again": "Order again", + "order_detail": "Order detail", + "delivery_info": "Delivery Info.", + "delivery_address": "Delivery address", + "pickup_address": "Pickup address", + "schedule_delivery": "Schedule delivery", + "delivery_method": "Delivery method", + "store_delivery": "Store delivery", + "order_info": "Order info.", + "order_number": "Order No.", + "amount_not_meet": "The order amount does not meet the minimum requirements.", + "select_canada_post_shipping_rate": "Please select Canada Post shipping rate.", + "mon": "Mon", + "monday": "Monday", + "tue": "Tue", + "tuesday": "Tuesday", + "wed": "Wed", + "wednesday": "Wednesday", + "thu": "Thu", + "thursday": "Thursday", + "fri": "Fri", + "friday": "Friday", + "sat": "Sat", + "saturday": "Saturday", + "sun": "Sun", + "sunday": "Sunday", + "today": "Today", + "tomorrow": "Tomorrow", + "shipping_time_will_schedule": "Delivery time cannot be determined. Usually we will send it out as soon as possible. If you wish to deliver at a certain time, please state in the remarks.", + "select_a_payment_method": "Select a payment method", + "payment_method_not_set": "No payment method specified. Please pay on delivery or pickup.", + "no_coupon_available": "No coupon available", + "percentage_discount_token": "{discount}%off", + "discount_amount_token": "-${discount}", + "pick_a_coupon": "Pick a coupon", + "dont_use": "Do not redeem", + "percentage_discount_token2": "-${amount}({discount}%off)", + "type_your_order_remark": "Type your order remark", + "quick_input": "Quick input", + "light_tase": "Light tase", + "no_spicy": "No spicy", + "no_onion": "No onion", + "no_pickle": "No pickle", + "enter_coupon_code": "Enter coupon code", + "please_enter_coupon_code": "Please enter a coupon code.", + "get_coupon": "Get coupon", + "percent_discount": "%OFF", + "expiration_date_token": "Expires on {expirationDate}", + "no_restriction": "No restriction", + "no_expiration": "No expiration", + "redeem_coupon": "Redeem", + "choose_a_shipping_rate": "Choose a shipping rate", + "payment_amount": "Payment amount", + "pay_later": "Pay later", + "pay_after_meal": "Pay after meal", + "pay_with_existing_cards": "Pay with existing cards", + "add_credit_card": "Add credit card", + "this_credit_card_is_invalid": "This credit card is invalid", + "expire_token": "Exp: {mon}/{yer}", + "confirmation": "Confirmation", + "are_you_sure_to_remove_the_card": "Are you srue you want to remove the credit card?", + "payment_verification": "Payment verification", + "payment_verification_sent": "Payment verification code has been sent to your mobile phone {mobile}. Please enter the verification code below.", + "wrong_payment_verification_code": "Wrong payment verification code. Please check and enter again.", + "pay_with": "Pay with", + "pay_with_token": "Pay with {method}", + "you_have_no_orders_yet": "You have no orders yet", + "comment": "Comment", + "delivery_guy": "Delivery guy", + "delivery_distance_token": "The delivery guy are {km} away from the customer, and it will take about {time} to deliver.", + "copy": "Copy", + "order_number_copied_to_clipboard": "Order number copied to clipboard", + "online_payment": "Online payment", + "order_datetime": "Order datetime", + "payment_status": "Payment status", + "paid": "Paid", + "unpaid": "Unpaid", + "order_fulfillment": "Order fulfillment", + "cancel_order": "Cancel order", + "store": "Store", + "customer": "Customer", + "are_you_sure_to_cancel_the_order": "Are you sure you want to cancel the order?", + "no": "No", + "yes": "Yes", + "pay": "Pay", + "no_instance_delivery_desc": "Tracking information will be provided when available.", + "comment_empty": "Please type your comment.", + "thank_you_for_your_comment": "Thank you for your comment.", + "add_pictures": "Add pictures", + "input_your_comment": "Input your comment", + "new_comment": "New comment", + "coupons": "Coupons", + "general_coupon": "General coupon", + "available_for_order_over_token": "Available for order over ${value}", + "includes": "Includes", + "by_email": "By Emails", + "by_phone": "By phone", + "address": "Address", + "view_on_google_map": "View on Google maps", + "toll_free": "Toll free: ", + "group_number_can_be_found": "Group number can be found...", + "group_number": "Group number", + "please_enter_group_number": "Please enter your group number.", + "group_license_renewal": "Group Lisence Renewal", + "renewal_fee": "Renewal fee", + "tax": "Tax", + "pay_amount_token": "Pay ${amount} now", + "expired_at": "Expired at", + "after_renewed": "After renewal", + "expiration_date": "Expiration date", + "purchase_renew_service": "Purchase/Renew service", + "service_descritpion": "Service description", + "your_group": "Your group", + "current_plan": "Current plan", + "select_a_plan": "Select a plan", + "price": "Price" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_CN.arb b/lib/l10n/intl_zh_CN.arb index d1854b8..597bcee 100644 --- a/lib/l10n/intl_zh_CN.arb +++ b/lib/l10n/intl_zh_CN.arb @@ -30,5 +30,363 @@ "login": "登入", "logout": "登出", "download_with_token": "在{oss}下载", - "install_in_store": "店内安装" + "install_in_store": "店内安装", + "igoshow": "iGoShow", + "mobile_email_username": "手机号码,Email或MiniOffice用户名", + "this_field_is_required": "该字段必填。", + "password": "密码", + "password_is_required": "密码必填。", + "new_user_question": "新用户?", + "forgot_password_question": "忘记密码?", + "error": "错误", + "ok": "确定", + "please_login": "请登陆", + "login_instruction": "如果您已经注册了账号,请使用如下表单登陆。\n如果您是MiniOffice的用户,您可直接输入您的MiniOffice用户名和密码登陆。", + "me": "我的", + "wallet": "钱包", + "red_coupon": "红包", + "point": "积分", + "my_favorites": "我的收藏", + "my_addresses": "我的地址", + "my_cards": "我的卡", + "my_support": "支持", + "warning": "注意", + "email_needed": "需要电子邮箱", + "cancel": "取消", + "business_cooperation": "商业合作", + "change_password": "修改密码", + "old_password": "旧密码", + "current_password_is_required": "需要当前密码", + "password_again": "再输入密码", + "password_is_not_match_password_again": "两次输入密码不一致。", + "submit": "提交", + "success": "成功", + "password_has_been_changed": "密码修改成功", + "profile": "个人资料", + "basic_info": "基本信息", + "avatar": "头像", + "nick_name": "昵称", + "account_binding": "账号绑定", + "mobile_number": "手机号码", + "not_binding": "未绑定", + "email": "电子邮箱", + "get_picture": "获取图片", + "get_picture_from": "从...获取图片", + "gallery": "图库", + "camera": "相机", + "please_select": "请选取", + "please_select_an_image": "请选取一张图片", + "select": "选取", + "error_read_file": "读取文件过程中出错。", + "change_nickname": "修改昵称", + "enter_new_nickname": "输入新昵称", + "nickname_is_required": "昵称不能为空", + "submit_to_change": "提交修改", + "are_you_sure_to_logout": "您确定要登出吗?", + "yes_i_am_sure": "是的!我确定。", + "registration": "注册", + "user_registration": "用户注册", + "mobile_or_email": "手机号码或电子邮箱", + "mobile_or_email_is_required": "必须输入手机号码或电子邮箱地址", + "verification_code": "验证码", + "verification_code_is_required": "必须输入验证码", + "register": "注册", + "get_code": "获取验证码", + "verification_code_sent": "验证码已经发送。", + "enter_mobile_or_email": "输入手机号码或电子邮箱地址", + "get_code_token": "{second}秒后重试", + "get_code_again": "重新获取验证码", + "credit_debit_card": "信用卡或银行卡", + "credit_card": "信用卡", + "alipay": "支付宝", + "wechatpay": "微信支付", + "paypal": "Paypal", + "pay_on_deliery_pickup": "自提或到货付款", + "pay_on_deliery": "到货付款", + "set_password": "设置密码", + "user_account_created_success": "用户账号成功创建", + "forgot_password": "忘记密码", + "reset_password": "重置密码", + "verify": "验证", + "reset_password_success": "重置密码成功", + "change_mobile": "修改手机号码", + "change_email": "修改电子邮箱", + "mobile_is_required": "手机号码必填", + "email_is_required": "电子邮箱必填", + "the_mobile_number_is_same_as_current": "手机号码和当前的是一样的。", + "the_email_is_same_as_current": "电子邮箱和当前的是一样的。", + "update_success": "更新成功", + "edit_address": "修改地址", + "no_address_yet": "您还没有输入任何地址。", + "contact_name": "联系人姓名", + "contact_name_is_required": "联系人姓名必填", + "mr": "先生", + "ms": "女士", + "mobile_phone_number": "手机号码", + "mobile_phone_number_is_required": "手机号码必填", + "street_line_1": "街道名称第一行", + "street_line_1_is_required": "街道名称第一行必填", + "street_line_2": "街道名称第二行", + "city": "城市", + "city_is_required": "城市必填", + "province": "省份", + "postal_code": "邮政编码", + "postal_code_is_required": "邮政编码必填", + "optional_information": "可选填信息", + "email_is_not_valid": "电子邮箱无效", + "fax": "传真", + "delete": "删除", + "are_you_sure_to_delete_the_address": "您确定要删除该地址吗?", + "save": "保存", + "the_address_has_been_deleted": "地址成功删除", + "search_place": "搜索地址", + "enter_delivery_address": "输入送货地址", + "empty_address_change_keyword": "找不到地址。点击这里跳过地址搜索。", + "empty_result_change_keyword": "找不到您需要的数据,请改变关键字再试。", + "new_address": "添加新地址", + "change_password_desc": "请输入旧密码和希望的新密码。", + "back": "返回", + "revise_user_profile": "修改用户信息", + "learn_more_about_minipos": "详细了解MiniPOS", + "learn_more_about_igoshow": "详细了解iGoShow", + "downloads": "下载", + "user_registration_desc": "请输入您的手机号码,然后单击“获取验证码”按钮。", + "forgot_password_description": "输入您注册时用的Email或手机号码,然后点击‘获取验证码’ 按钮。", + "change_mobile_desc": "输入新的手机号码,然后点击'获取验证码'按钮。", + "change_email_desc": "输入新的Email,然后点击'获取验证码'按钮。", + "add_new_address": "添加新地址", + "today_with_time": "今天{time}", + "tomorrow_with_time": "明天{time}", + "followups_token": "{num}条回复", + "pull_up_to_load_more": "上拉加载更多", + "load_failed_retry": "载入失败,请重试。", + "release_to_load_more": "松开载入", + "no_more_record": "没有更多记录了", + "add_new_ticket": "创建一个新的支持票", + "new_ticket": "新的支持票", + "add_new_ticket_desc": "请输入您的问题。您还可以上传文件或图片来使您的提问更清楚。", + "your_question_issue": "您的问题", + "are_you_sure_to_remove_the_picture": "您确定要移除该图片吗?", + "attach_pictures": "图片附件", + "attach_pictures_desc": "建议附上图片(屏幕截图)让问题更清楚。", + "ticket_created_success": "支持票成功创建。", + "view_ticket": "查看支持票", + "ticket_number_token": "票号 #{num}", + "close": "关闭", + "follow_ups": "跟进", + "reply": "回复", + "the_ticket_is_closed_desc": "该支持票已经关闭。如果您还有问题,请创建新的支持票。", + "your_reply": "您的回复", + "no_blog_yet": "尚无要显示的博客。", + "view_blog": "查看博客", + "reset_password_desc": "输入您想要的新密码两次。", + "set_password_desc": "输入您想要的密码两次。", + "order_more": "还差{minprice}起送", + "select_options": "选择选项", + "previous": "上一步", + "next": "下一步", + "finish": "完成", + "radio_option_select_token": "请选择{optionName}", + "radio_option_is_required": "选项必选。选择一个选项后点下一步。", + "radio_option_is_optional": "可选选项。如果不选择可以直接点下一步。", + "check_option_select_token": "多项选择{optionName}", + "check_option_is_required": "选项必选。选择至少一项再点击下一步。", + "check_option_is_optional": "可选选项。如果不选择可以直接点下一步。", + "checkout": "去结算", + "are_you_sure_to_remove_the_item": "您确定要移除该物品吗?", + "out_of_stock": "脱销", + "product_insufficient": "该产品数量不足。", + "your_basket_is_empty": "您的购物篮是空的", + "empty_basket": "清空购物篮", + "delivery_fee": "配送费{shippingfee}起", + "are_you_sure_to_empty_basket": "您确定要清空购物篮吗?", + "search_product": "搜索产品", + "search_products": "搜索产品", + "enter_product_keyword": "输入产品关键字", + "promotions": "推广", + "products": "产品", + "comments": "评价", + "no_comments_yet": "还没评价", + "response_from_store": "店家回复", + "show_more": "显示更多", + "show_less": "显示更少", + "hour_token": "{hours, plural, one{1小时} other{{hours}小时}}", + "minute_token": "{minutes, plural, one{1分钟} other{{minutes}分钟}}", + "min_order_amount_token": "${minamount}起送", + "min_shipping_fee": "配送费${shipfee}起", + "store_introduction": "店家简介", + "store_policy": "商家政策", + "sold_per_month_token": " 月售{sold_qty}单", + "detail": "详细描述", + "specification": "规格", + "weight_token": "重量:{weight}", + "dimentions_token": "尺寸:{length}(L)x{width}(W)x{height}(H)", + "loading": "装载中...", + "store_closed": "商店已关", + "book_now_delivery_later_token": "现在预定,{hour}:00后开始配送。", + "confirm_order": "订单确定", + "delivery": "外卖配送", + "pickup": "到店自提", + "delivery_now": "立刻配送", + "end_of_the_list": "全部加载", + "hot_sale": "热卖", + "featured_product": "特色产品", + "ocr_scan": "OCR扫描", + "from_camera": "手机照相机", + "from_gallery": "手机图库", + "feature_not_available_web": "该功能在网页版上不能用。请下载App版本。", + "document_type": "文件类型", + "ubereats_receipt": "UberEats收据", + "business_card": "名片", + "select_document_type": "选择一种文件类型", + "english": "英文", + "chinese_simplified": "简体中文", + "chinese_traditional": "繁体中文", + "document_langage": "文档语言", + "select_document_lanuage": "选择一种语言", + "submit_to_generate": "提交生成", + "add_to_basket": "放入购物篮", + "no_delivery_method": "没有可用的送货方式", + "under_renovation": "本店还在装修", + "pay_now": "去支付", + "canada_post": "加拿大邮政", + "canada_post_delivery": "加拿大邮政配送", + "table_token": "桌号:{num}", + "number_of_people": "人数", + "pickup_at": "自提地址", + "delivery_unavailable": "配送时间", + "select_delivery_time": "选择配送时间", + "credit_coupon": "红包/抵用券", + "subtotal_token": "小计:{subtotal}", + "subtotal": "小计", + "extra_fee_token": "{name}({rate}%)", + "total": "总计", + "checkout_no_deliver": "现在这时间我们不派送。", + "payment_method": "付款方式", + "order_remark": "订单备注", + "please_provide_shipping_address": "请提供收货地址", + "over_delivery_distance": "地址超出配送范围", + "pickup_discount": "自提折扣", + "service_fee": "服务费", + "pending": "待接单", + "order_acceipt": "已接单", + "order_processing": "准备中", + "order_complete": "已完成", + "order_cancelled": "已取消", + "my_orders": "我的订单", + "and_more_item_token": "等{qty}件", + "order_again": "再来一单", + "order_detail": "订单详情", + "delivery_info": "配送信息", + "delivery_address": "收货地址", + "pickup_address": "自提地址", + "schedule_delivery": "预约送货时间", + "delivery_method": "配送方式", + "store_delivery": "商家配送", + "order_info": "订单信息", + "order_number": "订单号码", + "amount_not_meet": "订单金额没有达到最低要求。", + "select_canada_post_shipping_rate": "请选择加拿大邮政运费。", + "mon": "周一", + "monday": "星期一", + "tue": "周二", + "tuesday": "星期二", + "wed": "周三", + "wednesday": "星期三", + "thu": "周四", + "thursday": "星期四", + "fri": "周五", + "friday": "星期五", + "sat": "周六", + "saturday": "星期六", + "sun": "周日", + "sunday": "星期日", + "today": "今天", + "tomorrow": "明天", + "shipping_time_will_schedule": "配送时间无法确定。通常我们会尽快送出。如果您希望在某时配送,请在备注里注明。", + "select_a_payment_method": "选择付款方式", + "payment_method_not_set": "没有指定付款方式,请货到付款或自提付款。", + "no_coupon_available": "无可用红包", + "percentage_discount_token": "{discount}%折扣", + "discount_amount_token": "-${discount}", + "pick_a_coupon": "使用红包", + "dont_use": "不使用", + "percentage_discount_token2": "-${amount}({discount}%折扣)", + "type_your_order_remark": "输入订单备注", + "quick_input": "快速输入", + "light_tase": "清淡口味", + "no_spicy": "不要辣", + "no_onion": "不要洋葱", + "no_pickle": "不要泡菜", + "enter_coupon_code": "输入红包号码", + "please_enter_coupon_code": "请输入红包号码。", + "get_coupon": "获取红包", + "percent_discount": "%折扣", + "expiration_date_token": "{expirationDate}到期", + "no_restriction": "无门槛", + "redeem_coupon": "去使用", + "choose_a_shipping_rate": "选择运费", + "payment_amount": "付款金额", + "pay_later": "稍后付款", + "pay_after_meal": "餐后付款", + "pay_with_existing_cards": "用现有的卡付款", + "expire_token": "到期:{mon}/{yer}", + "confirmation": "请确定", + "are_you_sure_to_remove_the_card": "您确定要移除该信用卡吗?", + "payment_verification": "付款验证", + "payment_verification_sent": "付款码已经发送到您的手机{mobile}。请在下面输入付款码。", + "wrong_payment_verification_code": "错误的付款码,请查证后再输入。", + "pay_with": "使用", + "pay_with_token": "使用{method}付款", + "you_have_no_orders_yet": "您还没有订单", + "comment": "评价", + "delivery_guy": "配送员", + "delivery_distance_token": "配送员离您{km},大概需时{time}送达。", + "copy": "复制", + "order_number_copied_to_clipboard": "订单号码复制到剪切板", + "online_payment": "在线付款", + "order_datetime": "下单时间", + "payment_status": "付款状态", + "paid": "已付款", + "unpaid": "未付款", + "order_fulfillment": "订单完成度", + "cancel_order": "取消订单", + "store": "店家", + "customer": "客户", + "are_you_sure_to_cancel_the_order": "您确定要取消该订单吗?", + "no": "不是", + "yes": "是的", + "pay": "付款", + "no_instance_delivery_desc": "跟踪信息将在可用时提供。", + "comment_empty": "请输入您的评价。", + "thank_you_for_your_comment": "谢谢您的评价。", + "add_pictures": "晒照片", + "input_your_comment": "写下您的评价", + "new_comment": "新的评价", + "coupons": "优惠券", + "general_coupon": "通用优惠券", + "available_for_order_over_token": "订单满${value}可用", + "includes": "包含", + "by_email": "电子邮箱", + "by_phone": "联系电话", + "address": "地址", + "view_on_google_map": "在谷歌地图打开", + "toll_free": "无费号码:", + "group_number_can_be_found": "按如下图找到集团号码...", + "group_number": "集团号码", + "please_enter_group_number": "请输入您的集团号码。", + "group_license_renewal": "集团许可续订", + "renewal_fee": "续订费用", + "tax": "税费", + "pay_amount_token": "马上付款 ${amount}", + "expired_at": "到期日期", + "after_renewed": "续约后", + "expiration_date": "到期日期", + "purchase_renew_service": "购买/续期服务", + "service_descritpion": "服务描述", + "your_group": "您的集团", + "current_plan": "当前计划", + "select_a_plan": "选择一个计划", + "price": "价格", + "no_ticket_yet": "还没有支持票。 如果您有任何疑问/问题,请点击上方的加号图标以创建支持票。" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 453caa8..b31778f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,48 +1,106 @@ +import 'dart:async'; + import 'package:catcher/catcher.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_device_locale/flutter_device_locale.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_redux/flutter_redux.dart'; +import 'package:flutter_wisetronic/pages/buy_service.dart'; +import 'package:geolocator/geolocator.dart' as geolocator; +import 'package:url_strategy/url_strategy.dart'; +import 'models/located_address.dart'; +import 'pages/me.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:splashscreen/splashscreen.dart'; +// import 'package:uni_links/uni_links.dart'; import 'constants.dart'; +import 'events/eventbus.dart'; +import 'events/events.dart'; import 'generated/l10n.dart'; import 'pages/home.dart'; +import 'pages/plain_page.dart'; +import 'pages/renew_minioffice.dart'; import 'routes.dart'; import 'store/actions.dart'; import 'store/store.dart'; +import 'utils/configure_nonweb.dart' if (dart.library.html) 'utils/configure_web.dart'; +import 'utils/http_util.dart'; +import 'utils/util_io.dart' if (dart.library.html) 'utils/util_web.dart'; -void main() async { +void main() { WidgetsFlutterBinding.ensureInitialized(); - Locale locale = await DeviceLocale.getCurrentLocale(); - - runApp(MyApp(locale)); + // Enable configureApp will cause route problem. + // configureApp(); + setPathUrlStrategy(); + runApp(MyApp()); } class MyApp extends StatelessWidget { - final Locale localLocate; + String _to = '/'; - MyApp(this.localLocate) { + MyApp() { Routes.configure(); - store.dispatch(UpdateLocale(localLocate)); + Util().init(); + // getCurrentPosition(); + } + + static getCurrentPosition() async { + bool serviceEnabled; + geolocator.LocationPermission permission; + + serviceEnabled = await geolocator.Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + print('Location services are disabled'); + return; + } + + permission = await geolocator.Geolocator.checkPermission(); + if (permission == geolocator.LocationPermission.deniedForever) { + print( + 'Location permissions are permantly denied, we cannot request permissions.'); + return; + } + + if (permission == geolocator.LocationPermission.denied) { + permission = await geolocator.Geolocator.requestPermission(); + if (permission != geolocator.LocationPermission.whileInUse && + permission != geolocator.LocationPermission.always) { + print( + 'Location permissions are denied (actual value: $permission).'); + return; + } + geolocator.Position geoPosition = await geolocator.Geolocator.getCurrentPosition( + desiredAccuracy: geolocator.LocationAccuracy.high); + HttpUtil.httpGet('v1/getposition', + queryParameters: { + 'latitude': geoPosition.latitude.toString(), + 'longitude': geoPosition.longitude.toString(), + }, + ).then((value) { + print('value: $value'); + store.dispatch(UpdateLocatedAddress(LocatedAddress.fromJson(value))); + }).catchError((error) { + print('error get address: $error'); + }); + } } // This widget is the root of your application. @override Widget build(BuildContext context) { + Widget _buildBody() { return SplashScreen( seconds: 5, - routeName: '/', - navigateAfterSeconds: Home(localLocate, title: 'Welcome',), + routeName: _to, + navigateAfterSeconds: Home(title: Constants.APP_TITLE,), styleTextUnderTheLoader: new TextStyle( fontWeight: FontWeight.bold, fontSize: 20.0, ), - // imageBackground: (Image.network(Constants.BASE_API_URL + 'gallery/get-wisetronic-slpash')).image, - imageBackground: (new Image.network(Constants.BASE_API_URL + 'gallery/get-minimanager-splash/')).image, + imageBackground: (Image.network(Constants.BASE_API_URL + 'gallery/get-minimanager-splash/')).image, backgroundColor: Colors.white, onClick: () { @@ -63,21 +121,59 @@ class MyApp extends StatelessWidget { ], supportedLocales: S.delegate.supportedLocales, localeResolutionCallback: (Locale locale, Iterable supportedLocales) { - print('Language code: ${localLocate.languageCode}'); + print('Language code: ${locale.languageCode}, Country code: ${locale.countryCode}'); for (final supportedLocale in supportedLocales) { - if (supportedLocale.languageCode == localLocate.languageCode) { + if (supportedLocale.languageCode == locale.languageCode) { + store.dispatch(UpdateLocale(locale)); return supportedLocale; } } + store.dispatch(UpdateLocale(supportedLocales.first)); return supportedLocales.first; }, + onGenerateRoute: (RouteSettings settings) { + final List pathElements = settings.name.split('/'); + print('path elements: $pathElements'); + if (pathElements[0] != '') { + return null; + } + if (pathElements[1] == 'me') { + return MaterialPageRoute( + builder: (BuildContext context) => Me() + ); + } + if (pathElements[1] == 'privacy-policy') { + return MaterialPageRoute( + builder: (BuildContext context) => + PlainPage( + 'privacy-policy', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ), + ); + } + if (pathElements[1] == 'renew-minioffice') { + return MaterialPageRoute(builder: (BuildContext context) => + RenewMiniOffice(int.parse(pathElements[2])) + ); + } + if (pathElements[1] == 'buy-service') { + return MaterialPageRoute(builder: (BuildContext context) => + BuyService(int.parse(pathElements[2]), pathElements[3]) + ); + } + return MaterialPageRoute( + builder: (BuildContext context) => + Home(title: Constants.APP_TITLE,), + ); + }, title: 'Wisetronic Inc.', theme: ThemeData( primarySwatch: Colors.blue, ), home: StoreProvider( store: store, - child: _buildBody(), + child: Home(title: Constants.APP_TITLE,), ), ); } diff --git a/lib/models/Subproduct.dart b/lib/models/Subproduct.dart new file mode 100644 index 0000000..fcbdab7 --- /dev/null +++ b/lib/models/Subproduct.dart @@ -0,0 +1,66 @@ + +import 'dart:convert'; + +import 'package:flutter_wisetronic/models/product_attribute.dart'; + +class Subproduct { + int id; + SimpleProduct product; + double quantity; + + Subproduct.fromJson(Map json) : + id = json['id'], + product = SimpleProduct.fromJson(json['product']), + quantity = double.parse(json['quantity'].toString()); + + Map toJson() => { + 'id': id, + 'product': product, + 'quantity': quantity, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class SimpleProduct { + int id; + String name; + String sku; + double price; + String description; + String image; + List productAttributes; + double customerLastPrice; + String extraData; + + SimpleProduct.fromJson(Map json) : + id = json['id'], + name = json['name'], + sku = json['sku'], + price = double.parse(json['price'].toString()), + description = json['description'], + image = json['image'], + productAttributes = (json['productattributes'] as List).map((i) => ProductAttribute.fromJson(i)).toList(), + customerLastPrice = double.parse(json['customer_last_price'].toString()), + extraData = json['extra_data']; + + Map toJson() => { + 'id': id, + 'name': name, + 'sku': sku, + 'price': price, + 'description': description, + 'image': image, + 'productattributes': productAttributes, + 'customer_last_price': customerLastPrice, + 'extra_data': extraData, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/address.dart b/lib/models/address.dart new file mode 100644 index 0000000..ffd7ec3 --- /dev/null +++ b/lib/models/address.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +class Address { + int id; + String addressLine1; + String addressLine2; + String city; + String state; + String country; + String zip; + String phone; + String fax; + String email; + String contactName; + int gender; + String lat; + String lng; + String fullAddress; + + Address.fromJson(Map json) + : id = json['id'], + addressLine1 = json['address_line1'], + addressLine2 = json['address_line2'], + city = json['city'], + state = json['state'], + country = json['country'], + zip = json['zip'], + phone = json['phone'], + fax = json['fax'], + email = json['email'], + contactName = json['name'], + gender = json['gender'], + lat = json['lat'], + lng = json['lng'], + fullAddress = json['full_address']; + + Map toJson() => { + 'id': id, + 'address_line1': addressLine1, + 'address_line2': addressLine2, + 'city': city, + 'state': state, + 'country': country, + 'zip': zip, + 'phone': phone, + 'fax': fax, + 'email': email, + 'name': contactName, + 'gender': gender, + 'lat': lat, + 'lng': lng, + 'full_address': fullAddress, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/blog.dart b/lib/models/blog.dart new file mode 100644 index 0000000..538a5fb --- /dev/null +++ b/lib/models/blog.dart @@ -0,0 +1,58 @@ + +import 'dart:convert'; + +class Section { + int id; + String name; + + Section.fromJson(Map json) : + id = json['id'], + name = json['name']; + + Map toJson() => { + 'id': id, + 'name': name, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class Blog { + int id; + String title; + String body; + String createdAt; + String thumbUrl; + String imageUrl; + String videoUrl; + Section section; + + Blog.fromJson(Map json) : + id = json['id'], + title = json['title'], + body = json['body'], + createdAt = json['created_at'], + thumbUrl = json['thumb_url'], + imageUrl = json['image_url'], + videoUrl = json['video_url'], + section = Section.fromJson(json['section']); + + Map toJson() => { + 'id': id, + 'title': title, + 'body': body, + 'created_at': createdAt, + 'thumb_url': thumbUrl, + 'image_url': imageUrl, + 'video_url': videoUrl, + 'section': section, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/booking_date_time.dart b/lib/models/booking_date_time.dart new file mode 100644 index 0000000..f9cb071 --- /dev/null +++ b/lib/models/booking_date_time.dart @@ -0,0 +1,32 @@ + +import 'dart:convert'; + +import 'booking_time.dart'; + +class BookingDateTime { + String viewDate; + int unixTime; + String sendTimeTip; + List bookTimes; + + BookingDateTime(this.viewDate, this.unixTime, this.sendTimeTip, this.bookTimes); + + BookingDateTime.fromJson(Map json) + : viewDate = json['view_date'], + unixTime = int.parse(json['unix_time'].toString()), + sendTimeTip = json['send_time_tip'], + bookTimes = (json['book_times'] as List).map((e) => BookingTime.fromJson(e)).toList(); + + Map toJson() => { + 'view_date': viewDate, + 'unix_time': unixTime, + 'send_time_tip': sendTimeTip, + 'book_times': bookTimes, + }; + + @override + String toString() { + return json.encode(this); + } + +} \ No newline at end of file diff --git a/lib/models/booking_time.dart b/lib/models/booking_time.dart new file mode 100644 index 0000000..9f4c055 --- /dev/null +++ b/lib/models/booking_time.dart @@ -0,0 +1,27 @@ + +import 'dart:convert'; + +class BookingTime { + String viewTime; + int unixTime; + String sendTimeTip; + + BookingTime(this.viewTime, this.unixTime, this.sendTimeTip); + + BookingTime.fromJson(Map json) + : viewTime = json['view_time'], + unixTime = int.parse(json['unix_time'].toString()), + sendTimeTip = json['send_time_tip']; + + Map toJson() => { + 'view_time': viewTime, + 'unix_time': unixTime, + 'send_time_tip': sendTimeTip, + }; + + @override + String toString() { + return json.encode(this); + } + +} \ No newline at end of file diff --git a/lib/models/business.dart b/lib/models/business.dart new file mode 100644 index 0000000..d6e1bd0 --- /dev/null +++ b/lib/models/business.dart @@ -0,0 +1,164 @@ +import 'dart:convert'; + +import 'address.dart'; +import 'business_time.dart'; +import 'distance_info.dart'; +import 'image_url.dart'; +import 'product.dart'; +import 'waiting_line.dart'; + +class QuickInput { + String value; + QuickInput.fromJson(Map json) + : value = json['value']; + + Map toJson() => { + 'value': value + }; + + @override + String toString() { + return json.encode(this); + } +} + +class Business { + int id; + String name; + String phone; + Address address; + String fullAddress; + String picUrl; + int status; + List openingTime; + double shippingFee; + double packageFee; + double minPrice; + int shippingTime; + int monthSales; + String bulletin; + int category; + bool isLike; + String description; + String policy; + String bannerImageUrl; + int todayOpen; + int todayClose; + bool showMonthlySold; + List slideImages; + List promoProducts; + String paypalEmail; + bool deliveryCanadaPost; + bool deliveryStoreDelivery; + bool deliveryPickup; + String currency; + List waitingLine; + bool enableWaitingLine; + double pointsToCreditsConversionRate; + String chaseXLogin; + String chaseTransactionKey; + String chaseResponseKey; + DistanceInfo distanceInfo; + int commentsCount; + bool forceClose; + bool isPublic; + double referrerCoupon; + bool instanceDelivery; + List quickInputs; + + Business.fromJson(Map json) + : id = json['id'], + name = json['name'], + phone = json['phone'], + address = Address.fromJson(json['address']), + fullAddress = json['full_address'], + picUrl = json['pic_url'], + status = json['status'], + openingTime = (json['opening_time'] as List).map((i) => BusinessTime.fromJson(i)).toList(), + shippingFee = double.parse(json['shipping_fee'].toString()), + packageFee = double.parse(json['package_fee'].toString()), + minPrice = double.parse(json['min_price'].toString()), + shippingTime = json['shipping_time'], + monthSales = json['month_sales'], + bulletin = json['bulletin'], + category = json['category'], + isLike = json['is_like'], + description = json['description'], + policy = json['policy'], + bannerImageUrl = json['banner_image_url'], + todayOpen = json['today_open'], + todayClose = json['today_close'], + showMonthlySold = json['show_monthly_sold'], + slideImages = (json['slide_images'] as List).map((i) => ImageUrl.fromJson(i)).toList(), + promoProducts = (json['promo_products'] as List).map((i) => Product.fromJson(i)).toList(), + paypalEmail = json['paypal_email'], + deliveryCanadaPost = json['delivery_canada_post'], + deliveryStoreDelivery = json['delivery_store_delivery'], + deliveryPickup = json['delivery_pickup'], + currency = json['currency'], + + waitingLine = (json['waiting_line'] as List).map((i) => WaitingLine.fromJson(i)).toList(), + + enableWaitingLine = json['enable_waiting_line'], + pointsToCreditsConversionRate = double.parse(json['points_to_credits_conversion_rate'].toString()), + chaseXLogin = json['chase_x_login'], + chaseTransactionKey = json['chase_transaction_key'], + chaseResponseKey = json['chase_response_key'], + distanceInfo = json['distance_info'] != null ? DistanceInfo.fromJson(json['distance_info']) : null, + commentsCount = json['comments_count'], + forceClose = json['force_close'], + isPublic = json['is_public'], + referrerCoupon = double.parse(json['customer_referrer_coupon_amount'].toString()), + instanceDelivery = json['instance_delivery'], + quickInputs = (json['quick_inputs'] as List).map((i) => QuickInput.fromJson(i)).toList(); + + Map toJson() => { + 'id': id, + 'name': name, + 'phone': phone, + 'address': address, + 'full_address': fullAddress, + 'pic_url': picUrl, + 'status': status, + 'opening_time': openingTime, + 'shipping_fee': shippingFee, + 'package_fee': packageFee, + 'min_price': minPrice, + 'shipping_time': shippingTime, + 'month_sales': monthSales, + 'bulletin': bulletin, + 'category': category, + 'is_like': isLike, + 'description': description, + 'policy': policy, + 'banner_image_url': bannerImageUrl, + 'today_open': todayOpen, + 'today_close': todayClose, + 'show_monthly_sold': showMonthlySold, + 'slide_images': slideImages, + 'promo_products': promoProducts, + 'paypal_email': paypalEmail, + 'delivery_canada_post': deliveryCanadaPost, + 'delivery_store_delivery': deliveryStoreDelivery, + 'delivery_pickup': deliveryPickup, + 'currency': currency, + 'waiting_line': waitingLine, + 'enable_waiting_line': enableWaitingLine, + 'points_to_credits_conversion_rate': pointsToCreditsConversionRate, + 'chase_x_login': chaseXLogin, + 'chase_transaction_key': chaseTransactionKey, + 'chase_response_key': chaseResponseKey, + 'distance_info': distanceInfo, + 'comments_count': commentsCount, + 'force_close': forceClose, + 'is_public': isPublic, + 'customer_referrer_coupon_amount': referrerCoupon, + 'instance_delivery': instanceDelivery, + 'quick_inputs': quickInputs + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/business_time.dart b/lib/models/business_time.dart new file mode 100644 index 0000000..3247474 --- /dev/null +++ b/lib/models/business_time.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +class BusinessTime { + String openTime; + String closeTime; + + BusinessTime.fromJson(Map json) + : openTime = json['open_time'], + closeTime = json['close_time']; + + Map toJson() => { + 'open_time': openTime, + 'close_time': closeTime + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/cart_info.dart b/lib/models/cart_info.dart new file mode 100644 index 0000000..4516a53 --- /dev/null +++ b/lib/models/cart_info.dart @@ -0,0 +1,94 @@ + + +import 'dart:convert'; + +import 'business.dart'; +import 'cart_line_item.dart'; +import 'extra_fee.dart'; + +class CartInfo { + int id; + double originPrice; + double discountPrice; + double totalPrice; + Business businessInfo; + List productList; + List extraFeeList; + List discountList; + int deliveryMethod; + String shippingMethod; + double amountPaid; + String extraData; + + CartInfo.fromJson(Map json) + : id = json['id'], + originPrice = json['origin_price'] != null ? double.parse(json['origin_price'].toString()) : 0.0, + discountPrice = json['discount_price'] != null ? double.parse(json['discount_price'].toString()) : 0.0, + totalPrice = json['total_price'] != null ? double.parse(json['total_price'].toString()) : 0.0, + businessInfo = json['business_info'] != null ? Business.fromJson(json['business_info']) : null, + productList = (json['product_list'] as List).map((i) => CartLineItem.fromJson(i)).toList(), + extraFeeList = (json['extra_fee_list'] as List).map((i) => ExtraFee.fromJson(i)).toList(), + discountList = (json['discount_list'] as List).map((e) => ExtraFee.fromJson(e)).toList(), + deliveryMethod = json['delivery_method'], + shippingMethod = json['shipping_method'], + amountPaid = double.parse(json['amount_paid'].toString()), + extraData = json['extra_data']; + + Map toJson() => { + 'id': id, + 'origin_price': originPrice, + 'discount_price': discountPrice, + 'total_price': totalPrice, + 'business_info': businessInfo, + 'product_list': productList, + 'extra_fee_list': extraFeeList, + 'discount_list': discountList, + 'delivery_method': deliveryMethod, + 'shipping_method': shippingMethod, + 'amount_paid': amountPaid, + 'extra_data': extraData, + }; + + addToCart(CartLineItem item) { + if (productList == null) { + productList = [item]; + } else { + productList.add(item); + } + + } + + removeFromCart() { + + } + + @override + String toString() { + return json.encode(this); + } + + CartInfo(); + + double getTotalPrice() { + double newTotal = 0.0; + originPrice = 0.0; + discountPrice = 0.0; + totalPrice = 0.0; + for (var i = 0; i < productList.length; i++) { + newTotal += productList[i].getTotalPrice(); + } + originPrice = newTotal; + totalPrice = newTotal; + return totalPrice; + } + + int getProductListTotalQuantity() { + int qty = 0; + if (productList != null && productList.length > 0) { + for (var i = 0; i < productList.length; i++) { + qty += productList[i].quantity.round(); + } + } + return qty; + } +} \ No newline at end of file diff --git a/lib/models/cart_line_item.dart b/lib/models/cart_line_item.dart new file mode 100644 index 0000000..5adb0ec --- /dev/null +++ b/lib/models/cart_line_item.dart @@ -0,0 +1,54 @@ + + +import 'dart:convert'; +import 'package:uuid/uuid.dart'; + +import 'product.dart'; + +class CartLineItem { + int id; + double unitPrice; + double totalPrice; + Product product; + String name; + String description; + double quantity; + String uuid; + String parentUuid; + + CartLineItem() { + uuid = Uuid().v4(); + } + + CartLineItem.fromJson(Map json) + : id = json['id'], + unitPrice = double.parse(json['unit_price'].toString()), + totalPrice = json['total_price'] != null ? double.parse(json['total_price'].toString()) : double.parse(json['unit_price'].toString()) * double.parse(json['quantity'].toString()), + product = json['product'] != null ? Product.fromJson(json['product']) : null, + name = json['name'], + description = json['description'], + quantity = double.parse(json['quantity'].toString()), + uuid = Uuid().v4(); + + Map toJson() => { + 'id': id, + 'unit_price': unitPrice, + 'total_price': totalPrice, + 'product': product, + 'name': name, + 'description': description, + 'quantity': quantity, + 'uuid': uuid, + 'parentUuid': parentUuid + }; + + @override + String toString() { + return json.encode(this); + } + + double getTotalPrice() { + totalPrice = unitPrice * quantity; + return totalPrice; + } +} \ No newline at end of file diff --git a/lib/models/category.dart b/lib/models/category.dart new file mode 100644 index 0000000..9f41558 --- /dev/null +++ b/lib/models/category.dart @@ -0,0 +1,21 @@ + +import 'dart:convert'; + +class Category { + int id; + String name; + + Category.fromJson(Map json) + : id = json['id'], + name = json['name']; + + Map toJson() => { + 'id': id, + 'name': name, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/category_products.dart b/lib/models/category_products.dart new file mode 100644 index 0000000..f740278 --- /dev/null +++ b/lib/models/category_products.dart @@ -0,0 +1,38 @@ + +import 'dart:convert'; + +import 'product.dart'; + +class CategoryProducts { + int id; + int businessId; + String name; + String description; + String iconUrl; + List products; + + CategoryProducts.fromJson(Map json) + : id = json['id'], + businessId = json['business_id'], + name = json['name'], + description = json['description'], + iconUrl = json['icon_url'], + products = (json['products'] as List).map((i) => Product.fromJson(i)).toList(); + + Map toJson() => { + 'id': id, + 'business_id': businessId, + 'name': name, + 'description': description, + 'icon_url': iconUrl, + 'products': products + }; + + @override + String toString() { + return json.encode(this); + } + + CategoryProducts(this.id, this.businessId, this.name, this.description, + this.iconUrl, this.products); +} \ No newline at end of file diff --git a/lib/models/comment.dart b/lib/models/comment.dart new file mode 100644 index 0000000..780dd27 --- /dev/null +++ b/lib/models/comment.dart @@ -0,0 +1,44 @@ + +import 'dart:convert'; + +import 'product_image.dart'; + +class Comment { + int id; + String contact; + String content; + int rating; + List images; + String createdAt; + int thumbUp; + int thumbDown; + String replyFromStore; + + Comment.fromJson(Map json) : + id = json['id'], + contact = json['contact'], + content = json['content'], + rating = json['rating'], + images = (json['images'] as List).map((e) => ProductImage.fromJson(e)).toList(), + createdAt = json['created_at'], + thumbUp = json['thumb_up'], + thumbDown = json['thumb_down'], + replyFromStore = json['reply_from_store']; + + Map toJson() => { + 'id': id, + 'contact': contact, + 'content': content, + 'rating': rating, + 'images': images, + 'created_at': createdAt, + 'thumb_up': thumbUp, + 'thumb_down': thumbDown, + 'reply_from_store': replyFromStore, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/coupon.dart b/lib/models/coupon.dart new file mode 100644 index 0000000..45ac37b --- /dev/null +++ b/lib/models/coupon.dart @@ -0,0 +1,41 @@ + +import 'dart:convert'; + +import 'business.dart'; + +class Coupon { + int id; + String code; + String description; + String expirationDate; + double minAmount; + Business store; + double valueAmount; + bool isPercentage; + + Coupon.fromJson(Map json) + : id = json['id'], + code = json['code'].toString(), + description = json['description'], + expirationDate = json['expiration_date'], + minAmount = double.parse(json['min_amount'].toString()), + store = json['store'] != null ? Business.fromJson(json['store']) : null, + valueAmount = double.parse(json['value_amount'].toString()), + isPercentage = json['is_percentage']; + + Map toJson() => { + 'id': id, + 'code': code, + 'description': description, + 'expiration_date': expirationDate, + 'min_amount': minAmount, + 'store': store, + 'value_amount': valueAmount, + 'is_percentage': isPercentage, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/distance_info.dart b/lib/models/distance_info.dart new file mode 100644 index 0000000..96cad60 --- /dev/null +++ b/lib/models/distance_info.dart @@ -0,0 +1,26 @@ + +import 'dart:convert'; + +import 'text_value.dart'; + +class DistanceInfo { + TextValue duration; + TextValue distance; + String status; + + DistanceInfo.fromJson(Map json) + : duration = json['duration'] != null ? TextValue.fromJson(json['duration']) : null, + distance = json['distance'] != null ? TextValue.fromJson(json['distance']) : null, + status = json['status']; + + Map toJson() => { + 'duration': duration, + 'distance': distance, + 'status': status + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/error_message.dart b/lib/models/error_message.dart new file mode 100644 index 0000000..e99e64e --- /dev/null +++ b/lib/models/error_message.dart @@ -0,0 +1,21 @@ + +import 'dart:convert'; + +class ErrorMessage { + String code; + String string; + + ErrorMessage.fromJson(Map json) + : code = json['code'], + string = json['string']; + + Map toJson() => { + 'code': code, + 'string': string, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/extra_fee.dart b/lib/models/extra_fee.dart new file mode 100644 index 0000000..65f85df --- /dev/null +++ b/lib/models/extra_fee.dart @@ -0,0 +1,31 @@ + + +import 'dart:convert'; + +class ExtraFee { + int id; + String name; + String description; + double rate; + double price; + + ExtraFee.fromJson(Map json) + : id = json['id'], + name = json['name'], + description = json['description'], + rate = double.parse(json['rate'].toString()), + price = double.parse(json['price'].toString()); + + Map toJson() => { + 'id': id, + 'name': name, + 'description': description, + 'rate': rate, + 'price': price, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/fulfillment.dart b/lib/models/fulfillment.dart new file mode 100644 index 0000000..5d94fac --- /dev/null +++ b/lib/models/fulfillment.dart @@ -0,0 +1,30 @@ + +import 'dart:convert'; + +class Fulfillment { + int id; + String shippingMethod; + String trackingNumber; + String note; + String createdAt; + + Fulfillment.fromJson(Map json) : + id = json['id'], + shippingMethod = json['shipping_method'], + trackingNumber = json['tracking_number'], + note = json['note'], + createdAt = json['created_at']; + + Map toJson() => { + 'id': id, + 'shipping_method': shippingMethod, + 'tracking_number': trackingNumber, + 'note': note, + 'created_at': createdAt, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/image_url.dart b/lib/models/image_url.dart new file mode 100644 index 0000000..62599c1 --- /dev/null +++ b/lib/models/image_url.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +class ImageUrl { + int id; + String imageUrl; + + ImageUrl.fromJson(Map json) + : id = json['id'], + imageUrl = json['image_url']; + + Map toJson() => { + 'id': id, + 'image_url': imageUrl + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/key_value.dart b/lib/models/key_value.dart new file mode 100644 index 0000000..333271c --- /dev/null +++ b/lib/models/key_value.dart @@ -0,0 +1,12 @@ + +class KeyValue { + String name; + dynamic value; + + KeyValue(this.name, this.value); + + @override + String toString() { + return '$name: $value'; + } +} \ No newline at end of file diff --git a/lib/models/located_address.dart b/lib/models/located_address.dart new file mode 100644 index 0000000..1f9dd24 --- /dev/null +++ b/lib/models/located_address.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +class LocatedAddress { + int storeId; + double latitude; + double longitude; + String streetNumber; + String streetName; + String city; + String locality; + String province; + String country; + String postalCode; + String formattedAddress; + + LocatedAddress.fromJson(Map json) + : storeId = json['store_id'], + latitude = json['latitude'], + longitude = json['longitude'], + streetNumber = json['street_number'], + streetName = json['street_name'], + city = json['city'], + locality = json['locality'], + province = json['province'], + country = json['country'], + postalCode = json['postal_code'], + formattedAddress = json['formatted_address']; + + Map toJson() => { + 'store_id': storeId, + 'latitude': latitude, + 'longitude': longitude, + 'street_number': streetNumber, + 'street_name': streetName, + 'city': city, + 'locality': locality, + 'province': province, + 'country': country, + 'postal_code': postalCode, + 'formatted_address': formattedAddress + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/order.dart b/lib/models/order.dart new file mode 100644 index 0000000..9528d3b --- /dev/null +++ b/lib/models/order.dart @@ -0,0 +1,113 @@ + + +import 'dart:convert'; + +import 'address.dart'; +import 'business.dart'; +import 'cart_info.dart'; +import 'cart_line_item.dart'; +import 'distance_info.dart'; +import 'fulfillment.dart'; +import 'user_position.dart'; + +class Order { + int id; + int userId; + int businessId; + Business businessInfo; + CartInfo cartInfo; + int status; + double originPrice; + double discountPrice; + double totalPrice; + String consignee; + String phone; + String address; + Address shippingAddress; + int payMethod; + String remark; + String orderNum; + int bookedAt; + int createdAt; + String shippingMethod; + String paymentStatus; // UNPAID, PAID, PARTIALPAID + double amountPaid; + List fulfillments; + String extraData; + bool hasComment; + double fee; + UserPosition shipperPosition; + DistanceInfo deliveryDistance; + + Order.fromJson(Map json) + : id = json['id'], + userId = json['user_id'], + businessId = json['business_id'], + businessInfo = json['business_info'] != null ? Business.fromJson(json['business_info']) : null, + cartInfo = json['cart_info'] != null ? CartInfo.fromJson(json['cart_info']) : null, + status = json['status'], + originPrice = double.parse(json['origin_price'].toString()), + discountPrice = double.parse(json['discount_price'].toString()), + totalPrice = double.parse(json['total_price'].toString()), + consignee = json['consignee'], + phone = json['phone'], + address = json['address'], + shippingAddress = (json['shipping_address'] != null) ? Address.fromJson(json['shipping_address']) : null, + payMethod = json['pay_method'], + remark = json['remark'], + orderNum = json['order_num'], + bookedAt = int.parse(json['booked_at'].toString()), + createdAt = int.parse(json['created_at'].toString()), + shippingMethod = json['shipping_method'], + paymentStatus = json['payment_status'], + amountPaid = double.parse(json['amount_paid'].toString()), + fulfillments = (json['fulfillments'] as List).map((e) => Fulfillment.fromJson(e)).toList(), + extraData = json['extra_data'], + hasComment = json['has_comment'], + fee = double.parse(json['fee'].toString()), + shipperPosition = UserPosition.fromJson(json['shipper_position']), + deliveryDistance = DistanceInfo.fromJson(json['delivery_distance']); + + Map toJson() => { + 'id': id, + 'user_id': userId, + 'business_id': businessId, + 'business_info': businessInfo, + 'cart_info': cartInfo, + 'status': status, + 'origin_price': originPrice, + 'discount_price': discountPrice, + 'total_price': totalPrice, + 'consignee': consignee, + 'phone': phone, + 'address': address, + 'shipping_address': shippingAddress, + 'pay_method': payMethod, + 'remark': remark, + 'order_num': orderNum, + 'booked_at': bookedAt, + 'created_at': createdAt, + 'shipping_method': shippingMethod, + 'payment_status': paymentStatus, + 'amount_paid': amountPaid, + 'fulfillments': fulfillments, + 'extra_data': extraData, + 'has_comment': hasComment, + 'fee': fee, + 'shipper_position': shipperPosition, + 'delivery_distance': deliveryDistance, + }; + + double getSubtotal() { + double subtotal = 0.0; + for (CartLineItem lineItem in cartInfo.productList) { + subtotal += lineItem.getTotalPrice(); + } + return subtotal; + } + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/order_status.dart b/lib/models/order_status.dart new file mode 100644 index 0000000..a18930c --- /dev/null +++ b/lib/models/order_status.dart @@ -0,0 +1,10 @@ + +enum OrderStatus { + WAIT_FOR_SUBMISSION, // -1 + WAIT_FOR_PAYMENT, // 0 + WAIT_FOR_APPROVAL, // 1 + WAIT_FOR_DELIVERY, // 2 + WAIT_FOR_ARRIVAL, // 3 + RECEIVED_CONFIRMATION, // 4 + DONE // 5 +} \ No newline at end of file diff --git a/lib/models/payment_method.dart b/lib/models/payment_method.dart new file mode 100644 index 0000000..fca7d24 --- /dev/null +++ b/lib/models/payment_method.dart @@ -0,0 +1,5 @@ + +enum PaymentMethod { + ONLINE, // 1 + OFFLINE // 0 +} \ No newline at end of file diff --git a/lib/models/payment_platform.dart b/lib/models/payment_platform.dart new file mode 100644 index 0000000..5395030 --- /dev/null +++ b/lib/models/payment_platform.dart @@ -0,0 +1,85 @@ + + +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import '../generated/l10n.dart'; + +class PaymentPlatform { + int id; + String name; + String code; + String method; + String icon; + String xLogin; + String transactionKey; + String responseKey; + String squareAppId; + String squareAccessToken; + String squareLocationId; + String publishableKey; + String merchantId; + + PaymentPlatform(this.code); + + PaymentPlatform.fromJson(Map json) + : id = json['id'], + name = json['name'], + code = json['code'], + method = json['method'], + icon = json['icon'], + xLogin = json['x_login'], + transactionKey = json['transaction_key'], + responseKey = json['response_key'], + squareAppId = json['square_app_id'], + squareAccessToken = json['square_access_token'], + squareLocationId = json['square_location_id'], + publishableKey = json['publishable_key'], + merchantId = json['merchant_id']; + + Map toJson() => { + 'id': id, + 'name': name, + 'code': code, + 'method': method, + 'icon': icon, + 'x_login': xLogin, + 'transaction_key': transactionKey, + 'response_key': responseKey, + 'square_app_id': squareAppId, + 'square_access_token': squareAccessToken, + 'square_location_id': squareLocationId, + 'publishable_key': publishableKey, + 'merchant_id': merchantId, + }; + + static String getPaymentPlatformName(BuildContext context, String code) { + switch(code) { + case 'chase': + return S.of(context).credit_debit_card; + break; + case 'web-credit-card': + return S.of(context).credit_card; + break; + case 'ALIPAY': + return S.of(context).alipay; + break; + case 'WECHATPAY': + return S.of(context).wechatpay; + break; + case 'paypal': + return S.of(context).paypal; + case 'payondeliverypickup': + return S.of(context).pay_on_deliery_pickup; + case 'stripe': + return S.of(context).credit_card; + default: + return S.of(context).pay_on_deliery; + } + } + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/position.dart b/lib/models/position.dart new file mode 100644 index 0000000..96a202e --- /dev/null +++ b/lib/models/position.dart @@ -0,0 +1,34 @@ + +class Position { + final double latitude; + final double longitude; + + Position({this.latitude, this.longitude}); + + @override + bool operator ==(other) { + var areEqual = other is Position && + other.latitude == latitude && + other.longitude == longitude; + + return areEqual; + } + + @override + int get hashCode => latitude.hashCode ^ + longitude.hashCode; + + @override + String toString() { + return 'Lat $latitude, Lng $longitude'; + } + + Position.fromJson(Map json) : + latitude = double.parse(json['latitude'].toString()), + longitude = double.parse(json['longitude'].toString()); + + Map toJson() => { + 'latitude': latitude, + 'longitude': longitude + }; +} \ No newline at end of file diff --git a/lib/models/product.dart b/lib/models/product.dart new file mode 100644 index 0000000..f143476 --- /dev/null +++ b/lib/models/product.dart @@ -0,0 +1,77 @@ + +import 'dart:convert'; + +import 'Subproduct.dart'; +import 'product_attribute.dart'; + +class Product { + int id; + int businessId; + int categoryId; + String name; + double price; + double regularPrice; + String description; + String detailDescription; + String imagePath; + double monthSales; + int rate; + double leftNum; + List productAttributes; + bool nonInventory; + String extraData; + List subproducts; + + Product(int id, int businessId, String name, double price, String description, + String imagePath, List productAttributes) { + this.id = id; + this.businessId = businessId; + this.name = name; + this.price = price; + this.description = description; + this.imagePath = imagePath; + this.productAttributes = productAttributes; + } + + Product.fromJson(Map json) + : id = json['id'], + businessId = json['business_id'], + categoryId = json['category_id'], + name = json['name'], + price = double.parse(json['price'].toString()), + regularPrice = double.parse(json['regular_price'].toString()), + description = json['description'], + detailDescription = json['detail_description'], + imagePath = json['image_path'], + monthSales = json['month_sales'] != null ? double.parse(json['month_sales'].toString()) : 0.0, + rate = json['rate'], + leftNum = json['left_num'] != null ? double.parse(json['left_num'].toString()) : 0.0, + productAttributes = json['productattributes'] != null ? (json['productattributes'] as List).map((i) => ProductAttribute.fromJson(i)).toList() : [], + nonInventory = json['non_inventory'], + extraData = json['extra_data'], + subproducts = json['subproducts'] != null ? (json['subproducts'] as List).map((e) => Subproduct.fromJson(e)).toList() : []; + + Map toJson() => { + 'id': id, + 'business_id': businessId, + 'category_id': categoryId, + 'name': name, + 'price': price, + 'regular_price': regularPrice, + 'description': description, + 'detail_description': detailDescription, + 'image_path': imagePath, + 'month_sales': monthSales, + 'rate': rate, + 'left_num': leftNum, + 'productattributes': productAttributes, + 'non_inventory': nonInventory, + 'extra_data': extraData, + 'subproducts': subproducts, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/product_attribute.dart b/lib/models/product_attribute.dart new file mode 100644 index 0000000..e519a4d --- /dev/null +++ b/lib/models/product_attribute.dart @@ -0,0 +1,41 @@ + +import 'dart:convert'; + +import 'product_option.dart'; + +class ProductAttribute { + int id; + String name; + List productOptions; + bool singleSelection; + bool byQuantity; + bool required; + String extra; + bool disabled; + + ProductAttribute.fromJson(Map json) + : id = json['id'], + name = json['name'], + productOptions = (json['productoptions'] as List).map((i) => ProductOption.fromJson(i)).toList(), + singleSelection = json['single_selection'], + byQuantity = json['by_quantity'], + required = json['required'], + extra = json['extra'], + disabled = json['disabled']; + + Map toJson() => { + 'id': id, + 'name': name, + 'productoptions': productOptions, + 'single_selection': singleSelection, + 'by_quantity': byQuantity, + 'required': required, + 'extra': extra, + 'disabled': disabled + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/product_detail.dart b/lib/models/product_detail.dart new file mode 100644 index 0000000..2d6f529 --- /dev/null +++ b/lib/models/product_detail.dart @@ -0,0 +1,93 @@ + +import 'dart:convert'; + +import 'package:flutter_wisetronic/models/Subproduct.dart'; + +import 'category.dart'; +import 'product_attribute.dart'; +import 'product_image.dart'; + +class ProductDetail { + int id; + String image; + List images; + String sku; + String name; + double price; + double regularPrice; + String description; + String description2; + String detailDescription; + double monthSales; + int rate; + double leftNum; + List productAttributes; + bool nonInventory; + String extraData; + String purchaseNote; + double weight; + double dimentionsLength; + double dimentionsWidth; + double dimentionsHeight; + bool reviewsAllowed; + List categories; + List subproducts; + + ProductDetail.fromJson(Map json) + : id = json['id'], + image = json['image'], + images = (json['images'] as List).map((e) => ProductImage.fromJson(e)).toList(), + sku = json['sku'], + name = json['name'], + price = double.parse(json['price'].toString()), + regularPrice = double.parse(json['regular_price'].toString()), + description = json['description'], + description2 = json['description2'], + detailDescription = json['detail_description'], + reviewsAllowed = json['reviews_allowed'], + monthSales = double.parse(json['month_sales'].toString()), + rate = json['rate'], + leftNum = double.parse(json['left_num'].toString()), + productAttributes = (json['productattributes'] as List).map((i) => ProductAttribute.fromJson(i)).toList(), + nonInventory = json['non_inventory'], + extraData = json['extra_data'], + purchaseNote = json['purchase_note'], + weight = double.parse(json['weight'].toString()), + dimentionsLength = double.parse(json['dimentions_length'].toString()), + dimentionsWidth = double.parse(json['dimentions_width'].toString()), + dimentionsHeight = double.parse(json['dimentions_height'].toString()), + categories = (json['categories'] as List).map((e) => Category.fromJson(e)).toList(), + subproducts = (json['subproducts'] as List).map((e) => Subproduct.fromJson(e)).toList(); + + Map toJson() => { + 'id': id, + 'image': image, + 'images': images, + 'sku': sku, + 'name': name, + 'price': price, + 'regular_price': regularPrice, + 'description': description, + 'description2': description2, + 'detail_description': detailDescription, + 'month_sales': monthSales, + 'rate': rate, + 'left_num': leftNum, + 'productattributes': productAttributes, + 'non_inventory': nonInventory, + 'extra_data': extraData, + 'purchase_note': purchaseNote, + 'weight': weight, + 'dimentions_length': dimentionsLength, + 'dimentions_width': dimentionsWidth, + 'dimentions_height': dimentionsHeight, + 'reviews_allowed': reviewsAllowed, + 'categories': categories, + 'subproducts': subproducts, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/product_image.dart b/lib/models/product_image.dart new file mode 100644 index 0000000..5275d57 --- /dev/null +++ b/lib/models/product_image.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +class ProductImage { + int id; + String image; + + ProductImage.fromJson(Map json) + : id = json['id'], + image = json['image']; + + Map toJson() => { + 'id': id, + 'image': image + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/product_option.dart b/lib/models/product_option.dart new file mode 100644 index 0000000..6e50e96 --- /dev/null +++ b/lib/models/product_option.dart @@ -0,0 +1,30 @@ + +import 'dart:convert'; + +class ProductOption { + int id; + String name; + double adjustAmount; + String extra; + bool disabled; + + ProductOption.fromJson(Map json) + : id = json['id'], + name = json['name'], + adjustAmount = double.parse(json['adjust_amount'].toString()), + extra = json['extra'], + disabled = json['disabled']; + + Map toJson() => { + 'id': id, + 'name': name, + 'adjust_amount': adjustAmount, + 'extra': extra, + 'disabled': disabled + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/shipping_rate.dart b/lib/models/shipping_rate.dart new file mode 100644 index 0000000..08f811e --- /dev/null +++ b/lib/models/shipping_rate.dart @@ -0,0 +1,73 @@ + +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class ShippingRate { + String name; + double price; + List rates; + + ShippingRate.fromJson(Map json) + : name = json['name'], + price = double.parse(json['price'].toString()), + rates = (json['rates'] as List).map((e) => Rate.fromJson(e)).toList(); + + Map toJson() => { + 'name': name, + 'price': price, + 'rates': rates, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ShippingRate && + runtimeType == other.runtimeType && + name == other.name && + price.toStringAsFixed(2) == other.price.toStringAsFixed(2) && + listEquals(rates, other.rates); + + @override + String toString() { + return json.encode(this); + } + + @override + int get hashCode => + name.hashCode ^ + price.hashCode ^ + rates.hashCode; +} + +class Rate { + String name; + double rate; + + Rate.fromJson(Map json) + : name = json['name'], + rate = double.parse(json['rate'].toString()); + + Map toJson() => { + 'name': name, + 'rate': rate, + }; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Rate && + runtimeType == other.runtimeType && + name == other.name && + rate.toStringAsFixed(2) == other.rate.toStringAsFixed(2); + + @override + String toString() { + return json.encode(this); + } + + @override + int get hashCode => + name.hashCode ^ + rate.hashCode; +} \ No newline at end of file diff --git a/lib/models/simple_business.dart b/lib/models/simple_business.dart new file mode 100644 index 0000000..cfcab03 --- /dev/null +++ b/lib/models/simple_business.dart @@ -0,0 +1,26 @@ + +import 'dart:convert'; + +import 'address.dart'; + +class Store { + int id; + String name; + Address address; + + Store.fromJson(Map json) + : id = json['id'], + name = json['name'], + address = Address.fromJson(json['address']); + + Map toJson() => { + 'id': id, + 'name': name, + 'address': address, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/simple_product.dart b/lib/models/simple_product.dart new file mode 100644 index 0000000..206745f --- /dev/null +++ b/lib/models/simple_product.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; + +class SimpleProduct { + int id; + String name; + double price; + double regularPrice; + String description; + String imagePath; + + SimpleProduct.fromJson(Map json) + : id = json['id'], + name = json['name'], + price = double.parse(json['price'].toString()), + regularPrice = double.parse(json['regular_price'].toString()), + description = json['description'], + imagePath = json['image_path']; + + Map toJson() => { + 'id': id, + 'name': name, + 'price': price, + 'regular_price': regularPrice, + 'description': description, + 'image_path': imagePath + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/text_value.dart b/lib/models/text_value.dart new file mode 100644 index 0000000..57d5c90 --- /dev/null +++ b/lib/models/text_value.dart @@ -0,0 +1,21 @@ + +import 'dart:convert'; + +class TextValue { + String text; + int value; + + TextValue.fromJson(Map json) + : text = json['text'], + value = int.parse(json['value'].toString()); + + Map toJson() => { + 'text': text, + 'value': value + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/ticket.dart b/lib/models/ticket.dart new file mode 100644 index 0000000..4226a3a --- /dev/null +++ b/lib/models/ticket.dart @@ -0,0 +1,110 @@ + +import 'dart:convert'; + +import 'simple_business.dart'; + +class TicketFile { + int id; + String url; + String fileName; + String fileType; + + TicketFile.fromJson(Map json) + : id = json['id'], + url = json['url'], + fileName = json['file_name'], + fileType = json['file_type']; + + Map toJson() => { + 'id': id, + 'url': url, + 'file_name': fileName, + 'file_type': fileType, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class Issue { + int id; + String msg; + List files; + + Issue.fromJson(Map json) + : id = json['id'], + msg = json['msg'], + files = (json['files'] as List).map((e) => TicketFile.fromJson(e)).toList(); + + Map toJson() => { + 'id': id, + 'msg': msg, + 'files': files, + }; + + @override + String toString() { + return json.encode(this); + } +} + +class FollowUp { + int id; + String msg; + String poster; + String createdAt; + List files; + + FollowUp.fromJson(Map json) + : id = json['id'], + msg = json['msg'], + poster = json['poster'], + createdAt = json['created_at'], + files = (json['files'] as List).map((e) => TicketFile.fromJson(e)).toList(); + + Map toJson() => { + 'id': id, + 'msg': msg, + 'poster': poster, + 'created_at': createdAt, + 'files': files + }; + + @override + String toString() { + return json.encode(this); + } +} + +class Ticket { + int id; + Issue issue; + Store store; + List followUps; + bool isClosed; + String createdAt; + + Ticket.fromJson(Map json) + : id = json['id'], + issue = Issue.fromJson(json['issue']), + store = Store.fromJson(json['store']), + followUps = (json['follow_ups'] as List).map((e) => FollowUp.fromJson(e)).toList(), + isClosed = json['is_closed'], + createdAt = json['created_at']; + + Map toJson() => { + 'id': id, + 'issue': issue, + 'store': store, + 'follow_ups': followUps, + 'is_closed': isClosed, + 'created_at': createdAt, + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/user_position.dart b/lib/models/user_position.dart new file mode 100644 index 0000000..dbe9f56 --- /dev/null +++ b/lib/models/user_position.dart @@ -0,0 +1,21 @@ + +import 'dart:convert'; + +class UserPosition { + double lat; + double lng; + + UserPosition.fromJson(Map json) : + lat = double.parse(json['lat'].toString()), + lng = double.parse(json['lng'].toString()); + + Map toJson() => { + 'lat': lat, + 'lng': lng + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/waiting_line.dart b/lib/models/waiting_line.dart new file mode 100644 index 0000000..dc1dffa --- /dev/null +++ b/lib/models/waiting_line.dart @@ -0,0 +1,23 @@ + +import 'dart:convert'; + +import 'waiting_line_item.dart'; + +class WaitingLine { + List waitingLines; + String storeSn; + + WaitingLine.fromJson(Map json) + : waitingLines = (json['waiting_lines'] as List).map((i) => WaitingLineItem.fromJson(i)).toList(), + storeSn = json['store_sn']; + + Map toJson() => { + 'waiting_lines': waitingLines, + 'store_sn': storeSn + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/models/waiting_line_item.dart b/lib/models/waiting_line_item.dart new file mode 100644 index 0000000..ce1127f --- /dev/null +++ b/lib/models/waiting_line_item.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +class WaitingLineItem { + String deviceId; + String phone; + String createdAt; + int peopleCount; + int waitingNumber; + + WaitingLineItem.fromJson(Map json) + : deviceId = json['device_id'], + phone = json['phone'], + createdAt = json['created_at'], + peopleCount = json['people_count'], + waitingNumber = json['waiting_number']; + + Map toJson() => { + 'device_id': deviceId, + 'phone': phone, + 'created_at': createdAt, + 'people_count': peopleCount, + 'waiting_number': waitingNumber + }; + + @override + String toString() { + return json.encode(this); + } +} \ No newline at end of file diff --git a/lib/pages/attribute_selection.dart b/lib/pages/attribute_selection.dart new file mode 100644 index 0000000..a977d1a --- /dev/null +++ b/lib/pages/attribute_selection.dart @@ -0,0 +1,27 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_attribute_selection.dart'; +import 'package:responsive_builder/responsive_builder.dart'; +import '../models/business.dart'; +import '../models/product.dart'; + +class AttributeSelection extends StatelessWidget { + final Product product; + final Business business; + final GlobalKey startKey; + + const AttributeSelection({Key key, this.product, this.business, this.startKey}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileAttributeSelection(product: product, business: business, startKey: startKey,), + tablet: MobileAttributeSelection(product: product, business: business, startKey: startKey,), + desktop: MobileAttributeSelection(product: product, business: business, startKey: startKey,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/blog.dart b/lib/pages/blog.dart new file mode 100644 index 0000000..8ef70ef --- /dev/null +++ b/lib/pages/blog.dart @@ -0,0 +1,32 @@ + + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../widgets/desktop/desktop_blog.dart'; +import '../widgets/mobile/mobile_blog.dart'; + +class Blog extends StatelessWidget { + final Key key; + final int businessId; + + const Blog({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileBlog(businessId: businessId,), + tablet: DesktopBlog(businessId: businessId,), + desktop: Scrollbar( + isAlwaysShown: true, + child: DesktopBlog(businessId: businessId,), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/buy_service.dart b/lib/pages/buy_service.dart new file mode 100644 index 0000000..25f1f9b --- /dev/null +++ b/lib/pages/buy_service.dart @@ -0,0 +1,76 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_buy_service.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_renew_minioffice.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_buy_service.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_renew_minioffice.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../models/business.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_renew_license.dart'; +import '../widgets/mobile/mobile_renew_license.dart'; + +class BuyService extends StatefulWidget { + final int gid; + final String serviceName; + + const BuyService(this.gid, this.serviceName, {Key key}) : + super(key: key); + + @override + State createState() => BuyServiceState(); +} + +class BuyServiceState extends State { + Map data; + + @override + Widget build(BuildContext context) { + + store.dispatch(UpdateContext(context)); + + if (data == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileBuyService(data), + tablet: DesktopBuyService(data), + desktop: DesktopBuyService(data), + ), + ); + } + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + HttpUtil.httpGet( + 'v1/get-buy-service-info/${widget.serviceName}/${widget.gid}', + businessId: Constants.BUSINESS_ID, + ).then((value) { + data = value; + setState(() {}); + }).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/pages/change_mobile_or_email.dart b/lib/pages/change_mobile_or_email.dart new file mode 100644 index 0000000..ea85924 --- /dev/null +++ b/lib/pages/change_mobile_or_email.dart @@ -0,0 +1,48 @@ + + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_change_mobile_or_email.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_change_mobile_or_email.dart'; + +class ChangeMobileOrEmail extends StatelessWidget { + final bool isMobile; + + const ChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: isMobile ? S.of(context).change_mobile : S.of(context).change_email, + back: true, + breadCrumbs: [ + BreadCrumb(isMobile ? S.of(context).change_mobile : S.of(context).change_email, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileChangeMobileOrEmail(isMobile), + tablet: DesktopChangeMobileOrEmail(isMobile), + desktop: DesktopChangeMobileOrEmail(isMobile), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/change_password.dart b/lib/pages/change_password.dart new file mode 100644 index 0000000..6fca5b3 --- /dev/null +++ b/lib/pages/change_password.dart @@ -0,0 +1,46 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_change_password.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_change_password.dart'; + +class ChangePassword extends StatelessWidget { + final _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).change_password, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).change_password, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileChangePassword(), + tablet: DesktopChangePassword(), + desktop: DesktopChangePassword(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/checkout.dart b/lib/pages/checkout.dart new file mode 100644 index 0000000..da5f521 --- /dev/null +++ b/lib/pages/checkout.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_checkout.dart'; +import '../widgets/mobile/mobile_checkout.dart'; + +class Checkout extends StatelessWidget { + final int businessId; + + const Checkout(this.businessId, {Key key}) : + super(key: key); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileCheckout(businessId), + tablet: DesktopCheckout(businessId), + desktop: DesktopCheckout(businessId), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/contact_us.dart b/lib/pages/contact_us.dart new file mode 100644 index 0000000..1d67451 --- /dev/null +++ b/lib/pages/contact_us.dart @@ -0,0 +1,70 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../models/business.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/http_util.dart'; +import '../utils/utils.dart'; +import '../widgets/desktop/desktop_contact_us.dart'; +import '../widgets/mobile/mobile_contact_us.dart'; + +class ContactUs extends StatefulWidget { + final int businessId; + + const ContactUs({this.businessId, Key key}) : + super(key: key); + + @override + State createState() => ContactUsState(); +} + +class ContactUsState extends State { + Business business; + + @override + Widget build(BuildContext context) { + + store.dispatch(UpdateContext(context)); + + if (business == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileContactUs(business), + tablet: DesktopContactUs(business), + desktop: DesktopContactUs(business), + ), + ); + } + + @override + void initState() { + super.initState(); + loadData(); + } + + void loadData() { + HttpUtil.httpGet( + 'v1/get-business', + businessId: widget.businessId, + ).then((value) { + business = Business.fromJson(value); + setState(() {}); + }).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/pages/coupons.dart b/lib/pages/coupons.dart new file mode 100644 index 0000000..666cb4d --- /dev/null +++ b/lib/pages/coupons.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_coupons.dart'; +import '../widgets/mobile/mobile_coupons.dart'; + +class Coupons extends StatelessWidget { + final int contactId; + + const Coupons(this.contactId, {Key key}) : + super(key: key); + + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileCoupons(contactId,), + tablet: DesktopCoupons(contactId,), + desktop: DesktopCoupons(contactId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/download.dart b/lib/pages/download.dart index 468aac6..2ed2128 100644 --- a/lib/pages/download.dart +++ b/lib/pages/download.dart @@ -1,14 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/widgets/general/download_apps.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_download_apps.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/general/double_back_to_close_app_wrapper.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_download_apps.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; import '../events/eventbus.dart'; import '../events/events.dart'; import '../generated/l10n.dart'; -import '../utils/double_back_to_close_app.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/http_util.dart'; import '../widgets/general/bottom_nav.dart'; -import '../widgets/mobile/mobile_navigation_drawer.dart'; import '../widgets/general/navigationbar.dart'; -import 'package:responsive_builder/responsive_builder.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_navigation_drawer.dart'; class Download extends StatefulWidget { @@ -21,25 +30,46 @@ class Download extends StatefulWidget { class DownloadState extends State { final _scaffoldKey = GlobalKey(); + Map data; @override Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + if (data == null) { + return Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } return ResponsiveBuilder( builder: (context, sizingInformation) => Scaffold( key: _scaffoldKey, - appBar: NavigationBar(title: S.of(context).download,), + appBar: NavigationBar( + title: S.of(context).download, + back: true, + breadCrumbs: [BreadCrumb(S.of(context).downloads, null)], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null, - body: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - DownloadApps(), - ], + body: ScreenTypeLayout( + mobile: MobileDownloadApps(data), + tablet: DesktopDownloadApps(data), + desktop: Scrollbar( + isAlwaysShown: true, + child: DesktopDownloadApps(data), ), ), - bottomNavigationBar: BottomNav(), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(), + tablet: BottomNav(), + desktop: BottomNav(), + ), ), ); } @@ -52,6 +82,18 @@ class DownloadState extends State { _scaffoldKey.currentState.openDrawer(); } }); + _loadData(); + } + void _loadData() { + HttpUtil.httpGet('v1/get-wisetronic-download-page') + .then((value) { + print('$value'); + if (mounted) { + setState(() { + data = value; + }); + } + }); } } \ No newline at end of file diff --git a/lib/pages/edit_address.dart b/lib/pages/edit_address.dart new file mode 100644 index 0000000..3324ae9 --- /dev/null +++ b/lib/pages/edit_address.dart @@ -0,0 +1,34 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_edit_address.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_edit_address.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../models/address.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; + +class EditAddress extends StatelessWidget { + final Key key; + final Address address; + final int businessId; + const EditAddress(this.address, {this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileEditAddress(address, businessId: businessId,), + tablet: DesktopEditAddress(address, businessId: businessId,), + desktop: DesktopEditAddress(address, businessId: businessId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/forgot_password.dart b/lib/pages/forgot_password.dart new file mode 100644 index 0000000..0140f73 --- /dev/null +++ b/lib/pages/forgot_password.dart @@ -0,0 +1,45 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_forgot_password.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_forgot_password.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; + +class ForgotPassword extends StatelessWidget { + const ForgotPassword({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).forgot_password, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).forgot_password, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileForgotPassword(), + tablet: DesktopForgotPassword(), + desktop: DesktopForgotPassword(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 0258bb4..3cd5e61 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,29 +1,39 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/models/gallery.dart'; -import '../widgets/general/bottom_nav.dart'; -import '../widgets/general/index_carousel.dart'; -import '../widgets/general/index_main_content_1.dart'; -import '../widgets/general/index_main_content_2.dart'; -import '../widgets/general/index_main_content_3.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:responsive_builder/responsive_builder.dart'; +// import 'package:uni_links/uni_links.dart'; + import '../events/eventbus.dart'; import '../events/events.dart'; -import '../generated/l10n.dart'; +import '../models/gallery.dart'; import '../store/actions.dart'; import '../store/store.dart'; import '../utils/http_util.dart'; -import '../utils/double_back_to_close_app.dart'; +import '../utils/utils.dart'; +import '../widgets/desktop/desktop_Index_carousel.dart'; +import '../widgets/desktop/desktop_index_main_content_1.dart'; +import '../widgets/desktop/desktop_index_main_content_2.dart'; +import '../widgets/desktop/desktop_index_main_content_3.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/double_back_to_close_app_wrapper.dart'; import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_index_carousel.dart'; +import '../widgets/mobile/mobile_index_main_content_1.dart'; +import '../widgets/mobile/mobile_index_main_content_2.dart'; +import '../widgets/mobile/mobile_index_main_content_3.dart'; import '../widgets/mobile/mobile_navigation_drawer.dart'; -import 'package:responsive_builder/responsive_builder.dart'; class Home extends StatefulWidget { - Locale locale; final String title; - Home(Locale locale, {Key key, this.title}) : - locale = locale ?? store.state.locale; + Home({Key key, this.title = ''}) : super(key: key); @override State createState() { @@ -35,36 +45,78 @@ class Home extends StatefulWidget { class HomeState extends State { final _scaffoldKey = GlobalKey(); List galleries = []; - String content1Message = ''; + String content1Message; Map content2; + StreamSubscription _sub; + Uri _latestUri; + @override Widget build(BuildContext context) { store.dispatch(UpdateContext(context)); + if (content1Message == null) { + return Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + return ResponsiveBuilder( builder: (context, sizingInformation) => Scaffold( key: _scaffoldKey, appBar: NavigationBar(), drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null, - body: DoubleBackToCloseApp( - snackBar: SnackBar( - content: Text(S.of(context).tap_back_again_to_exit), - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - IndexCarousel(galleries), - IndexMainContent1(content1Message), - IndexMainContent2(content2), - IndexMainContent3(content2), - ], + body: DoubleBackToCloseAppWrapper( + child: ScreenTypeLayout( + mobile: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MobileIndexCarousel(galleries), + MobileIndexMainContent1(content1Message), + MobileIndexMainContent2(content2), + MobileIndexMainContent3(content2), + ], + ), + ), + tablet: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DesktopIndexCarousel(galleries), + DesktopIndexMainContent1(content1Message), + DesktopIndexMainContent2(content2), + DesktopIndexMainContent3(content2), + ], + ), + ), + desktop: Scrollbar( + isAlwaysShown: true, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DesktopIndexCarousel(galleries), + DesktopIndexMainContent1(content1Message), + DesktopIndexMainContent2(content2), + DesktopIndexMainContent3(content2), + ], + ), + ), ), ), ), - bottomNavigationBar: BottomNav(), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), ), ); } @@ -72,13 +124,21 @@ class HomeState extends State { @override void initState() { super.initState(); + eventBus.on().listen((event) { + if (mounted) { + print('got latest Uri $_latestUri'); + } + }); + initPlatformStateForUri(); _loadData(); eventBus.on().listen((event) { if (mounted) { _scaffoldKey.currentState.openDrawer(); } }); - + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Utils.getCurrentUser(); + }); } void _loadData() { @@ -94,4 +154,39 @@ class HomeState extends State { } }); } + + initPlatformStateForUri() async { + if (kIsWeb) { + if (!mounted) return; + _latestUri = Uri.base; + print('uri base $_latestUri'); + eventBus.fire(OnGotDeepLinkUri(_latestUri)); + } else { +// _sub = getUriLinksStream().listen((Uri uri) { +// print('_sub should get uri $uri'); +// // if (!mounted) return; +// _latestUri = uri; +// eventBus.fire(OnGotDeepLinkUri(uri)); +// }, onError: (err) { +// if (!mounted) return; +// _latestUri = null; +// }); +// +// Uri initialUri; +// +// try { +// initialUri = await getInitialUri(); +// print('initial uri: ${initialUri?.path} ${initialUri +// ?.queryParametersAll}'); +// } on PlatformException { +// initialUri = null; +// } on FormatException { +// initialUri = null; +// } +// +// if (!mounted) return; +// _latestUri = initialUri; +// eventBus.fire(OnGotDeepLinkUri(_latestUri)); + } + } } \ No newline at end of file diff --git a/lib/pages/igoshow_learn_more.dart b/lib/pages/igoshow_learn_more.dart new file mode 100644 index 0000000..c6d2a26 --- /dev/null +++ b/lib/pages/igoshow_learn_more.dart @@ -0,0 +1,89 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/http_util.dart'; +import '../widgets/desktop/desktop_igoshow_learn_more.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_igoshow_learn_more.dart'; + +class IGoShowLearnMore extends StatefulWidget { + const IGoShowLearnMore({Key key}) : super(key: key); + + @override + State createState() { + return IGoShowLearnMoreState(); + } +} + +class IGoShowLearnMoreState extends State { + final _scaffoldKey = GlobalKey(); + Map data; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + if (data == null) { + return Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).igoshow, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).igoshow, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileiGoShowLearnMore(data), + tablet: DesktopiGoShowLearnMore(data), + desktop: DesktopiGoShowLearnMore(data), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + HttpUtil.httpGet('v1/get-igoshow-learn-more-page') + .then((value) { + print('$value'); + if (mounted) { + setState(() { + data = value; + }); + } + }); + } +} \ No newline at end of file diff --git a/lib/pages/login.dart b/lib/pages/login.dart new file mode 100644 index 0000000..1f4e5f7 --- /dev/null +++ b/lib/pages/login.dart @@ -0,0 +1,66 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_login.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_login.dart'; + +class Login extends StatefulWidget { + final Key key; + + const Login({this.key}) : super(key: key); + + @override + State createState() { + return LoginState(); + } +} + +class LoginState extends State { + + final _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).login, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).login, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileLogin(), + tablet: DesktopLogin(), + desktop: DesktopLogin(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/me.dart b/lib/pages/me.dart new file mode 100644 index 0000000..da065dd --- /dev/null +++ b/lib/pages/me.dart @@ -0,0 +1,69 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_me.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/double_back_to_close_app_wrapper.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_me.dart'; + +class Me extends StatefulWidget { + final Key key; + + const Me({this.key}) : super(key: key); + + @override + State createState() { + return MeState(); + } +} + +class MeState extends State { + + final _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: sizingInformation.isMobile? null : NavigationBar( + title: S.of(context).me, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).me, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: DoubleBackToCloseAppWrapper( + child: ScreenTypeLayout( + mobile: MobileMe(), + tablet: DesktopMe(), + desktop: DesktopMe(), + ), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 3,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/minipos_learn_more.dart b/lib/pages/minipos_learn_more.dart new file mode 100644 index 0000000..536a100 --- /dev/null +++ b/lib/pages/minipos_learn_more.dart @@ -0,0 +1,94 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/http_util.dart'; +import '../widgets/desktop/desktop_minipos_learn_more.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_minipos_learn_more.dart'; + +class MiniPosLearnMore extends StatefulWidget { + const MiniPosLearnMore({Key key}) : super(key: key); + + @override + State createState() { + return MiniPosLearnMoreState(); + } +} + +class MiniPosLearnMoreState extends State { + final _scaffoldKey = GlobalKey(); + Map data; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + if (data == null) { + return Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return WillPopScope( + child: ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).minipos, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).minipos, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileMiniPosLearnMore(data), + tablet: DesktopMiniPosLearnMore(data), + desktop: DesktopMiniPosLearnMore(data), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ), + onWillPop: () async { + return true; + }, + ); + } + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + HttpUtil.httpGet('v1/get-minipos-learn-more-page') + .then((value) { + print('$value'); + if (mounted) { + setState(() { + data = value; + }); + } + }); + } +} \ No newline at end of file diff --git a/lib/pages/my_addresses.dart b/lib/pages/my_addresses.dart new file mode 100644 index 0000000..70967de --- /dev/null +++ b/lib/pages/my_addresses.dart @@ -0,0 +1,29 @@ + + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../widgets/desktop/desktop_my_addresses.dart'; +import '../widgets/mobile/mobile_my_addresses.dart'; + +class MyAddresses extends StatelessWidget { + final Key key; + final int businessId; + + const MyAddresses({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileMyAddresses(businessId: businessId,), + tablet: DesktopMyAddresses(businessId: businessId,), + desktop: DesktopMyAddresses(businessId: businessId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/my_cards.dart b/lib/pages/my_cards.dart new file mode 100644 index 0000000..6fe1405 --- /dev/null +++ b/lib/pages/my_cards.dart @@ -0,0 +1,46 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../routes.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_my_cards.dart'; +import '../widgets/mobile/mobile_my_cards.dart'; + +class MyCards extends StatelessWidget { + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + store.dispatch(UpdateRedirectRoute('/my-cards')); + Routes.router.navigateTo(context, '/login', replace: true); + return; + } + }); + + if (store.state.user == null) { + return Center( + child: Image.asset( + 'assets/images/denied.png', + width: 48, + height: 48, + fit: BoxFit.fill, + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileMyCards(), + tablet: DesktopMyCards(), + desktop: DesktopMyCards(), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/my_support.dart b/lib/pages/my_support.dart new file mode 100644 index 0000000..908b3d2 --- /dev/null +++ b/lib/pages/my_support.dart @@ -0,0 +1,29 @@ + + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../widgets/desktop/desktop_my_support.dart'; +import '../widgets/mobile/mobile_my_support.dart'; + +class MySupport extends StatelessWidget { + final Key key; + final int businessId; + + const MySupport({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileMySupport(businessId: businessId,), + tablet: DesktopMySupport(businessId: businessId,), + desktop: DesktopMySupport(businessId: businessId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/new_address.dart b/lib/pages/new_address.dart new file mode 100644 index 0000000..08044cf --- /dev/null +++ b/lib/pages/new_address.dart @@ -0,0 +1,36 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../models/located_address.dart'; +import '../widgets/desktop/desktop_new_address.dart'; +import '../widgets/mobile/mobile_new_address.dart'; + +class NewAddress extends StatelessWidget { + final Key key; + final LocatedAddress locatedAddress; + final int businessId; + const NewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileNewAddress( + locatedAddress: locatedAddress, + businessId: businessId, + ), + tablet: DesktopNewAddress( + locatedAddress: locatedAddress, + businessId: businessId, + ), + desktop: DesktopNewAddress( + locatedAddress: locatedAddress, + businessId: businessId, + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/new_comment.dart b/lib/pages/new_comment.dart new file mode 100644 index 0000000..1770d87 --- /dev/null +++ b/lib/pages/new_comment.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_new_comment.dart'; +import '../widgets/mobile/mobile_new_comment.dart'; + +class NewComment extends StatelessWidget { + final int orderId; + + const NewComment(this.orderId, {Key key}) : + super(key: key); + + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileNewComment(orderId,), + tablet: DesktopNewComment(orderId,), + desktop: DesktopNewComment(orderId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/new_ticket.dart b/lib/pages/new_ticket.dart new file mode 100644 index 0000000..ca5fb96 --- /dev/null +++ b/lib/pages/new_ticket.dart @@ -0,0 +1,68 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_new_ticket.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_new_ticket.dart'; + +class NewTicket extends StatefulWidget { + final Key key; + final int businessId; + + const NewTicket({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + State createState() { + return NewTicketState(); + } +} + +class NewTicketState extends State { + + final _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).new_ticket, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).new_ticket, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileNewTicket(businessId: widget.businessId,), + tablet: DesktopNewTicket(businessId: widget.businessId,), + desktop: DesktopNewTicket(businessId: widget.businessId,), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/new_user.dart b/lib/pages/new_user.dart new file mode 100644 index 0000000..b11a2e3 --- /dev/null +++ b/lib/pages/new_user.dart @@ -0,0 +1,45 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_new_user.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_new_user.dart'; + +class NewUser extends StatelessWidget { + const NewUser({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).registration, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).registration, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileNewUser(), + tablet: DesktopNewUser(), + desktop: DesktopNewUser(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/order_detail.dart b/lib/pages/order_detail.dart new file mode 100644 index 0000000..40d9a6a --- /dev/null +++ b/lib/pages/order_detail.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_order_detail.dart'; +import '../widgets/mobile/mobile_order_detail.dart'; + +class OrderDetail extends StatelessWidget { + final int orderId; + final bool fromOrders; + + const OrderDetail(this.orderId, {this.fromOrders = false, Key key}) : + super(key: key); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileOrderDetail(orderId, fromOrders: fromOrders,), + tablet: DesktopOrderDetail(orderId, fromOrders: fromOrders,), + desktop: DesktopOrderDetail(orderId, fromOrders: fromOrders,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/orders.dart b/lib/pages/orders.dart new file mode 100644 index 0000000..cda2983 --- /dev/null +++ b/lib/pages/orders.dart @@ -0,0 +1,46 @@ + +import 'package:flutter/material.dart'; +import '../routes.dart'; +import '../widgets/desktop/desktop_orders.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/mobile/mobile_orders.dart'; + +class Orders extends StatelessWidget { + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + store.dispatch(UpdateRedirectRoute('/orders')); + Routes.router.navigateTo(context, '/login', replace: true); + return; + } + }); + + if (store.state.user == null) { + return Center( + child: Image.asset( + 'assets/images/denied.png', + width: 48, + height: 48, + fit: BoxFit.fill, + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileOrders(), + tablet: DesktopOrders(), + desktop: DesktopOrders(), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/pay_now.dart b/lib/pages/pay_now.dart new file mode 100644 index 0000000..6096f33 --- /dev/null +++ b/lib/pages/pay_now.dart @@ -0,0 +1,31 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_pay_now.dart'; +import '../widgets/mobile/mobile_pay_now.dart'; + +class PayNow extends StatelessWidget { + final int orderId; + + const PayNow(this.orderId, {Key key}) : + super(key: key); + + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobilePayNow(orderId,), + tablet: DesktopPayNow(orderId,), + desktop: DesktopPayNow(orderId,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/plain_page.dart b/lib/pages/plain_page.dart new file mode 100644 index 0000000..5fc2a1b --- /dev/null +++ b/lib/pages/plain_page.dart @@ -0,0 +1,74 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_plain_page.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_plain_page.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../models/blog.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../utils/http_util.dart'; +import '../utils/utils.dart'; + +class PlainPage extends StatefulWidget { + final int businessId; + final String slug; + + const PlainPage(this.slug, {this.businessId, Key key}) : + super(key: key); + + @override + State createState() => PlainPageState(); +} + +class PlainPageState extends State { + Blog blog; + + @override + Widget build(BuildContext context) { + + store.dispatch(UpdateContext(context)); + + if (blog == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobilePlainPage(blog), + tablet: DesktopPlainPage(blog), + desktop: DesktopPlainPage(blog), + ), + ); + } + + @override + void initState() { + super.initState(); + loadData(); + } + + void loadData() { + HttpUtil.httpGet( + 'v1/special-page', + businessId: widget.businessId, + queryParameters: { + 'bid': widget.slug, + } + ).then((value) { + blog = Blog.fromJson(value); + setState(() {}); + }).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/pages/product_detail_page.dart b/lib/pages/product_detail_page.dart new file mode 100644 index 0000000..995b755 --- /dev/null +++ b/lib/pages/product_detail_page.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../models/business.dart'; +import '../models/product.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_product_detail_page.dart'; +import '../widgets/mobile/mobile_product_detail_page.dart'; + +class ProductDetailPage extends StatelessWidget { + final Business business; + final Product product; + + const ProductDetailPage({this.business, this.product, Key key}) : + super(key: key); + + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileProductDetailPage(business: business, product: product,), + tablet: DesktopProductDetailPage(business: business, product: product,), + desktop: DesktopProductDetailPage(business: business, product: product,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/renew_license.dart b/lib/pages/renew_license.dart new file mode 100644 index 0000000..0a7d395 --- /dev/null +++ b/lib/pages/renew_license.dart @@ -0,0 +1,43 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../models/business.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_renew_license.dart'; +import '../widgets/mobile/mobile_renew_license.dart'; + +class RenewLicense extends StatefulWidget { + + const RenewLicense({Key key}) : + super(key: key); + + @override + State createState() => RenewLicenseState(); +} + +class RenewLicenseState extends State { + + @override + Widget build(BuildContext context) { + + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileRenewLicense(Constants.BUSINESS_ID), + tablet: DesktopRenewLicense(Constants.BUSINESS_ID), + desktop: DesktopRenewLicense(Constants.BUSINESS_ID), + ), + ); + } + + @override + void initState() { + super.initState(); + } +} \ No newline at end of file diff --git a/lib/pages/renew_minioffice.dart b/lib/pages/renew_minioffice.dart new file mode 100644 index 0000000..c9b4c34 --- /dev/null +++ b/lib/pages/renew_minioffice.dart @@ -0,0 +1,73 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_renew_minioffice.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_renew_minioffice.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../models/business.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_renew_license.dart'; +import '../widgets/mobile/mobile_renew_license.dart'; + +class RenewMiniOffice extends StatefulWidget { + final int gid; + + const RenewMiniOffice(this.gid, {Key key}) : + super(key: key); + + @override + State createState() => RenewMiniOfficeState(); +} + +class RenewMiniOfficeState extends State { + Map data; + + @override + Widget build(BuildContext context) { + + store.dispatch(UpdateContext(context)); + + if (data == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileRenewMiniOffice(data), + tablet: DesktopRenewMiniOffice(data), + desktop: DesktopRenewMiniOffice(data), + ), + ); + } + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + HttpUtil.httpGet( + 'v1/renew-minioffice/${widget.gid}', + businessId: Constants.BUSINESS_ID, + ).then((value) { + data = value; + setState(() {}); + }).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/pages/reset_password.dart b/lib/pages/reset_password.dart new file mode 100644 index 0000000..b9b1113 --- /dev/null +++ b/lib/pages/reset_password.dart @@ -0,0 +1,47 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_reset_password.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_reset_password.dart'; + +class ResetPassword extends StatelessWidget { + final String mobile; + final String code; + const ResetPassword(this.mobile, {Key key, this.code}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).reset_password, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).reset_password, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileResetPassword(mobile, code: code,), + tablet: DesktopResetPassword(mobile, code: code,), + desktop: DesktopResetPassword(mobile, code: code,), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/search_place.dart b/lib/pages/search_place.dart new file mode 100644 index 0000000..158e68f --- /dev/null +++ b/lib/pages/search_place.dart @@ -0,0 +1,24 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_search_place.dart'; +import '../widgets/mobile/mobile_search_place.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +class SearchPlace extends StatelessWidget { + final Key key; + final int businessId; + const SearchPlace(this.businessId, {this.key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileSearchPlace(businessId), + tablet: DesktopSearchPlace(businessId), + desktop: DesktopSearchPlace(businessId), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/set_password.dart b/lib/pages/set_password.dart new file mode 100644 index 0000000..2a56823 --- /dev/null +++ b/lib/pages/set_password.dart @@ -0,0 +1,47 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_set_password.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_set_password.dart'; + +class SetPassword extends StatelessWidget { + final String mobile; + final String code; + const SetPassword(this.mobile, {Key key, this.code}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).set_password, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).set_password, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileSetPassword(mobile, code: code,), + tablet: DesktopSetPassword(mobile, code: code,), + desktop: DesktopSetPassword(mobile, code: code,), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/shop.dart b/lib/pages/shop.dart new file mode 100644 index 0000000..bc82894 --- /dev/null +++ b/lib/pages/shop.dart @@ -0,0 +1,193 @@ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/cart_info.dart'; +import 'package:flutter_wisetronic/models/cart_line_item.dart'; +import 'package:flutter_wisetronic/models/product.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_shop.dart'; +import '../widgets/mobile/mobile_shop.dart'; + +class Shop extends StatefulWidget { + final int businessId; + + const Shop({this.businessId = Constants.BUSINESS_ID, Key key}) : + super(key: key); + + @override + State createState() => ShopState(); +} + +class ShopState extends State { + StreamSubscription onProductWillAddToCartSubscription; + StreamSubscription onProductWillRemoveFromCartSubscription; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileShop(businessId: widget.businessId,), + tablet: DesktopShop(businessId: widget.businessId,), + desktop: DesktopShop(businessId: widget.businessId,), + ), + ); + } + + @override + void dispose() { + onProductWillAddToCartSubscription?.cancel(); + onProductWillRemoveFromCartSubscription?.cancel(); + super.dispose(); + print("shop parent dispose"); + } + + @override + void initState() { + super.initState(); + + onProductWillAddToCartSubscription = + eventBus.on().listen((event) { + if (mounted) { + + CartInfo cartInfo = + Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + + if (cartInfo == null || cartInfo.productList.length == 0) { + cartInfo = CartInfo(); + cartInfo.id = 0; + cartInfo.amountPaid = 0.0; + cartInfo.businessInfo = event.business; + cartInfo.extraFeeList = []; + cartInfo.discountList = []; + CartLineItem lineItem = Utils.newCartLineItem( + id: 0, + price: event.price, + product: event.product, + name: event.product.name, + description: event.description, + quantity: 1.0); + cartInfo.productList = [lineItem]; + Utils.addSubproductToCard(cartInfo, lineItem); + store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + } else { + if (event.product.productAttributes.length > 0) { + CartLineItem lineItem = Utils.newCartLineItem( + id: 0, + price: event.price, + product: event.product, + name: event.product.name, + description: event.description, + quantity: 1.0); + cartInfo.productList.add(lineItem); + Utils.addSubproductToCard(cartInfo, lineItem); + store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + } else { + int found = -1; + for (var i = 0; i < cartInfo.productList.length; i++) { + if (event.product.id == cartInfo.productList[i].product.id) { + found = i; + break; + } + } + if (found == -1) { + CartLineItem lineItem = Utils.newCartLineItem( + id: 0, + price: event.price, + product: event.product, + name: event.product.name, + description: event.description, + quantity: 1.0); + cartInfo.productList.add(lineItem); + Utils.addSubproductToCard(cartInfo, lineItem); + store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + } else { + if (cartInfo.productList[found].quantity + 1.0 > + event.product.leftNum) { + Fluttertoast.showToast( + msg: S.of(context).product_insufficient, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white); + } else { + cartInfo.productList[found].quantity += 1; + Utils.addSubproductQty(cartInfo, cartInfo.productList[found]); + store.dispatch(new UpdateCartInfo( + Utils.addCartInfoToCartInfoList( + store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + } + } + } + } + } + }); + onProductWillRemoveFromCartSubscription = + eventBus.on().listen((event) { + if (mounted) { + CartInfo cartInfo = + Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + if (cartInfo != null) { + if (cartInfo.productList.length > 0) { + if (event.productListIndex != -1) { + if (cartInfo.productList[event.productListIndex].quantity <= 1) { + String uuid = cartInfo.productList[event.productListIndex].uuid; + cartInfo.productList.removeAt(event.productListIndex); + Utils.removeSubproduct(cartInfo, uuid); + } else { + cartInfo.productList[event.productListIndex].quantity -= 1; + Utils.addSubproductQty(cartInfo, cartInfo.productList[event.productListIndex], remove: true); + } + } else { + int productListIndex = -1; + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.id == event.product.id) { + productListIndex = i; + break; + } + } + if (productListIndex != -1) { + if (cartInfo.productList[productListIndex].quantity <= 1) { + String uuid = cartInfo.productList[productListIndex].uuid; + cartInfo.productList.removeAt(productListIndex); + Utils.removeSubproduct(cartInfo, uuid); + } else { + cartInfo.productList[productListIndex].quantity -= 1; + Utils.addSubproductQty(cartInfo, cartInfo.productList[productListIndex], remove: true); + } + } + } + } + + if (cartInfo.productList.length <= 0) { + store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList( + store.state.cartInfos, cartInfo))); + } else { + store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList( + store.state.cartInfos, cartInfo))); + } + eventBus.fire(new OnCartInfoUpdated()); + } + } + }); + } +} \ No newline at end of file diff --git a/lib/pages/store_product_search.dart b/lib/pages/store_product_search.dart new file mode 100644 index 0000000..2749668 --- /dev/null +++ b/lib/pages/store_product_search.dart @@ -0,0 +1,48 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_store_product_search.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../models/business.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_store_product_search.dart'; + +class StoreProductSearch extends StatelessWidget { + final Business business; + + const StoreProductSearch(this.business, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).search_product, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).search_product, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileStoreProductSearch(business,), + tablet: DesktopStoreProductSearch(business), + desktop: DesktopStoreProductSearch(business), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 1,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/stripe_pay_web.dart b/lib/pages/stripe_pay_web.dart new file mode 100644 index 0000000..9d4c2d8 --- /dev/null +++ b/lib/pages/stripe_pay_web.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../models/order.dart'; +import '../models/payment_platform.dart'; +import '../models/stripe_payment_method.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_stripe_pay_web.dart'; +import '../widgets/mobile/mobile_stripe_pay_web.dart'; + +class StripePayWeb extends StatelessWidget { + final Order order; + final PaymentPlatform paymentPlatform; + final StripePaymentMethod stripePaymentMethod; + + const StripePayWeb(this.order, this.paymentPlatform, {this.stripePaymentMethod}); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), + tablet: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), + desktop: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/tutorials.dart b/lib/pages/tutorials.dart new file mode 100644 index 0000000..d72ff80 --- /dev/null +++ b/lib/pages/tutorials.dart @@ -0,0 +1,66 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../events/eventbus.dart'; +import '../events/events.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_tutorials.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_navigation_drawer.dart'; +import '../widgets/mobile/mobile_tutorials.dart'; + +class Tutorials extends StatefulWidget { + const Tutorials({Key key}) : super(key: key); + + @override + State createState() { + return TutorialsState(); + } + +} + +class TutorialsState extends State { + final _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).tutorials, + back: true, + breadCrumbs: [BreadCrumb(S.of(context).tutorials, null)], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileTutorials(), + tablet: DesktopTutorials(), + desktop: DesktopTutorials(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + _scaffoldKey.currentState.openDrawer(); + } + }); + } +} \ No newline at end of file diff --git a/lib/pages/user_profile.dart b/lib/pages/user_profile.dart new file mode 100644 index 0000000..5c2eb02 --- /dev/null +++ b/lib/pages/user_profile.dart @@ -0,0 +1,46 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../widgets/desktop/desktop_user_profile.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_user_profile.dart'; + +class UserProfile extends StatelessWidget { + const UserProfile({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + appBar: NavigationBar( + title: S.of(context).profile, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).profile, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileUserProfile(), + tablet: DesktopUserProfile(), + desktop: DesktopUserProfile(), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/pages/view_blog.dart b/lib/pages/view_blog.dart new file mode 100644 index 0000000..762c30d --- /dev/null +++ b/lib/pages/view_blog.dart @@ -0,0 +1,67 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_view_blog.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_view_blog.dart'; + +class ViewBlog extends StatefulWidget { + final Key key; + final int bid; + + const ViewBlog(this.bid, {this.key}) : super(key: key); + + @override + State createState() { + return ViewBlogState(); + } +} + +class ViewBlogState extends State { + + final _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).view_blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).view_blog, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileViewBlog(widget.bid), + tablet: DesktopViewBlog(widget.bid), + desktop: DesktopViewBlog(widget.bid), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/view_ticket.dart b/lib/pages/view_ticket.dart new file mode 100644 index 0000000..b89a5e7 --- /dev/null +++ b/lib/pages/view_ticket.dart @@ -0,0 +1,67 @@ + +import 'package:flutter/material.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +import '../constants.dart'; +import '../generated/l10n.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/desktop/desktop_view_ticket.dart'; +import '../widgets/general/bottom_nav.dart'; +import '../widgets/general/breadcrumbs.dart'; +import '../widgets/general/navigationbar.dart'; +import '../widgets/mobile/MobileBottomNav.dart'; +import '../widgets/mobile/mobile_view_ticket.dart'; + +class ViewTicket extends StatefulWidget { + final Key key; + final int ticketId; + + const ViewTicket(this.ticketId, {this.key}) : super(key: key); + + @override + State createState() { + return ViewTicketState(); + } +} + +class ViewTicketState extends State { + + final _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ResponsiveBuilder( + builder: (context, sizingInformation) => + Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).view_ticket, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).view_ticket, null), + ], + breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT, + ), + drawer: null, + body: ScreenTypeLayout( + mobile: MobileViewTicket(widget.ticketId), + tablet: DesktopViewTicket(widget.ticketId), + desktop: DesktopViewTicket(widget.ticketId), + ), + bottomNavigationBar: ScreenTypeLayout( + mobile: MobileBottomNav(currentIndex: 0,), + tablet: BottomNav(), + desktop: BottomNav(), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/routes.dart b/lib/routes.dart index ebbab7f..a5a32d0 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,9 +1,44 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/pages/buy_service.dart'; +import 'package:flutter_wisetronic/pages/contact_us.dart'; +import 'package:flutter_wisetronic/pages/plain_page.dart'; +import 'package:flutter_wisetronic/pages/renew_license.dart'; +import 'package:flutter_wisetronic/pages/renew_minioffice.dart'; +import 'package:flutter_wisetronic/store/store.dart'; + +import 'constants.dart'; +import 'pages/blog.dart'; +import 'pages/change_mobile_or_email.dart'; +import 'pages/change_password.dart'; +import 'pages/checkout.dart'; +import 'pages/coupons.dart'; import 'pages/download.dart'; -import 'store/store.dart'; +import 'pages/forgot_password.dart'; import 'pages/home.dart'; +import 'pages/igoshow_learn_more.dart'; +import 'pages/login.dart'; +import 'pages/me.dart'; +import 'pages/minipos_learn_more.dart'; +import 'pages/my_addresses.dart'; +import 'pages/my_cards.dart'; +import 'pages/my_support.dart'; +import 'pages/new_comment.dart'; +import 'pages/new_ticket.dart'; +import 'pages/new_user.dart'; +import 'pages/order_detail.dart'; +import 'pages/orders.dart'; +import 'pages/pay_now.dart'; +import 'pages/reset_password.dart'; +import 'pages/search_place.dart'; +import 'pages/set_password.dart'; +import 'pages/shop.dart'; +import 'pages/tutorials.dart'; +import 'pages/user_profile.dart'; +import 'pages/view_blog.dart'; +import 'pages/view_ticket.dart'; +// import 'widgets/mobile/ocr_scan.dart'; class Routes { static final router = FluroRouter(); @@ -11,7 +46,7 @@ class Routes { static void configure() { router.define('/', handler: new Handler( handlerFunc: (BuildContext context, Map> params) { - return Home(null); + return Home(title: Constants.APP_TITLE,); }), transitionType: TransitionType.fadeIn ); @@ -21,5 +56,228 @@ class Routes { }), transitionType: TransitionType.inFromRight ); + router.define('/minipos-learn-more', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return MiniPosLearnMore(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/igoshow-learn-more', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return IGoShowLearnMore(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/tutorials', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Tutorials(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/login', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Login(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/me', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Me(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/change-password', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ChangePassword(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/user-profile', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return UserProfile(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/new-user', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return NewUser(); + }), + transitionType: TransitionType.inFromRight + ); + router.define('/set-password/:mobile/:code', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return SetPassword(params['mobile'][0], code: params['code'][0]); + } + )); + router.define('/forgot-password', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ForgotPassword(); + } + )); + router.define('/reset-password/:mobile/:code', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ResetPassword(params['mobile'][0], code: params['code'][0]); + } + )); + router.define('/change-mobile-email/:ismobile', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + if (params['ismobile'][0] == '1') { + return ChangeMobileOrEmail(true); + } + return ChangeMobileOrEmail(false); + } + )); + router.define('/my-addresses/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return MyAddresses(businessId: int.parse(params['business_id'][0]),); + } + )); + router.define('/my-support/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return MySupport(businessId: int.parse(params['business_id'][0]),); + } + )); + router.define('/new-ticket/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return NewTicket(businessId: int.parse(params['business_id'][0]),); + } + )); + router.define('/search-place/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return SearchPlace(int.parse(params['business_id'][0])); + } + )); + router.define('/view-ticket/:ticket_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ViewTicket(int.parse(params['ticket_id'][0]),); + } + )); + router.define('/blog/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Blog(businessId: int.parse(params['business_id'][0]),); + } + )); + router.define('/view-blog/:bid', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ViewBlog(int.parse(params['bid'][0]),); + } + )); + router.define('/shop/:business_id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Shop(businessId: int.parse(params['business_id'][0]),); + } + )); + router.define('/shop', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Shop(); + } + )); + // router.define('/ocr-scan', handler: new Handler( + // handlerFunc: (BuildContext context, Map> params) { + // return OCRScan(); + // } + // )); + router.define('/checkout/:id', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Checkout(int.parse(params['id'][0])); + }), + ); + router.define('/paynow/:orderId', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PayNow(int.parse(params['orderId'][0])); + }), + ); + router.define('/orders', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Orders(); + } + )); + router.define('/my-cards', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return MyCards(); + } + )); + router.define('/orderdetail/:orderId', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return OrderDetail(int.parse(params['orderId'][0])); + }), + ); + router.define('/new-comment/:orderId', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return NewComment(int.parse(params['orderId'][0])); + }), + ); + router.define('/coupons/:contactId', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return Coupons(int.parse(params['contactId'][0])); + }), + ); + router.define('/service-policy', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PlainPage( + 'service-policy', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ); + }), + ); + router.define('/return-policy', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PlainPage( + 'return-policy', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ); + }), + ); + router.define('/privacy-policy', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PlainPage( + 'privacy-policy', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ); + }), + ); + router.define('/end-user-license-agreement', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PlainPage( + 'end-user-license-agreement', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ); + }), + ); + router.define('/about-us', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return PlainPage( + 'about-us', + // businessId: Constants.BUSINESS_ID, + businessId: 310, + ); + }), + ); + router.define('/contact-us', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return ContactUs( + businessId: Constants.BUSINESS_ID, + ); + }), + ); + router.define('/renew-license', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return RenewLicense(); + } + )); + router.define('/renew-minioffice/:gid', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return RenewMiniOffice(int.parse(params['gid'][0])); + } + )); + router.define('/buy-service/:gid/:servicename', handler: new Handler( + handlerFunc: (BuildContext context, Map> params) { + return BuyService(int.parse(params['gid'][0]), params['servicename'][0]); + } + )); } } \ No newline at end of file diff --git a/lib/store/actions.dart b/lib/store/actions.dart index 9716b02..2b96ca6 100644 --- a/lib/store/actions.dart +++ b/lib/store/actions.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/models/user.dart'; +import '../models/located_address.dart'; +import '../models/cart_info.dart'; +import '../models/user.dart'; class UpdateContext { final BuildContext context; @@ -15,4 +17,39 @@ class UpdateLocale { class UpdateCurrentUser { final User user; UpdateCurrentUser(this.user); +} + +class UpdateRedirectRoute { + final String route; + UpdateRedirectRoute(this.route); +} + +class UpdateCartInfo { + final List cartInfos; + UpdateCartInfo(this.cartInfos); +} + +class UpdateLocatedAddress { + final LocatedAddress locatedAddress; + UpdateLocatedAddress(this.locatedAddress); +} + +class UpdateFcmToken { + final String token; + UpdateFcmToken(this.token); +} + +class UpdateLastVisit { + final List lastVisit; + UpdateLastVisit(this.lastVisit); +} + +class UpdateDeviceId { + final String deviceId; + UpdateDeviceId(this.deviceId); +} + +class UpdateTableNumber { + final String tableNumber; + UpdateTableNumber(this.tableNumber); } \ No newline at end of file diff --git a/lib/store/reducer/app_reducer.dart b/lib/store/reducer/app_reducer.dart index deec659..ed0c269 100644 --- a/lib/store/reducer/app_reducer.dart +++ b/lib/store/reducer/app_reducer.dart @@ -1,13 +1,27 @@ -import 'package:flutter_wisetronic/store/reducer/context_reducer.dart'; -import 'package:flutter_wisetronic/store/reducer/locale_reducer.dart'; -import 'package:flutter_wisetronic/store/reducer/user_reducer.dart'; -import 'package:flutter_wisetronic/store/state/app_state.dart'; +import '../../store/reducer/cart_info_reducer.dart'; +import '../../store/reducer/context_reducer.dart'; +import '../../store/reducer/device_id_reducer.dart'; +import '../../store/reducer/fcmtoken_reducer.dart'; +import '../../store/reducer/last_visit_reducer.dart'; +import '../../store/reducer/locale_reducer.dart'; +import '../../store/reducer/locate_address_reducer.dart'; +import '../../store/reducer/redirect_route_reducer.dart'; +import '../../store/reducer/table_number_reducer.dart'; +import '../../store/reducer/user_reducer.dart'; +import '../../store/state/app_state.dart'; AppState appReducer(AppState state, action) { return AppState( context: contextReducer(state.context, action), locale: localeReducer(state.locale, action), user: userReducer(state.user, action), + redirectRoute: redirectRouteReducer(state.redirectRoute, action), + locatedAddress: locatedAddressReducer(state.locatedAddress, action), + fcmToken: fcmtokenReducer(state.fcmToken, action), + lastVisit: lastVisitReducer(state.lastVisit, action), + cartInfos: cartInfoReducer(state.cartInfos, action), + deviceId: deviceIdReducer(state.deviceId, action), + tableNumber: tableNumberReducer(state.tableNumber, action) ); } \ No newline at end of file diff --git a/lib/store/reducer/cart_info_reducer.dart b/lib/store/reducer/cart_info_reducer.dart new file mode 100644 index 0000000..4f2db10 --- /dev/null +++ b/lib/store/reducer/cart_info_reducer.dart @@ -0,0 +1,13 @@ + +import '../../models/cart_info.dart'; +import 'package:redux/redux.dart'; + +import '../actions.dart'; + +final cartInfoReducer = combineReducers>([ + TypedReducer, UpdateCartInfo>(_updateCartInfo) +]); + +List _updateCartInfo(List cartInfos, action) { + return action.cartInfos; +} \ No newline at end of file diff --git a/lib/store/reducer/device_id_reducer.dart b/lib/store/reducer/device_id_reducer.dart new file mode 100644 index 0000000..b7942ab --- /dev/null +++ b/lib/store/reducer/device_id_reducer.dart @@ -0,0 +1,11 @@ +import 'package:redux/redux.dart'; + +import '../actions.dart'; + +final deviceIdReducer = combineReducers([ + TypedReducer(_updateDeviceId) +]); + +String _updateDeviceId(String deviceId, action) { + return action.deviceId; +} \ No newline at end of file diff --git a/lib/store/reducer/fcmtoken_reducer.dart b/lib/store/reducer/fcmtoken_reducer.dart new file mode 100644 index 0000000..6a4c228 --- /dev/null +++ b/lib/store/reducer/fcmtoken_reducer.dart @@ -0,0 +1,11 @@ +import 'package:redux/redux.dart'; + +import '../actions.dart'; + +final fcmtokenReducer = combineReducers([ + TypedReducer(_updateFcmToken) +]); + +String _updateFcmToken(String token, action) { + return action.token; +} \ No newline at end of file diff --git a/lib/store/reducer/last_visit_reducer.dart b/lib/store/reducer/last_visit_reducer.dart new file mode 100644 index 0000000..5531bfa --- /dev/null +++ b/lib/store/reducer/last_visit_reducer.dart @@ -0,0 +1,11 @@ +import 'package:redux/redux.dart'; + +import '../actions.dart'; + +final lastVisitReducer = combineReducers>([ + TypedReducer, UpdateLastVisit>(_updateLastVisit) +]); + +List _updateLastVisit(List lastVisit, action) { + return action.lastVisit; +} \ No newline at end of file diff --git a/lib/store/reducer/locate_address_reducer.dart b/lib/store/reducer/locate_address_reducer.dart new file mode 100644 index 0000000..1004a56 --- /dev/null +++ b/lib/store/reducer/locate_address_reducer.dart @@ -0,0 +1,12 @@ +import 'package:redux/redux.dart'; + +import '../../models/located_address.dart'; +import '../actions.dart'; + +final locatedAddressReducer = combineReducers([ + TypedReducer(_updateLocatedAddress) +]); + +LocatedAddress _updateLocatedAddress(LocatedAddress locatedAddress, action) { + return action.locatedAddress; +} \ No newline at end of file diff --git a/lib/store/reducer/redirect_route_reducer.dart b/lib/store/reducer/redirect_route_reducer.dart new file mode 100644 index 0000000..14c57ec --- /dev/null +++ b/lib/store/reducer/redirect_route_reducer.dart @@ -0,0 +1,11 @@ + +import 'package:redux/redux.dart'; +import '../actions.dart'; + +final redirectRouteReducer = combineReducers([ + TypedReducer(_updateRedirectRoute) +]); + +String _updateRedirectRoute(String route, action) { + return action.route; +} \ No newline at end of file diff --git a/lib/store/reducer/table_number_reducer.dart b/lib/store/reducer/table_number_reducer.dart new file mode 100644 index 0000000..6653ad8 --- /dev/null +++ b/lib/store/reducer/table_number_reducer.dart @@ -0,0 +1,11 @@ +import 'package:redux/redux.dart'; + +import '../actions.dart'; + +final tableNumberReducer = combineReducers([ + TypedReducer(_updateTableNumber) +]); + +String _updateTableNumber(String tableNumber, action) { + return action.tableNumber; +} \ No newline at end of file diff --git a/lib/store/state/app_state.dart b/lib/store/state/app_state.dart index f9c1e61..44e9d57 100644 --- a/lib/store/state/app_state.dart +++ b/lib/store/state/app_state.dart @@ -1,14 +1,25 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/models/user.dart'; +import '../../models/located_address.dart'; +import '../../models/cart_info.dart'; +import '../../models/user.dart'; @immutable class AppState { final BuildContext context; final Locale locale; final User user; + final String redirectRoute; + final List cartInfos; + final LocatedAddress locatedAddress; + final String fcmToken; + final List lastVisit; + final String deviceId; + final String tableNumber; - AppState({this.context, this.locale, this.user}); + AppState({this.context, this.locale, this.user, this.redirectRoute, + this.cartInfos, this.locatedAddress, this.fcmToken, this.lastVisit, + this.deviceId, this.tableNumber}); factory AppState.init() => AppState(); @@ -19,6 +30,13 @@ class AppState { context: context ?? this.context, locale: locale ?? this.locale, user: user ?? this.user, + redirectRoute: redirectRoute ?? this.redirectRoute, + cartInfos: cartInfos ?? this.cartInfos, + locatedAddress: locatedAddress ?? this.locatedAddress, + fcmToken: fcmToken ?? this.fcmToken, + lastVisit: lastVisit ?? this.lastVisit, + deviceId: deviceId ?? this.deviceId, + tableNumber: tableNumber ?? this.tableNumber ); } @@ -26,7 +44,15 @@ class AppState { int get hashCode => context.hashCode ^ locale.hashCode ^ - user.hashCode; + user.hashCode ^ + redirectRoute.hashCode ^ + cartInfos.hashCode ^ + locatedAddress.hashCode ^ + fcmToken.hashCode ^ + lastVisit.hashCode ^ + deviceId.hashCode ^ + tableNumber.hashCode; + @override bool operator ==(Object other) => @@ -34,9 +60,20 @@ class AppState { other is AppState && context == other.context && locale == other.locale && - user == other.user; + user == other.user && + redirectRoute == other.redirectRoute && + cartInfos == other.cartInfos && + locatedAddress == other.locatedAddress && + fcmToken == other.fcmToken && + lastVisit == other.lastVisit && + deviceId == other.deviceId && + tableNumber == other.tableNumber; @override String toString() => - 'AppState(context: $context, locale: $locale), user: $user'; + 'AppState(context: $context, locale: $locale), user: $user, ' + 'redirectRoute: $redirectRoute, cart info: $cartInfos, ' + 'locate address: $locatedAddress, fcm: $fcmToken, ' + 'last visit: $lastVisit, deviceId: $deviceId, ' + 'tableNumber: $tableNumber'; } \ No newline at end of file diff --git a/lib/utils/configure_nonweb.dart b/lib/utils/configure_nonweb.dart new file mode 100644 index 0000000..2e137c6 --- /dev/null +++ b/lib/utils/configure_nonweb.dart @@ -0,0 +1,4 @@ + +void configureApp() { + +} \ No newline at end of file diff --git a/lib/utils/configure_web.dart b/lib/utils/configure_web.dart new file mode 100644 index 0000000..8ecbb99 --- /dev/null +++ b/lib/utils/configure_web.dart @@ -0,0 +1,6 @@ + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +void configureApp() { + setUrlStrategy(PathUrlStrategy()); +} \ No newline at end of file diff --git a/lib/utils/fake_iframe_web.dart b/lib/utils/fake_iframe_web.dart new file mode 100644 index 0000000..a3a26b3 --- /dev/null +++ b/lib/utils/fake_iframe_web.dart @@ -0,0 +1,24 @@ + +import 'package:flutter/material.dart'; + +class IFrameWeb extends StatefulWidget { + final String width; + final String height; + final String src; + + const IFrameWeb({this.width, this.height, this.src}); + + @override + State createState() { + return IFrameWebState(); + } +} + +class IFrameWebState extends State { + + @override + Widget build(BuildContext context) { + return Container(); + } + +} \ No newline at end of file diff --git a/lib/utils/http_util.dart b/lib/utils/http_util.dart index a119f97..6fe26ad 100644 --- a/lib/utils/http_util.dart +++ b/lib/utils/http_util.dart @@ -81,14 +81,13 @@ class HttpUtil { onReceiveProgress: receiveProgress, ).timeout(const Duration(seconds: 30)); - print('HttpGet success. Request: $requestUrl, Response: ${response.statusCode}, Headers: ${response.request.headers}'); // print(response.data); // Utils.jsonPrettyPrint(response.data); int statusCode = response.statusCode; return response.data; } on DioError catch(e) { - print('error $e'); + print('HttpGet failed, request: $requestUrl, headers: $localHeaders, error ${e.response}'); if (returnError) { return e; } @@ -103,6 +102,7 @@ class HttpUtil { bool isFormData = false, Map additionalHeaders, Map body, + FormData formData, Function(int, int) sendProgress, Function(int, int) receiveProgress, bool returnError = false, @@ -121,6 +121,9 @@ class HttpUtil { localHeaders['Http-App-Key'] = platformName; localHeaders['Http-Device-Type'] = platformName; + // Add the line below will not work. + //localHeaders['Content-Type'] = isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType; + String requestUrl = Constants.BASE_API_URL + url; if (url.startsWith('http')) { requestUrl = url; @@ -135,16 +138,15 @@ class HttpUtil { Response response = await dio.post( requestUrl, queryParameters: queryParameters == null ? {} : queryParameters, - data: isFormData ? FormData.fromMap(body) : json.encode(body), + data: isFormData ? ((formData != null)?formData:FormData.fromMap(body)) : json.encode(body), options: Options( headers: localHeaders, -// contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType, + contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType, ), onSendProgress: sendProgress, onReceiveProgress: receiveProgress, ).timeout(Duration(seconds: 30)); - print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}'); // Utils.jsonPrettyPrint(response.data); int statusCode = response.statusCode; @@ -152,9 +154,8 @@ class HttpUtil { callback(response); } return response.data; - } on DioError catch(e) { + } on DioError catch(e, stacktrace) { if (e.response != null) { - print('HttpPost failed. Request: ${e.request.uri}, Headers: ${e.response.request.headers}'); try { Utils.jsonPrettyPrint(e.response.data); } catch (err) { @@ -215,7 +216,6 @@ class HttpUtil { onReceiveProgress: receiveProgress, ).timeout(Duration(seconds: 30)); - print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}'); // Utils.jsonPrettyPrint(response.data); int statusCode = response.statusCode; @@ -224,7 +224,6 @@ class HttpUtil { } return response.data; } on DioError catch(e) { - print('HttpPost failed. Request: ${e.request.uri}'); if (e.response != null) { Utils.jsonPrettyPrint(e.response.data); } @@ -284,7 +283,6 @@ class HttpUtil { onReceiveProgress: receiveProgress, ).timeout(Duration(seconds: 30)); - print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}'); // Utils.jsonPrettyPrint(response.data); int statusCode = response.statusCode; @@ -293,7 +291,6 @@ class HttpUtil { } return response.data; } on DioError catch(e) { - print('HttpPost failed. Request: ${e.request.uri}'); if (e.response != null) { Utils.jsonPrettyPrint(e.response.data); } @@ -349,7 +346,6 @@ class HttpUtil { ), ).timeout(Duration(seconds: 30)); - print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}'); // Utils.jsonPrettyPrint(response.data); int statusCode = response.statusCode; @@ -358,7 +354,6 @@ class HttpUtil { } return response.data; } on DioError catch(e) { - print('HttpPost failed. Request: ${e.request.uri}'); if (e.response != null) { Utils.jsonPrettyPrint(e.response.data); } diff --git a/lib/utils/iframe_web.dart b/lib/utils/iframe_web.dart new file mode 100644 index 0000000..50f3f04 --- /dev/null +++ b/lib/utils/iframe_web.dart @@ -0,0 +1,39 @@ + +import 'dart:html'; + +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; + +class IFrameWeb extends StatefulWidget { + final String width; + final String height; + final String src; + + const IFrameWeb({this.width, this.height, this.src}); + + @override + State createState() { + return IFrameWebState(); + } +} + +class IFrameWebState extends State { + final IFrameElement _iframeElement = IFrameElement(); + + @override + Widget build(BuildContext context) { + _iframeElement.height = widget.height; + _iframeElement.width = widget.width; + _iframeElement.src = widget.src; + _iframeElement.style.border = 'none'; + ui.platformViewRegistry.registerViewFactory( + 'iframeElement', + (int viewId) => _iframeElement, + ); + return HtmlElementView( + key: UniqueKey(), + viewType: 'iframeElement', + ); + } + +} \ No newline at end of file diff --git a/lib/utils/shop_scroll_controller.dart b/lib/utils/shop_scroll_controller.dart new file mode 100644 index 0000000..f987c38 --- /dev/null +++ b/lib/utils/shop_scroll_controller.dart @@ -0,0 +1,191 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'shop_scroll_coordinator.dart'; +import 'shop_scroll_position.dart'; + +/// 滑动控制器 +class ShopScrollController extends ScrollController { + final ShopScrollCoordinator coordinator; + + /// 为可滚动小部件创建一个控制器。 + /// `initialScrollOffset`和`keepScrollOffset`的值不能为null。 + ShopScrollController( + this.coordinator, { + double initialScrollOffset = 0.0, + this.keepScrollOffset = true, + this.debugLabel, + }) : assert(initialScrollOffset != null), + assert(keepScrollOffset != null), + _initialScrollOffset = initialScrollOffset; + + /// 用于[offset]的初始值。 + /// 如果[keepScrollOffset]为false或尚未保存滚动偏移量, + /// 则创建并附加到此控制器的新[ScrollPosition]对象的偏移量将初始化为该值。 + /// 默认为0.0。 + double get initialScrollOffset => _initialScrollOffset; + final double _initialScrollOffset; + + /// 每次滚动完成时,请使用[PageStorage]保存当前滚动[offset],如果重新创建了此控制器的可滚动内容,则将其还原。 + /// + /// 如果将此属性设置为false,则永远不会保存滚动偏移量,并且始终使用[initialScrollOffset] + /// 来初始化滚动偏移量。 如果为true(默认值),则第一次创建控制器的可滚动对象时将使用初始 + /// 滚动偏移量,因为尚无要还原的滚动偏移量。 随后,将恢复保存的偏移,并且忽略[initialScrollOffset]。 + /// + /// 也可以看看: + /// * [PageStorageKey],当同一路径中出现多个滚动条时,应使用[PageStorageKey] + /// 来区分用于保存滚动偏移量的[PageStorage]位置。 + final bool keepScrollOffset; + + /// [toString]输出中使用的标签。 旨在帮助在调试输出中标识滚动控制器实例。 + final String debugLabel; + + /// The currently attached positions. 当前附加的 [positions]。 + /// 这不应直接突变。 可以使用[attach]和[detach]添加和删除[ScrollPosition]对象。 + @protected + Iterable get positions => _positions; + final List _positions = []; + + /// 是否有任何[ScrollPosition]对象已使用[attach]方法将自身附加到[ScrollController]。 + /// + /// 如果为false,则不得调用与[ScrollPosition]交互的成员, + /// 例如[position],[offset],[animateTo]和[jumpTo]。 + bool get hasClients => _positions.isNotEmpty; + + /// 返回附加的[ScrollPosition],可以从中获取[ScrollView]的实际滚动偏移量。 + /// + /// Calling this is only valid when only a single position is attached. + /// 仅在仅连接一个[position]时调用此选项才有效。 + ShopScrollPosition get position { + assert(_positions.isNotEmpty, + 'ScrollController not attached to any scroll views.'); + assert(_positions.length == 1, + 'ScrollController attached to multiple scroll views.'); + return _positions.single; + } + + /// 可滚动小部件的当前滚动偏移量 + /// 可滚动小部件的当前滚动偏移量。要求控制器仅控制一个可滚动小部件。 + double get offset => position.pixels; + + /// 从当前位置到给定值的位置动画。 + /// 任何活动的动画都将被取消。 如果用户当前正在滚动,则该操作将被取消。 + /// 返回的[Future]将在动画结束时完成,无论它是否成功完成或是否被过早中断。 + /// + /// 每当用户尝试手动滚动或启动其他活动,或者动画到达视口边缘并尝试过度滚动时,动画都会中断。 + /// (如果[ScrollPosition]不会过度滚动,而是允许滚动超出范围,那么超出范围不会中断动画。) + /// + /// 动画对视口或内容尺寸的更改无动于衷。 + /// + /// 一旦动画完成,如果滚动位置的值不稳定,则滚动位置将尝试开始弹道活动。 + /// (例如,如果滚动超出范围,并且在这种情况下滚动位置通常会弹回) + /// + /// 持续时间不能为零。 要在没有动画的情况下跳至特定值,请使用[jumpTo]。 + Future animateTo( + double offset, { + @required Duration duration, + @required Curve curve, + }) { + assert(_positions.isNotEmpty, + 'ScrollController not attached to any scroll views.'); + final List> animations = List>(_positions.length); + for (int i = 0; i < _positions.length; i += 1) + animations[i] = + _positions[i].animateTo(offset, duration: duration, curve: curve); + return Future.wait(animations).then((List _) => null); + } + + /// 将滚动位置从其当前值跳转到给定值,而不进行动画处理,也无需检查新值是否在范围内。 + /// 任何活动的动画都将被取消。 如果用户当前正在滚动,则该操作将被取消。 + /// + /// 如果此方法更改了滚动位置,则将分派开始/更新/结束滚动通知的序列。 此方法不能生成过滚动通知。 + /// + /// 跳跃之后,如果数值超出范围,则立即开始弹道活动。 + void jumpTo(double value) { + assert(_positions.isNotEmpty, + 'ScrollController not attached to any scroll views.'); + for (ScrollPosition position in List.from(_positions)) + position.jumpTo(value); + } + + /// 在此控制器上注册给定位置。 + /// 此函数返回后,此控制器上的[animateTo]和[jumpTo]方法将操纵给定位置。 + void attach(ScrollPosition position) { + assert(!_positions.contains(position)); + _positions.add(position); + position.addListener(notifyListeners); + } + + /// 用此控制器注销给定位置。 + /// 此函数返回后,此控制器上的[animateTo]和[jumpTo]方法将不会操纵给定位置。 + void detach(ScrollPosition position) { + assert(_positions.contains(position)); + position.removeListener(notifyListeners); + _positions.remove(position); + } + + @override + void dispose() { + for (ScrollPosition position in _positions) + position.removeListener(notifyListeners); + super.dispose(); + } + + /// 创建一个[ScrollPosition]供[Scrollable]小部件使用。 + /// + /// 子类可以重写此功能,以自定义其控制的可滚动小部件使用的[ScrollPosition]。 + /// 例如,[PageController]重写此函数以返回面向页面的滚动位置子类, + /// 该子类在可滚动窗口小部件调整大小时保持同一页面可见。 + /// + /// 默认情况下,返回[ScrollPositionWithSingleContext]。 + /// 参数通常传递给正在创建的[ScrollPosition]: + /// + /// * `physics`:[ScrollPhysics]的一个实例,它确定[ScrollPosition]对用户交互的反应方式, + /// 释放或甩动时如何模拟滚动等。该值不会为null。 它通常来自[ScrollView]或其他创建 + /// [Scrollable]的小部件,或者(如果未提供)来自环境[ScrollConfiguration]。 + /// * `context`:一个[ScrollContext],用于与拥有[ScrollPosition]的对象进行通信 + /// (通常是[Scrollable]本身)。 + /// * `oldPosition`: 如果这不是第一次为此[Scrollable]创建[ScrollPosition], + /// 则它将是前一个实例。 当环境已更改并且[Scrollable]需要重新创建[ScrollPosition] + /// 对象时,将使用此方法。 第一次创建[ScrollPosition]时为null。 + ScrollPosition createScrollPosition(ScrollPhysics physics, + ScrollContext context, ScrollPosition oldPosition) { + return ShopScrollPosition( + coordinator: coordinator, + physics: physics, + context: context, + initialPixels: initialScrollOffset, + keepScrollOffset: keepScrollOffset, + oldPosition: oldPosition, + debugLabel: debugLabel, + ); + } + + @override + String toString() { + final List description = []; + debugFillDescription(description); + return '${describeIdentity(this)}(${description.join(", ")})'; + } + + /// 在给定的描述中添加其他信息,以供[toString]使用。 + /// 此方法使子类更易于协调以提供高质量的[toString]实现。 [ScrollController] + /// 基类上的[toString]实现调用[debugFillDescription]来从子类中收集有用的信息,以合并到其返回值中。 + /// 如果您重写了此方法,请确保通过调用`super.debugFillDescription(description)`来启动方法。 + @mustCallSuper + void debugFillDescription(List description) { + super.debugFillDescription(description); + if (debugLabel != null) description.add(debugLabel); + if (initialScrollOffset != 0.0) + description.add( + 'initialScrollOffset: ${initialScrollOffset.toStringAsFixed(1)}, '); + if (_positions.isEmpty) { + description.add('no clients'); + } else if (_positions.length == 1) { + // 实际上不列出客户端本身,因为它的toString可能引用了我们。 + description.add('one client, offset ${offset?.toStringAsFixed(1)}'); + } else { + description.add('${_positions.length} clients'); + } + } +} diff --git a/lib/utils/shop_scroll_coordinator.dart b/lib/utils/shop_scroll_coordinator.dart new file mode 100644 index 0000000..624dbeb --- /dev/null +++ b/lib/utils/shop_scroll_coordinator.dart @@ -0,0 +1,110 @@ +import 'dart:math' as math; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; + +import 'shop_scroll_controller.dart'; +import 'shop_scroll_position.dart'; + +enum PageExpandState { NotExpand, Expanding, Expanded } + +/// 协调器 +class ShopScrollCoordinator { + /// 页面主 CustomScrollView 控制 + final String pageLabel = "page"; + + ShopScrollController _pageScrollController; + double Function() pinnedHeaderSliverHeightBuilder; + + ShopScrollPosition get _pageScrollPosition => _pageScrollController.position; + + ScrollDragController scrollDragController; + + /// 主页面滑动部件默认位置 + double _pageInitialOffset; + + /// 获取主页面滑动控制器 + ShopScrollController pageScrollController([double initialOffset = 0.0]) { + assert(initialOffset != null, initialOffset >= 0.0); + _pageInitialOffset = initialOffset; + _pageScrollController = ShopScrollController(this, + debugLabel: pageLabel, initialScrollOffset: initialOffset); + return _pageScrollController; + } + + /// 创建并获取一个子滑动控制器 + ShopScrollController newChildScrollController([String debugLabel]) => + ShopScrollController(this, debugLabel: debugLabel); + + /// 子部件滑动数据协调 + /// [userScrollDirection]用户滑动方向 + /// [position]被滑动的子部件的位置信息 + void applyUserOffset(double delta, + [ScrollDirection userScrollDirection, ShopScrollPosition position]) { + if (userScrollDirection == ScrollDirection.reverse) { + updateUserScrollDirection(_pageScrollPosition, userScrollDirection); + final innerDelta = _pageScrollPosition.applyClampedDragUpdate(delta); + if (innerDelta != 0.0) { + updateUserScrollDirection(position, userScrollDirection); + position.applyFullDragUpdate(innerDelta); + } + } else { + updateUserScrollDirection(position, userScrollDirection); + final outerDelta = position.applyClampedDragUpdate(delta); + if (outerDelta != 0.0) { + updateUserScrollDirection(_pageScrollPosition, userScrollDirection); + _pageScrollPosition.applyFullDragUpdate(outerDelta); + } + } + } + + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent, + ShopScrollPosition position) { + if (pinnedHeaderSliverHeightBuilder != null) { + maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder(); + maxScrollExtent = math.max(0.0, maxScrollExtent); + } + return position.applyContentDimensions( + minScrollExtent, maxScrollExtent, true); + } + + /// 当默认位置不为0时,主部件已下拉距离超过默认位置,但超过的距离不大于该值时, + /// 若手指离开屏幕,主部件头部会回弹至默认位置 + double _scrollRedundancy = 80; + + /// 当前页面Header最大程度展开状态 + PageExpandState pageExpand = PageExpandState.NotExpand; + + /// 当手指离开屏幕 + void onPointerUp(PointerUpEvent event) { + final double _pagePixels = _pageScrollPosition.pixels; + if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset) { + if (pageExpand == PageExpandState.NotExpand && + _pageInitialOffset - _pagePixels > _scrollRedundancy) { + _pageScrollPosition + .animateTo(0.0, + duration: const Duration(milliseconds: 400), curve: Curves.ease) + .then((value) => pageExpand = PageExpandState.Expanded); + } else { + pageExpand = PageExpandState.Expanding; + _pageScrollPosition + .animateTo(_pageInitialOffset, + duration: const Duration(milliseconds: 400), curve: Curves.ease) + .then((value) => pageExpand = PageExpandState.NotExpand); + } + } + } + + /// 更新用户滑动方向 + void updateUserScrollDirection( + ShopScrollPosition position, ScrollDirection value) { + assert(position != null && value != null); + position.didUpdateScrollDirection(value); + } + + /// 以特定的速度开始一个物理驱动的模拟,该模拟确定[pixels]位置。 + /// 此方法遵从[ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出 + /// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。 + /// 速度应以每秒逻辑像素为单位。 + void goBallistic(double velocity) => + _pageScrollPosition.goBallistic(velocity); +} diff --git a/lib/utils/shop_scroll_position.dart b/lib/utils/shop_scroll_position.dart new file mode 100644 index 0000000..448e314 --- /dev/null +++ b/lib/utils/shop_scroll_position.dart @@ -0,0 +1,292 @@ +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/physics.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +import 'shop_scroll_coordinator.dart'; + +/// 滑动位置信息 +class ShopScrollPosition extends ScrollPosition + implements ScrollActivityDelegate { + final ShopScrollCoordinator coordinator; // 协调器 + ScrollDragController _currentDrag; + double _heldPreviousVelocity = 0.0; + + ShopScrollPosition( + {@required ScrollPhysics physics, + @required ScrollContext context, + double initialPixels = 0.0, + bool keepScrollOffset = true, + ScrollPosition oldPosition, + String debugLabel, + @required this.coordinator}) + : super( + physics: physics, + context: context, + keepScrollOffset: keepScrollOffset, + oldPosition: oldPosition, + debugLabel: debugLabel, + ) { + // 如果oldPosition不为null,则父级将首先调用Absorb(),它可以设置_pixels和_activity. + if (!hasPixels && initialPixels != null) correctPixels(initialPixels); + if (activity == null) goIdle(); + assert(activity != null); + } + + @override + AxisDirection get axisDirection => context.axisDirection; + + @override + double setPixels(double newPixels) { + assert(activity.isScrolling); + return super.setPixels(newPixels); + } + + @override + void absorb(ScrollPosition other) { + super.absorb(other); + if (other is! ScrollPositionWithSingleContext) { + goIdle(); + return; + } + activity.updateDelegate(this); + final ShopScrollPosition typedOther = other as ShopScrollPosition; + _userScrollDirection = typedOther._userScrollDirection; + assert(_currentDrag == null); + if (typedOther._currentDrag != null) { + _currentDrag = typedOther._currentDrag; + _currentDrag.updateDelegate(this); + typedOther._currentDrag = null; + } + } + + @override + void applyNewDimensions() { + super.applyNewDimensions(); + context.setCanDrag(physics.shouldAcceptUserOffset(this)); + } + + /// 返回未使用的增量。 + /// 正增量表示下降(在上方显示内容),负增量向上(在下方显示内容)。 + double applyClampedDragUpdate(double delta) { + assert(delta != 0.0); + // 如果我们要朝向maxScrollExtent(负滚动偏移),那么我们在minScrollExtent方向上 + // 可以达到的最大距离是负无穷大。 例如,如果我们已经过度滚动,则滚动以减少过度滚动不应禁止过度滚动。 + // 如果我们要朝minScrollExtent(正滚动偏移量)方向移动,那么我们在minScrollExtent方向 + // 上可以达到的最大距离是我们现在所处的位置(如果我们已经过度滚动) + // (在这种情况下,像素小于minScrollExtent),或者如果minScrollExtent可以 我们不是。 + // 换句话说,我们不能通过applyClampedDragUpdate进入过滚动状态。 + // 尽管如此,可能通过多种方式进入了过度滚动的情况。 一种是物理是否允许通过 + // applyFullDragUpdate(请参见下文)。也可能会发生过度滚动的情况,例如 如果使用滚动控制器人工设置了滚动位置。 + final double min = + delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels); + // max的逻辑是等效的,但反向。 + final double max = + delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels); + final double oldPixels = pixels; + final double newPixels = (pixels - delta).clamp(min, max) as double; + final double clampedDelta = newPixels - pixels; + if (clampedDelta == 0.0) return delta; + final double overScroll = physics.applyBoundaryConditions(this, newPixels); + final double actualNewPixels = newPixels - overScroll; + final double offset = actualNewPixels - oldPixels; + if (offset != 0.0) { + forcePixels(actualNewPixels); + didUpdateScrollPositionBy(offset); + } + return delta + offset; + } + + // Returns the overScroll. 返回过度滚动。 + double applyFullDragUpdate(double delta) { + assert(delta != 0.0); + final double oldPixels = pixels; + // Apply friction: 施加摩擦: + final double newPixels = + pixels - physics.applyPhysicsToUserOffset(this, delta); + if (oldPixels == newPixels) return 0.0; // 增量一定很小,我们在添加浮点数时将其删除了 + // Check for overScroll: 检查过度滚动: + final double overScroll = physics.applyBoundaryConditions(this, newPixels); + final double actualNewPixels = newPixels - overScroll; + if (actualNewPixels != oldPixels) { + forcePixels(actualNewPixels); + didUpdateScrollPositionBy(actualNewPixels - oldPixels); + } + return overScroll; + } + + /// 当手指滑动时,该方法会获取到滑动距离 + /// [delta]滑动距离,正增量表示下滑,负增量向上滑 + /// 我们需要把子部件的 滑动数据 交给协调器处理,主部件无干扰 + @override + void applyUserOffset(double delta) { + ScrollDirection userScrollDirection = + delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse; + if (debugLabel != coordinator.pageLabel) + return coordinator.applyUserOffset(delta, userScrollDirection, this); + updateUserScrollDirection(userScrollDirection); + setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); + } + + @override + void beginActivity(ScrollActivity newActivity) { + _heldPreviousVelocity = 0.0; + if (newActivity == null) return; + assert(newActivity.delegate == this); + super.beginActivity(newActivity); + _currentDrag?.dispose(); + _currentDrag = null; + if (!activity.isScrolling) updateUserScrollDirection(ScrollDirection.idle); + } + + /// 将[用户滚动方向]设置为给定值。 + /// 如果更改了该值,则将分派[User ScrollNotification]。 + @protected + @visibleForTesting + void updateUserScrollDirection(ScrollDirection value) { + assert(value != null); + if (userScrollDirection == value) return; + _userScrollDirection = value; + didUpdateScrollDirection(value); + } + + @override + ScrollHoldController hold(VoidCallback holdCancelCallback) { + final double previousVelocity = activity.velocity; + final HoldScrollActivity holdActivity = + HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback); + beginActivity(holdActivity); + _heldPreviousVelocity = previousVelocity; + return holdActivity; + } + + @override + Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { + final ScrollDragController drag = ScrollDragController( + delegate: this, + details: details, + onDragCanceled: dragCancelCallback, + carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity), + motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold, + ); + beginActivity(DragScrollActivity(this, drag)); + assert(_currentDrag == null); + _currentDrag = drag; + return drag; + } + + @override + void goIdle() { + beginActivity(IdleScrollActivity(this)); + } + + /// 以特定的速度开始一个物理驱动的模拟,该模拟确定[pixels]位置。 + /// 此方法遵从[ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出 + /// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。 + /// 速度应以每秒逻辑像素为单位。 + @override + void goBallistic(double velocity, [bool fromCoordinator = false]) { + if (debugLabel != coordinator.pageLabel) { + if (velocity > 0.0) coordinator.goBallistic(velocity); + } else { + if (fromCoordinator && velocity <= 0.0) return; + if (coordinator.pageExpand == PageExpandState.Expanding) return; + } + assert(pixels != null); + final Simulation simulation = + physics.createBallisticSimulation(this, velocity); + if (simulation != null) { + beginActivity(BallisticScrollActivity(this, simulation, context.vsync)); + } else { + goIdle(); + } + } + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent, + [bool fromCoordinator = false]) { + if (debugLabel == coordinator.pageLabel && !fromCoordinator) + return coordinator.applyContentDimensions( + minScrollExtent, maxScrollExtent, this); + return super.applyContentDimensions(minScrollExtent, maxScrollExtent); + } + + @override + ScrollDirection get userScrollDirection => _userScrollDirection; + ScrollDirection _userScrollDirection = ScrollDirection.idle; + + @override + Future animateTo( + double to, { + @required Duration duration, + @required Curve curve, + }) { + if (nearEqual(to, pixels, physics.tolerance.distance)) { + // 跳过动画,直接移到我们已经靠近的位置。 + jumpTo(to); + return Future.value(); + } + + final DrivenScrollActivity activity = DrivenScrollActivity( + this, + from: pixels, + to: to, + duration: duration, + curve: curve, + vsync: context.vsync, + ); + beginActivity(activity); + return activity.done; + } + + @override + void jumpTo(double value) { + goIdle(); + if (pixels != value) { + final double oldPixels = pixels; + forcePixels(value); + notifyListeners(); + didStartScroll(); + didUpdateScrollPositionBy(pixels - oldPixels); + didEndScroll(); + } + goBallistic(0.0); + } + + @Deprecated('This will lead to bugs.') + @override + void jumpToWithoutSettling(double value) { + goIdle(); + if (pixels != value) { + final double oldPixels = pixels; + forcePixels(value); + notifyListeners(); + didStartScroll(); + didUpdateScrollPositionBy(pixels - oldPixels); + didEndScroll(); + } + } + + @override + void dispose() { + _currentDrag?.dispose(); + _currentDrag = null; + super.dispose(); + } + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('${context.runtimeType}'); + description.add('$physics'); + description.add('$activity'); + description.add('$userScrollDirection'); + } + + @override + void pointerScroll(double delta) { + // TODO: implement pointerScroll + } +} \ No newline at end of file diff --git a/lib/utils/util_io.dart b/lib/utils/util_io.dart index da45a95..18847c3 100644 --- a/lib/utils/util_io.dart +++ b/lib/utils/util_io.dart @@ -1,12 +1,199 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dio/dio.dart'; +// import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../routes.dart'; -import 'utils.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hive/hive.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:stripe_payment/stripe_payment.dart'; + +import '../constants.dart'; +import '../events/eventbus.dart'; +import '../events/events.dart'; +import '../generated/l10n.dart'; +import '../models/comment.dart'; +import '../models/order.dart'; +import '../models/payment_platform.dart'; +import '../models/stripe_payment_method.dart'; +import '../models/user.dart'; +import '../routes.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../widgets/general/stripe_pay.dart'; +import 'http_util.dart'; +import 'utils.dart'; + +typedef void OnGotFile(int imageId, String filePath); class Util { + static final Util _util = Util._internal(); + factory Util() { + return _util; + } + Util._internal(); + + // final FirebaseMessaging firebaseMessaging = FirebaseMessaging(); + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin(); + static bool notificationTapped = false; + + init() { + // initLocalNotification(); + // initFirebaseMessaging(); + + // ePmqRwRUTmKuFNfbHA-6zq:APA91bEFEQx1YgJqfx0V0VYo86uRDGjG6SP1SPCAZW4OvQ7gMOeKWDQ47bfuEM00YavtKq7ZGpsWfFL7B3ypm00ZA6crjJJLK_-j_H8bS_dnQbLFwyRcW67IXCs5sRkro71bRZv7L1WM + + // eventBus.on().listen((event) { + // if (firebaseMessaging != null) { + // firebaseMessaging.subscribeToTopic(event.topic); + // } + // }); + // eventBus.on().listen((event) { + // if (firebaseMessaging != null) { + // firebaseMessaging.unsubscribeFromTopic(event.topic); + // } + // }); + } + + initLocalNotification() async { + var initializationSettingsAndroid = new AndroidInitializationSettings('ic_launcher'); + var initializationSettingsIOS = new IOSInitializationSettings( + onDidReceiveLocalNotification: onDidReceiveLocalNotification); + var initializationSettings = new InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + ); + await flutterLocalNotificationsPlugin.initialize(initializationSettings, + onSelectNotification: onSelectNotification); + } + + Future onSelectNotification(String payload, {bool replace=false}) async { + print('payload: $payload'); + if (payload != null && payload.isNotEmpty) { + Routes.router.navigateTo(store.state.context, payload, replace: replace); + } + } + +// initFirebaseMessaging() { +// if (Platform.isIOS) { +// iosPermission(); +// } +// firebaseMessaging.getToken().then((token) { +// print('FCM token: $token'); +// store.dispatch(UpdateFcmToken(token)); +// }); +// firebaseMessaging.configure( +// onMessage: (Map message) { +// print('FCM onMessage: $message'); +// // eventBus.fire(OnFcmReceived(message)); +// showNotificationWithDefaultSound(message); +// return; +// }, +// onBackgroundMessage: Platform.isIOS ? null : backgroundMessageHandler, +// onResume: (Map message) { +// print('FCM onResume: $message'); +// if (Platform.isAndroid) { +// if (message != null && message['data'] != null && message['data']['route'] != null) { +// notificationTapped = true; +// onSelectNotification(message['data']['route']); +// } +// } else { +// if (message != null && message['route'] != null) { +// notificationTapped = true; +// onSelectNotification(message['route']); +// } +// } +// return; +// }, +// onLaunch: (Map message) { +// print('FCM onLaunch: $message'); +// if (Platform.isAndroid) { +// if (message != null && message['data'] != null && message['data']['route'] != null) { +// notificationTapped = true; +// onSelectNotification(message['data']['route'], replace: true); +// } +// } else { +// if (message != null && message['route'] != null) { +// notificationTapped = true; +// onSelectNotification(message['route'], replace: true); +// } +// } +// return; +// }, +// ); +// } + + Future onDidReceiveLocalNotification(int id, String title, String body, String payload) async { + showDialog( + context: store.state.context, + builder: (BuildContext context) => CupertinoAlertDialog( + title: Text(title), + content: Text(body), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + child: Text(S.of(context).ok), + onPressed: () async { + Navigator.of(context, rootNavigator: true).pop(); + if (payload != null && payload.isNotEmpty) { + Routes.router.navigateTo(context, payload); + } + }, + ) + ], + ), + ); + } + + Future showNotificationWithDefaultSound(Map message) async { + var androidPlatformChannelSpecifics = new AndroidNotificationDetails( + 'channelId', + 'channelName', + 'channelDescription', + importance: Importance.max, + priority: Priority.high, + ); + var iOSPlatformChannelSpecifics = new IOSNotificationDetails(presentSound: true, badgeNumber: 1); + var platformChannelSpecifics = new NotificationDetails( + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics, + ); + await flutterLocalNotificationsPlugin.show( + 0, + Platform.isAndroid ? message['notification']['title'] : message['title'], + Platform.isAndroid ? message['notification']['body'] : message['body'], + platformChannelSpecifics, + payload: Platform.isAndroid ? message['data']['route'] : message['route'], + ); + } + + // iosPermission() { + // firebaseMessaging.requestNotificationPermissions( + // const IosNotificationSettings( + // sound: true, + // badge: true, + // alert: true, + // ) + // ); + // firebaseMessaging.onIosSettingsRegistered.listen((IosNotificationSettings settings) { + // print('IOS settings registered: $settings'); + // }); + // } + + static Future backgroundMessageHandler(Map message) { + print('FCM background message handler: $message'); + return Future.value(); + } + static Future getBox() async { final dir = await getApplicationDocumentsDirectory(); Hive.init(dir.path); @@ -16,31 +203,487 @@ class Util { static Widget showImage(String imageUrl, {double width, double height, BoxFit fit, Widget Function(BuildContext, String, dynamic) errorWidget}) { - if (imageUrl == null || imageUrl.isEmpty) { - return Container( + if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) { + return CachedNetworkImage( + imageUrl: imageUrl, + width: width, + height: width, + fit: fit, + placeholder: (context, url) => Utils.imageLoadingIndicator(width: width, height: height), + errorWidget: errorWidget != null ? errorWidget : (context, url, error) { + return Image.asset( + 'assets/images/not_found.png', + width: width, + height: height, + fit: fit, + ); + }, + ); + } else if (imageUrl != null && imageUrl.isNotEmpty) { + return Image.file( + File(imageUrl), width: width, height: height, - child: Text(''), + fit: fit, + errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) { + return Image.asset( + 'assets/images/not_found.png', + width: width, + height: height, + fit: fit, + ); + }, + ); + } else { + return Image.asset( + 'assets/images/not_found.png', + width: width, + height: height, + fit: fit, ); } - return CachedNetworkImage( - imageUrl: imageUrl, - width: width, - height: width, - fit: fit, - placeholder: (context, url) => Utils.imageLoadingIndicator(), - errorWidget: errorWidget != null ? errorWidget : (context, url, error) { - return Image.asset( - 'assets/images/not_found.png', - width: width, - height: height, - fit: fit, - ); - }, - ); } static void openWebUrl(BuildContext context, String link) { Routes.router.navigateTo(context, '/webview/$link'); } + + AlertDialog getPicture(BuildContext context, User user, {int commentId = -1, int orderId = 0}) { + return AlertDialog( + title: Text( + S.of(context).get_picture, + ), + content: Container( + height: 130.0, + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 12.0), + child: Text( + S.of(context).get_picture_from, + style: TextStyle( + fontSize: 15.0, + color: Colors.black26, + ), + ), + ), + Container( + child: TextButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.photo_library, + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).gallery, + ), + ), + ], + ), + onPressed: () { + getPictureFromGallery(context, user, commentId: commentId, orderId: orderId); + }, + ), + ), + Container( + child: TextButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.photo_camera, + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).camera, + ), + ), + ], + ), + onPressed: () { + getPictureFromCamera(context, user, commentId: commentId, orderId: orderId); + }, + ), + ) + ], + ), + ), + ); + } + + void getPictureFromGallery(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async { + final picker = ImagePicker(); + var image = await picker.pickImage(source: ImageSource.gallery); + Navigator.of(context).pop(); + uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId); + } + + void getPictureFromCamera(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async { + final picker = ImagePicker(); + var image = await picker.pickImage(source: ImageSource.camera); + Navigator.of(context).pop(); + uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId); + } + + void uploadPicture(BuildContext context, File image, User user, {int commentId = -1, int orderId = 0}) async { + if (await image.exists()) { + print('image: ${image.path}'); + String imageName = basename(image.path); + MultipartFile imageFile = await MultipartFile.fromFile(image.path, filename: imageName); + Map formMap = { + 'id': user.id, + 'file': imageFile, + }; + if (commentId >= 0) { + formMap = { + 'contact_id': user.id, + 'comment_id': commentId, + 'order_id': orderId, + 'file': imageFile, + }; + HttpUtil.httpPost( + 'v1/upload-comment-image', + (response) { + eventBus.fire(new OnCommentUpdatedEvent(Comment.fromJson(response.data))); + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnUploadCommentImageProgressEvent(false, 0.0)); + } else { + eventBus.fire(OnUploadCommentImageProgressEvent(true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } else { + HttpUtil.httpPost( + 'v1/upload-avatar', + (response) { + store.dispatch(new UpdateCurrentUser(User.fromJson(response.data))); + eventBus.fire(new OnCurrentUserUpdated()); + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnProgressEvent(false, 0.0)); + } else { + eventBus.fire(OnProgressEvent(true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + } + + AlertDialog getPicture2(BuildContext context, int imageId, OnGotFile onGotFile) { + return AlertDialog( + title: Text( + S.of(context).get_picture, + ), + content: Container( + height: 130.0, + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 12.0), + child: Text( + S.of(context).get_picture_from, + style: TextStyle( + fontSize: 15.0, + color: Colors.black26, + ), + ), + ), + Container( + child: TextButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.photo_library, + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).gallery, + ), + ), + ], + ), + onPressed: () { + getPictureFromGallery2(context, imageId, onGotFile); + }, + ), + ), + Container( + child: TextButton( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.photo_camera, + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).camera, + ), + ), + ], + ), + onPressed: () { + getPictureFromCamera2(context, imageId, onGotFile); + }, + ), + ) + ], + ), + ), + ); + } + + void getPictureFromGallery2(BuildContext context, int imageId, OnGotFile onGotFile) async { + ImagePicker picker = ImagePicker(); + var image = await picker.pickImage(source: ImageSource.gallery); + Navigator.of(context).pop(); + onGotFile(imageId, image.path); + } + + void getPictureFromCamera2(BuildContext context, int imageId, OnGotFile onGotFile) async { + ImagePicker picker = ImagePicker(); + var image = await picker.pickImage(source: ImageSource.camera); + Navigator.of(context).pop(); + onGotFile(imageId, image.path); + } + + Future createTicket(BuildContext context, String msg, List> images, + OnSuccess onSuccess, OnError onError, {int id}) { + var formData = FormData(); + formData.fields.add(MapEntry("msg", msg)); + formData.fields.add(MapEntry('id', id == null ? '0' : id.toString())); + for (int i = 0; i < images.length; i++) { + String imagePath = images[i]['path']; + if (imagePath.isNotEmpty) { + String imageName = basename(imagePath); + formData.files.add( + MapEntry( + "files", + MultipartFile.fromFileSync(imagePath, filename: imageName) + ) + ); + } + } + HttpUtil.httpPost('create-new-ticket-app/', (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + isFormData: true, + // body: null, + formData: formData, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnSubmitProgressEvent(false, 100.0)); + } else { + eventBus.fire(OnSubmitProgressEvent(true, sent / total * 100.0)); + } + }, + businessId: Constants.BUSINESS_ID, + ).catchError((error) { + if (onError != null) { + onError(error); + } + }); + } + + Widget getNativePay(BuildContext context, Order order, PaymentPlatform paymentPlatform) { + return GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(right: 6.0), + child: Text( + Platform.isAndroid ? S.of(context).pay_with : S.of(context).pay_with, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 10.0), + child: Platform.isAndroid ? Image.asset( + 'assets/images/google_pay.png', + height: 22.0, + fit: BoxFit.fitHeight, + ) : Image.asset( + 'assets/images/apple_pay.png', + height: 22.0, + fit: BoxFit.fitHeight, + ), + ), + ], + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black26, + ), + ), + ), + ), + onTap: () { + print(paymentPlatform); + PaymentPlatform newPaymentPlatform = PaymentPlatform.fromJson(json.decode(json.encode(paymentPlatform))); + newPaymentPlatform.code = Constants.PAYMENT_METHOD_NATIVE_PAY; + goPayment(context, order, newPaymentPlatform); + }, + ); + } + + static void goPayment(BuildContext context, Order order, + PaymentPlatform paymentPlatform, { + bool googlePay=false, + bool applePay=false, + StripePaymentMethod stripePaymentMethod, + }) { + switch(paymentPlatform.code) { + case Constants.PAYMENT_METHOD_CODE_SQUARE: + + break; + case Constants.PAYMENT_METHOD_CODE_CHASE: + + break; + case Constants.PAYMENT_METHOD_CODE_PAYPAL: + + break; + case Constants.PAYMENT_METHOD_CODE_OTT_ALIPAY: + + break; + case Constants.PAYMENT_METHOD_CODE_OTT_WECHATPAY: + + break; + case Constants.PAYMENT_METHOD_CODE_POD: + + break; + case Constants.PAYMENT_METHOD_NATIVE_PAY: + StripePayment.setOptions( + StripeOptions(publishableKey: paymentPlatform.publishableKey, + merchantId: paymentPlatform.merchantId, + androidPayMode: paymentPlatform.publishableKey.contains('_test_') + ? 'test' + : 'production' + ) + ); + StripePayment.paymentRequestWithNativePay( + androidPayOptions: AndroidPayPaymentRequest( + totalPrice: order.totalPrice.toStringAsFixed(2), + currencyCode: order.businessInfo.currency, + ), + applePayOptions: ApplePayPaymentOptions( + currencyCode: order.businessInfo.currency, + countryCode: order.businessInfo.address.country, + items: [ + ApplePayItem( + label: order.businessInfo.name, + amount: order.totalPrice.toStringAsFixed(2), + ), + ], + ), + ).then((token) { + print('return native token: ${token.tokenId}'); + processNativePay(context, token, order); + }).catchError((error) { + print('native pay error: $error'); + }); + break; + case Constants.PAYMENT_METHOD_CODE_STRIPE: + Navigator.pushReplacement(context, MaterialPageRoute( + builder: (BuildContext context) { + return StripePay(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,); + } + )); + break; + } + } + + static void processNativePay(BuildContext context, Token token, Order order) async { + PaymentMethod paymentMethod = await StripePayment.createPaymentMethod( + PaymentMethodRequest( + card: CreditCard( + token: token.tokenId, + ), + ), + ); + if (paymentMethod != null) { + Utils.stripePaymentIntent(order, null, paymentMethod.id, paymentMethod.type, (response) { + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + StripePayment.confirmPaymentIntent( + PaymentIntent( + clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], + paymentMethodId: response.data['payment_method'], + ), + ).then((paymentIntentResult) { + if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(order, + paymentMethod.id, + paymentIntentResult.paymentIntentId, + (response) { + StripePayment.completeNativePayRequest().then((_) { + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${order.id}', replace: true); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + }); + }); + }, + (error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + }); + } + ); + } else { + Utils.showMessageDialog(context, Exception('Unknown error')); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + }); + }); + } + }, (error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + }); + }); + } else { + + } + } + + static Future getBytesFromAsset(String path, int width) async { + ByteData data = await rootBundle.load(path); + ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width); + ui.FrameInfo fi = await codec.getNextFrame(); + return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List(); + } } \ No newline at end of file diff --git a/lib/utils/util_web.dart b/lib/utils/util_web.dart index a44c5d4..aedf7ca 100644 --- a/lib/utils/util_web.dart +++ b/lib/utils/util_web.dart @@ -1,11 +1,40 @@ +import 'dart:html'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'utils.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../constants.dart'; +import '../events/eventbus.dart'; +import '../events/events.dart'; +import '../generated/l10n.dart'; +import '../models/comment.dart' as mComment; +import '../models/order.dart'; +import '../models/payment_platform.dart'; +import '../models/stripe_payment_method.dart'; +import '../models/user.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; +import '../pages/stripe_pay_web.dart'; +import 'http_util.dart'; +import 'utils.dart'; + +typedef void OnGotFile(int imageId, String filePath); class Util { + static final Util _instance = Util._internal(); + factory Util() { + return _instance; + } + Util._internal(); + init() { + + } static Future getBox() async { Hive.initFlutter(); @@ -28,6 +57,7 @@ class Util { ); }, errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) { + print(stackTrace); if (errorWidget != null) { return errorWidget(context, null, null); } @@ -49,4 +79,250 @@ class Util { print('Could not launch $url'); } } + + AlertDialog getPicture(BuildContext context, User user, {int commentId = -1, int orderId = 0}) { + return AlertDialog( + title: Text(S.of(context).please_select), + content: Text(S.of(context).please_select_an_image), + actions: [ + TextButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text(S.of(context).select, style: TextStyle(color: Colors.white),), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + ), + onPressed: () { + Navigator.of(context).pop(); + startFilePicker(context, user, commentId: commentId, orderId: orderId); + }, + ), + ], + ); + } + + void startFilePicker(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async { + InputElement uploadInput = FileUploadInputElement(); + uploadInput.click(); + uploadInput.onChange.listen((e) { + final files = uploadInput.files; + if (files.length >= 1) { + final File file = files[0]; + final FileReader reader = new FileReader(); + + reader.onLoadEnd.listen((e) { + uploadPicture(context, reader.result, file.name, user, commentId: commentId, orderId: orderId); + }); + reader.onError.listen((e) { + Fluttertoast.showToast( + msg: S.of(context).error_read_file, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + }); + reader.readAsArrayBuffer(file); + } + }); + } + + void uploadPicture(BuildContext context, Uint8List fileBytes, String fileName, User user, {int commentId = -1, int orderId = 0}) { + MultipartFile imageFile = MultipartFile.fromBytes(fileBytes, filename: fileName); + Map formMap = { + 'id': user.id, + 'file': imageFile, + }; + if (commentId >= 0) { + formMap = { + 'contact_id': user.id, + 'comment_id': commentId, + 'order_id': orderId, + 'file': imageFile, + }; + HttpUtil.httpPost( + 'v1/upload-comment-image', + (response) { + eventBus.fire(new OnCommentUpdatedEvent(mComment.Comment.fromJson(response.data))); + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnUploadCommentImageProgressEvent(false, 0.0)); + } else { + eventBus.fire(OnUploadCommentImageProgressEvent(true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } else { + HttpUtil.httpPost( + 'v1/upload-avatar', + (response) { + store.dispatch(new UpdateCurrentUser(User.fromJson(response.data))); + eventBus.fire(new OnCurrentUserUpdated()); + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnProgressEvent(false, 0.0)); + } else { + eventBus.fire(OnProgressEvent(true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + AlertDialog getPicture2(BuildContext context, int imageId, OnGotFile onGotFile) { + return AlertDialog( + title: Text(S.of(context).please_select), + content: Text(S.of(context).please_select_an_image), + actions: [ + TextButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text(S.of(context).select, style: TextStyle(color: Colors.white),), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + ), + onPressed: () { + Navigator.of(context).pop(); + startFilePicker2(context, imageId, onGotFile); + }, + ), + ], + ); + } + + void startFilePicker2(BuildContext context, int imageId, OnGotFile onGotFile) async { + final _image = await WebImagePicker().pickImage(); + onGotFile(imageId, _image.dataScheme); + } + + void createTicket(BuildContext context, String msg, List> images, + OnSuccess onSuccess, OnError onError, {int id}) { + var formData = FormData(); + formData.fields.add(MapEntry("msg", msg)); + formData.fields.add(MapEntry('id', id == null ? '0' : id.toString())); + for (int i = 0; i < images.length; i++) { + String imagePath = images[i]['path']; + if (imagePath.isNotEmpty) { + // (formMap['images'] as List).add(imagePath); + formData.fields.add(MapEntry('images', imagePath)); + } + } + HttpUtil.httpPost('create-new-ticket-web/', (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + isFormData: true, + // body: formMap, + formData: formData, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnSubmitProgressEvent(false, 100.0)); + } else { + eventBus.fire(OnSubmitProgressEvent(true, sent / total * 100.0)); + } + }, + businessId: Constants.BUSINESS_ID, + ).catchError((error) { + if (onError != null) { + onError(error); + } + }); + } + + static void goPayment(BuildContext context, Order order, + PaymentPlatform paymentPlatform, { + bool googlePay=false, + bool applePay=false, + StripePaymentMethod stripePaymentMethod, + }) { + switch(paymentPlatform.code) { + case Constants.PAYMENT_METHOD_CODE_SQUARE: + + break; + case Constants.PAYMENT_METHOD_CODE_CHASE: + + break; + case Constants.PAYMENT_METHOD_CODE_PAYPAL: + + break; + case Constants.PAYMENT_METHOD_CODE_OTT_ALIPAY: + + break; + case Constants.PAYMENT_METHOD_CODE_OTT_WECHATPAY: + + break; + case Constants.PAYMENT_METHOD_CODE_POD: + + break; + case Constants.PAYMENT_METHOD_CODE_STRIPE: + Navigator.pushReplacement(context, MaterialPageRoute( + builder: (BuildContext context) { + return StripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,); + } + )); + break; + } + } + + Widget getNativePay(BuildContext context, Order order, PaymentPlatform paymentPlatform) { + return SizedBox.shrink(); + } + + static Future getBytesFromAsset(String path, int width) async { + return Uint8List(0); + } +} + +class ImageInfo { + String name; + String data; + String dataScheme; + String path; +} + +class WebImagePicker { + Future pickImage() async { + print('pickImage'); + final ImageInfo data = ImageInfo(); + final FileUploadInputElement input = FileUploadInputElement(); + document.body.children.add(input); + input..accept = 'image/*'; + input.click(); + await input.onChange.first; + if (input.files.isEmpty) return null; + final reader = FileReader(); + reader.readAsDataUrl(input.files[0]); + await reader.onLoad.first; + final encoded = reader.result as String; + // remove data:image/*;base64 preambule + final stripped = + encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), ''); + //final imageBase64 = base64.decode(stripped); + final imageName = input.files?.first?.name; + final imagePath = input.files?.first?.relativePath; + data.name = imageName; + data.data = stripped; + data.dataScheme = encoded; + data.path = imagePath; + return data; + } } \ No newline at end of file diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index f7a2f1c..e1acd34 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -1,4 +1,3 @@ - import 'dart:convert'; import 'package:dio/dio.dart'; @@ -6,31 +5,43 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/product.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hive/hive.dart'; import 'package:intl/intl.dart'; -import 'package:url_launcher/url_launcher.dart'; +import 'package:path/path.dart' as path; import 'package:universal_io/io.dart'; -import '../constants.dart'; -import 'util_web.dart' if (dart.library.io) 'util_io.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../constants.dart'; +import '../events/eventbus.dart'; +import '../events/events.dart'; +import '../generated/l10n.dart'; +import '../models/business.dart'; +import '../models/cart_info.dart'; +import '../models/cart_line_item.dart'; +import '../models/order.dart'; +import '../models/user.dart'; import '../routes.dart'; +import '../store/actions.dart'; +import '../store/store.dart'; import 'http_util.dart'; +import 'util_web.dart' if (dart.library.io) 'util_io.dart'; typedef void OnSuccess(Response response); typedef void OnError(dynamic error); typedef void OnComplete(dynamic data); typedef void OnOk(); +typedef void OnLoadImageError(); class Utils { - static bool equalsIgnoreCase(String a, String b) => (a == null && b == null) || - (a != null && b != null && a.toLowerCase() == b.toLowerCase()); + (a != null && b != null && a.toLowerCase() == b.toLowerCase()); - static int selectionsContains(Map selections, String key, String name) { + static int selectionsContains( + Map selections, String key, String name) { if (selections.containsKey(key.toUpperCase())) { for (var i = 0; i < (selections[key.toUpperCase()] as List).length; i++) { Map item = (selections[key.toUpperCase()] as List)[i]; @@ -42,18 +53,22 @@ class Utils { return -1; } - static List getSelectedAttributeValue(Map selections, String key) { + static List getSelectedAttributeValue( + Map selections, String key) { List valueArr = []; if (selections.containsKey(key.toUpperCase())) { for (var i = 0; i < (selections[key.toUpperCase()] as List).length; i++) { - valueArr.add((selections[key.toUpperCase()][i]['name'] as String).toLowerCase()); + valueArr.add( + (selections[key.toUpperCase()][i]['name'] as String).toLowerCase()); } } return valueArr; } - static bool selectionsNotEmptyAt(Map selections, String key) { - if (selections.containsKey(key.toUpperCase()) && (selections[key.toUpperCase()] as List).length > 0) { + static bool selectionsNotEmptyAt( + Map selections, String key) { + if (selections.containsKey(key.toUpperCase()) && + (selections[key.toUpperCase()] as List).length > 0) { return true; } return false; @@ -96,13 +111,18 @@ class Utils { return Util.getBox(); } - static Widget imageLoadingIndicator() { + static Widget imageLoadingIndicator( + {double width = 30.0, double height = 30.0}) { return SizedBox( - width: 30, - height: 30, - child: Container( - child: CupertinoActivityIndicator(), - color: Colors.transparent, + width: width, + height: height, + child: Center( + child: Container( + width: 30.0, + height: 30.0, + child: CupertinoActivityIndicator(), + color: Colors.transparent, + ), ), ); } @@ -135,20 +155,23 @@ class Utils { } static void createOrUpdateStripePaymentMethod( - String paymentMethodId, - String cardBrand, - String paymentMethodType, - String cardCountry, - int cardExpMonth, - int cardExpYear, - String cardFunding, - String cardLast4, - ) { - HttpUtil.httpPost('v1/create-update-stripe-payment-method', (response) { - if (response.statusCode == 200) { - print('create or update customer stripe payment method success. ${response.data}'); - } - }, + String paymentMethodId, + String cardBrand, + String paymentMethodType, + String cardCountry, + int cardExpMonth, + int cardExpYear, + String cardFunding, + String cardLast4, + ) { + HttpUtil.httpPost( + 'v1/create-update-stripe-payment-method', + (response) { + if (response.statusCode == 200) { + print( + 'create or update customer stripe payment method success. ${response.data}'); + } + }, body: { 'payment_method_id': paymentMethodId, 'card_brand': cardBrand, @@ -197,11 +220,9 @@ class Utils { toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, backgroundColor: Colors.red, - textColor: Colors.white - ); + textColor: Colors.white); return false; - } - ), + }), ); }, ); @@ -239,11 +260,9 @@ class Utils { toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, backgroundColor: Colors.red, - textColor: Colors.white - ); + textColor: Colors.white); return false; - } - ), + }), ); }, ); @@ -256,13 +275,14 @@ class Utils { return body.substring(0, 29); } - static void getMiniLink(String realLink, OnSuccess onSuccess, OnError onError) { - HttpUtil.httpPost('get-minilink/', (response) { - onSuccess(response); - }, - body: { - 'link': realLink + static void getMiniLink( + String realLink, OnSuccess onSuccess, OnError onError) { + HttpUtil.httpPost( + 'get-minilink/', + (response) { + onSuccess(response); }, + body: {'link': realLink}, isFormData: true, ).catchError((error) { onError(error); @@ -309,11 +329,648 @@ class Utils { } return Constants.FONT_TOPTONS; } + + static void getCurrentUser() { + getBox().then((box) { + int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0); + String accessToken = + box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: ''); + if (userId == 0 || accessToken.isEmpty) { + eventBus.fire(new OnGetCurrentUserFailed(Error())); + } else { + HttpUtil.httpGet( + 'v1/users/$userId', + queryParameters: {}, + businessId: Constants.BUSINESS_ID, + ).then((value) { + User user = User.fromJson(value); + store.dispatch(UpdateCurrentUser(user)); + eventBus.fire(OnCurrentUserUpdated()); + }).catchError((err) { + eventBus.fire(new OnGetCurrentUserFailed(err)); + }); + } + }).catchError((err) { + eventBus.fire(new OnGetCurrentUserFailed(err)); + }); + } + + static void showMessageDialog(BuildContext context, dynamic error, + {String title, List actions, OnOk onOk}) { + String message = ''; + print('error $error'); + if (error is DioError && error.response != null) { + if (error.response.data['message'] != null) { + message = error.response.data['message']; + } else if (error.response.data['error'] != null) { + if (error.response.data['error'] is String) { + message = error.response.data['error']; + } else if (error.response.data['error']['errmsg'] != null) { + message = error.response.data['error']['errmsg']; + } else if (error.response.data['error']['message'] != null) { + message = error.response.data['error']['message']; + } else { + message = error.response.data.toString(); + } + } + } else if (error.message != null) { + message = error.message; + } else { + message = '$error'; + } + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title != null ? title : S.of(context).error), + content: Text(message), + actions: actions == null + ? [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: onOk == null + ? () { + Navigator.of(context).pop(); + } + : onOk, + ), + ] + : actions, + ); + }); + } + + static Future> getLastVisit() async { + Box box = await getBox(); + List lastVisit = + json.decode(box.get(Constants.KEY_LAST_VISIT, defaultValue: '[]')); + List lv = lastVisit.map((e) => e as int).toList(); + store.dispatch(UpdateLastVisit(lv)); + return lv; + } + + static void saveLastVisit(List lastVisit) { + getBox().then((box) { + box.put(Constants.KEY_LAST_VISIT, lastVisit.toString()); + }).catchError((error) { + print('Error occurred: $error'); + }); + } + + static String utcDatetimeStringToLocalDatetimeString( + BuildContext context, String utcDatetimeString, + {bool withTime = false}) { + var utcDatetime; + try { + utcDatetime = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .parse(utcDatetimeString, true); + } catch (error) { + utcDatetime = DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .parse(utcDatetimeString, true); + } + var localDatetime = utcDatetime.toLocal(); + return datetimeFormat(context, localDatetime, withTime: withTime); + } + + static String datetimeFormat(BuildContext context, DateTime date, + {bool withTime = false}) { + final theDate = DateTime(date.year, date.month, date.day); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); + final tomorrow = DateTime(now.year, now.month, now.day + 1); + DateFormat formatter = DateFormat('yyyy/MM/dd'); + DateFormat timeFormatter = DateFormat('HH:mm'); + if (withTime != null && withTime) { + formatter = DateFormat('yyyy/MM/dd HH:mm:ss'); + } + if (theDate == today) { + return S.of(context).today_with_time(timeFormatter.format(date)); + } + if (theDate == tomorrow) { + return S.of(context).tomorrow_with_time(timeFormatter.format(date)); + } + return formatter.format(date); + } + + void uploadPicture( + BuildContext context, + File image, + Map product, + int imageId, + String currentImageUrl, + OnSuccess onSuccess, + {int originalImageId = 0}) async { + if (image != null && await image.exists()) { + print('image: ${image.path}'); + String imageName = path.basename(image.path); + MultipartFile imageFile = + await MultipartFile.fromFile(image.path, filename: imageName); + Map formMap = { + 'id': product['id'], + 'image': imageFile, + 'original_image_id': originalImageId + }; + if (imageId == -1) { + HttpUtil.httpPost( + 'upload-product-image/', + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnUploadImageProgressEvent(imageId, false, 1.0)); + } else { + eventBus.fire( + OnUploadImageProgressEvent(imageId, true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } else { + HttpUtil.httpPost( + 'upload-product-gallery-image/', + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + isFormData: true, + body: formMap, + sendProgress: (int sent, int total) { + if (sent == total) { + eventBus.fire(OnUploadImageProgressEvent(imageId, false, 0.0)); + } else { + eventBus.fire( + OnUploadImageProgressEvent(imageId, true, sent / total)); + } + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + } + + static CartInfo getCartInfoByBusiness( + List cartInfos, Business business) { + if (cartInfos == null || cartInfos.length <= 0) { + return null; + } + for (var i = 0; i < cartInfos.length; i++) { + if (cartInfos[i].businessInfo.id == business.id) { + return cartInfos[i]; + } + } + return null; + } + + static CartInfo getCartInfoByBusinessId( + List cartInfos, int businessId) { + if (cartInfos == null || cartInfos.length <= 0) { + return null; + } + for (var i = 0; i < cartInfos.length; i++) { + if (cartInfos[i].businessInfo.id == businessId) { + return cartInfos[i]; + } + } + return null; + } + + static List addCartInfoToCartInfoList( + List cartInfos, CartInfo cartInfo) { + if (cartInfos == null || cartInfos.length <= 0) { + cartInfos = [cartInfo]; + } else { + bool found = false; + for (var i = 0; i < cartInfos.length; i++) { + if (cartInfos[i].businessInfo.id == cartInfo.businessInfo.id) { + cartInfos[i] = cartInfo; + found = true; + break; + } + } + if (!found) { + cartInfos.add(cartInfo); + } + } + Utils.getBox().then((box) { + box.put(Constants.KEY_CARTINFOS, cartInfos.toString()); + }).catchError((error) { + print('Error occurred: $error'); + }); + return cartInfos; + } + + static List removeCartInfoFromCartInfoList( + List cartInfos, CartInfo cartInfo) { + if (cartInfos == null || cartInfos.length <= 0 || cartInfo == null) { + return []; + } + int removeIndex = -1; + for (var i = 0; i < cartInfos.length; i++) { + if (cartInfo.businessInfo.id == cartInfos[i].businessInfo.id) { + removeIndex = i; + break; + } + } + if (removeIndex != -1) { + cartInfos.removeAt(removeIndex); + } + getBox().then((box) { + box.put(Constants.KEY_CARTINFOS, cartInfos.toString()); + }).catchError((error) { + print('Error occurred: $error'); + }); + return cartInfos; + } + + static void clearCart(BuildContext context, CartInfo cartInfo, + {OnComplete onComplete}) { + if (cartInfo != null && cartInfo.productList.length > 0) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_empty_basket), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + store.dispatch(new UpdateCartInfo( + Utils.removeCartInfoFromCartInfoList( + store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + Navigator.of(context).pop(); + if (onComplete != null) { + onComplete(null); + } + }, + ) + ], + ); + }); + } + } + + static void requireLogin(BuildContext context, + {String returnUrl, bool replace = true, int delayMilliseconds = 400}) { + Future.delayed(Duration(milliseconds: delayMilliseconds), () { + if (returnUrl != null) { + store.dispatch(UpdateRedirectRoute(returnUrl)); + } + Routes.router.navigateTo(context, '/login', replace: replace); + }); + } + + static String getFirstNumberFromString(String numString) { + final intInStr = RegExp(r'\d+'); + return intInStr.firstMatch(numString).group(0); + } + + static void loadProducts(int businessId, int categoryId, int page, bool more, + OnComplete onComplete, OnError onError) { + HttpUtil.httpGet( + more ? 'v1/service-category-products-more' : 'v1/service-category-products', + businessId: businessId, + queryParameters: { + // 'lat': store.state.position.latitude.toString(), + // 'lng': store.state.position.longitude.toString(), + 'category_id': categoryId, + 'preview': 'no', + 'page': page, + 'num_per_page': Constants.ORDERS_PER_PAGE, + }, + ).then((value) { + if (onComplete != null) { + onComplete(value); + } + }).catchError((error) { + if (onError != null) { + onError(error); + } + }); + } + + static String timestampToString(BuildContext context, int timestamp, {bool withTime=false}) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); + return datetimeFormat(context, date, withTime: withTime); + } + + static String errorMsg2(BuildContext context, String code, String defaultString) { + switch (code) { + case 'no_delivery': + return S.of(context).checkout_no_deliver; + case 'no_shipping_address': + return S.of(context).please_provide_shipping_address; + case 'over_delivery_distance': + return S.of(context).over_delivery_distance; + case 'amount_not_meet': + return S.of(context).amount_not_meet; + case 'select_canada_post_shipping_rate': + return S.of(context).select_canada_post_shipping_rate; + case 'store_close': + return S.of(context).store_closed; + } + return defaultString; + } + + static String knownName(BuildContext context, String name) { + switch (name) { + case 'pickup-discount': + return S.of(context).pickup_discount; + case 'collect-tips': + return S.of(context).service_fee; + default: + return name; + } + } + + static String getWeekDayName(BuildContext context, int timestamp, bool shortName) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); + switch(date.weekday) { + case 1: + if (shortName) { + return S.of(context).mon; + } else { + return S.of(context).monday; + } + break; + case 2: + if (shortName) { + return S.of(context).tue; + } else { + return S.of(context).tuesday; + } + break; + case 3: + if (shortName) { + return S.of(context).wed; + } else { + return S.of(context).wednesday; + } + break; + case 4: + if (shortName) { + return S.of(context).thu; + } else { + return S.of(context).thursday; + } + break; + case 5: + if (shortName) { + return S.of(context).fri; + } else { + return S.of(context).friday; + } + break; + case 6: + if (shortName) { + return S.of(context).sat; + } else { + return S.of(context).saturday; + } + break; + case 7: + if (shortName) { + return S.of(context).sun; + } else { + return S.of(context).sunday; + } + break; + } + } + + static void stripePaymentIntent(Order order, String customerId, String paymentMethodId, String cardType, + OnSuccess onSuccess, OnError onError, + { + String cardBrand, + String cardCountry, + int cardExpMonth, + int cardExpYear, + String cardFunding, + String cardLast4, + }) { + + HttpUtil.httpPost('v1/stripe-payment-intent', + onSuccess, + body: { + 'order_id': order.id, + 'pay_amount': order.totalPrice, + 'customer_id': customerId == null ? '' : customerId, + 'payment_method_id': paymentMethodId, + 'payment_method_type': cardType, + 'card_brand': cardBrand, + 'card_country': cardCountry, + 'card_exp_month': cardExpMonth, + 'card_exp_year': cardExpYear, + 'card_funding': cardFunding, + 'card_last4': cardLast4, + }, + isFormData: true, + ).catchError(onError); + } + + static void stripeChargedSuccess(Order order, String paymentMethodId, + String paymentIntentId, OnSuccess onSuccess, OnError onError) { + HttpUtil.httpPost('v1/stripe-charged-success', + onSuccess, + isFormData: true, + body: { + 'order_id': order.id, + 'payment_method_id': paymentMethodId, + 'payment_intent_id': paymentIntentId, + } + ).catchError(onError); + } + + static int getProductLineInOrder(CartInfo cartInfo) { + int qty = 0; + for (CartLineItem lineItem in cartInfo.productList) { + if (lineItem.product.id != null) { + qty += 1; + } + } + return qty; + } + + static void orderAgain(BuildContext context, CartInfo cartInfo) { + cartInfo.productList.removeWhere((element) => element.product == null || element.product.id == null); + store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + Routes.router.navigateTo(context, '/checkout/${cartInfo.businessInfo.id}'); + } + + static String getOrderStatus(BuildContext context, int statusCode) { + switch(statusCode) { + case Constants.STATUS_PENDING: + return S.of(context).pending; + case Constants.STATUS_ACCEPT: + return S.of(context).order_acceipt; + case Constants.STATUS_PROCESSING: + return S.of(context).order_processing; + case Constants.STATUS_COMPLETE: + return S.of(context).order_complete; + case Constants.STATUS_CANCELLED: + return S.of(context).order_cancelled; + default: + return S.of(context).pending; + } + } + + static CartLineItem newCartLineItem( + {int id, + double price, + Product product, + String name, + String description, + double quantity, + String parentUuid, + }) { + CartLineItem lineItem = CartLineItem(); + lineItem.unitPrice = price; + lineItem.product = product; + lineItem.name = product.name; + lineItem.description = description; + lineItem.quantity = quantity; + lineItem.parentUuid = parentUuid; + return lineItem; + } + + static void addSubproductToCard(CartInfo cartInfo, CartLineItem lineItem) { + if (lineItem.product.subproducts.length > 0) { + for (int j = 0; j < lineItem.product.subproducts.length; j++) { + cartInfo.productList.add( + newCartLineItem( + id: 0, + price: 0.0, + product: Product( + lineItem.product.subproducts[j].product.id, + lineItem.product.businessId, + lineItem.product.subproducts[j].product.name, + lineItem.product.subproducts[j].product.price, + lineItem.product.subproducts[j].product.description, + lineItem.product.subproducts[j].product.image, + lineItem.product.subproducts[j].product.productAttributes + ), + name: lineItem.product.subproducts[j].product.name, + description: lineItem.product.subproducts[j].product.description, + quantity: 1.0, + parentUuid: lineItem.uuid + ) + ); + } + } + } + + static void addSubproductQty(CartInfo cartInfo, CartLineItem lineItem, + {bool remove = false}) { + if (lineItem.product.subproducts.length > 0) { + for (var i = 0; i < cartInfo.productList.length; i++) { + CartLineItem lineItem1 = cartInfo.productList[i]; + if (lineItem1.parentUuid == lineItem.uuid) { + for (var j = 0; j < lineItem.product.subproducts.length; j++) { + if (lineItem.product.subproducts[j].product.id == lineItem1.product.id) { + if (remove) { + lineItem1.quantity -= lineItem.product.subproducts[j].quantity; + } else { + lineItem1.quantity += lineItem.product.subproducts[j].quantity; + } + break; + } + } + } + } + } + } + + static void removeSubproduct(CartInfo cartInfo, String uuid) { + cartInfo.productList.removeWhere((element) => element.parentUuid == uuid); + } + + static void openEmail(String email) async { + final Uri params = Uri( + scheme: 'mailto', + path: '$email', + ); + String url = params.toString(); + if (await canLaunch(url)) { + await launchURL(url); + } else { + print('Could not launch $url'); + } + } + + static void callPhone(String phone) async { + String callNo = 'tel://$phone'; + if (await canLaunch(callNo)) { + await launchURL(callNo); + } else { + print('Could not call $phone'); + } + } + + static Widget buildLine(String name, dynamic value, {double nameSize, double valueSize}) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + row.children.add( + Container( + width: 150, + padding: EdgeInsets.only(), + child: Text( + name, + style: TextStyle( + color: Colors.black38, + fontSize: nameSize, + ), + ), + ), + ); + if (value is String) { + row.children.add( + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Text( + value, + style: TextStyle( + fontSize: valueSize, + ), + ), + ), + ), + ); + } else { + row.children.add( + Expanded(child: value), + ); + } + return Container( + padding: EdgeInsets.only(top: 8, bottom: 8), + child: row, + ); + } } -class RuntimeError extends Error{ +class RuntimeError extends Error { final int code; final String message; RuntimeError(this.message, {this.code}); String toString() => "Runtime Error: $message"; -} \ No newline at end of file +} diff --git a/lib/widgets/desktop/desktop_appbar_menu.dart b/lib/widgets/desktop/desktop_appbar_menu.dart new file mode 100644 index 0000000..70b5e06 --- /dev/null +++ b/lib/widgets/desktop/desktop_appbar_menu.dart @@ -0,0 +1,143 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../constants.dart'; +import '../../dialog/logout_dialog.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../widgets/general/navigationbar_logo.dart'; +import '../../widgets/general/text_link.dart'; + +class DesktopAppBarMenu extends StatelessWidget { + User _user; + + @override + Widget build(BuildContext context) { + _user = store.state.user; + String currentRoute = ModalRoute.of(context).settings.name; + Widget menuBar = Container( + height: 56.0, + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), + color: Colors.blue, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + NavigationBarLogo(), + Expanded( + child: Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(left: 8.0, right: 16.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 20.0,), + TextLink( + S.of(context).home, + '/', + color: Colors.white, + selected: currentRoute == '/', + clearStack: true, + ), + SizedBox(width: 15.0,), + TextLink( + S.of(context).download, + '/download', + color: Colors.white, + selected: currentRoute == '/download', + ), + SizedBox(width: 15.0,), + TextLink( + S.of(context).tutorials, + Constants.TUTORIAL_URL, + color: Colors.white, + selected: currentRoute == '/tutorials', + isLink: true, + ), + SizedBox(width: 15.0,), + TextLink( + S.of(context).support, + '/my-support/${Constants.BUSINESS_ID}', + color: Colors.white, + selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}', + ), + SizedBox(width: 15.0,), + TextLink( + S.of(context).shop, + '/shop', + color: Colors.white, + selected: currentRoute == '/shop', + ), + SizedBox(width: 15.0,), + TextLink( + S.of(context).blog, + '/blog/${Constants.BUSINESS_ID}', + color: Colors.white, + selected: currentRoute == '/blog/${Constants.BUSINESS_ID}', + ), + SizedBox(width: 15.0,), + _user != null ? + (currentRoute == '/me') ? + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 4.0), + child: Text( + S.of(context).logout, + style: TextStyle( + color: Colors.white, + ), + ), + ), + Icon( + Icons.logout, + color: Colors.white, + size: 16.0, + ) + ], + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return logoutDialog(context); + } + ); + }, + ), + ) : IconButton( + icon: Icon( + Icons.account_circle, + color: Colors.white, + ), + onPressed: () { + Routes.router.navigateTo(context, '/me'); + }, + ) : + TextLink( + S.of(context).login, + '/login', + color: Colors.white, + selected: currentRoute == '/login', + ), + ], + ), + ), + ), + ), + ], + ), + ); + return menuBar; + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_blog.dart b/lib/widgets/desktop/desktop_blog.dart new file mode 100644 index 0000000..0340f1e --- /dev/null +++ b/lib/widgets/desktop/desktop_blog.dart @@ -0,0 +1,314 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/address.dart'; +import '../../models/blog.dart'; +import '../../models/ticket.dart'; +import '../../pages/edit_address.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class DesktopBlog extends StatefulWidget { + final int businessId; + const DesktopBlog({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return DesktopBlogState(); + } + +} + +class DesktopBlogState extends State { + List blogs; + + double division = 2; + + int _page = 1; + int _pageCount = 1; + + bool _isLoading = false; + bool _loadingFinish = false; + + RefreshController _refreshController = RefreshController(initialRefresh: true); + + void _onRefresh() { + _page = 1; + if (blogs != null) { + blogs.clear(); + } else { + blogs = []; + } + _refreshController.resetNoData(); + loadBlogs(true); + } + + void _onLoadMore() { + // if failed,use loadFailed(),if no data return,use LoadNodata() + if (_pageCount > _page) { + _page += 1; + loadBlogs(false); + } else { + _refreshController.loadNoData(); + } + } + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + BuildContext mainContext = context; + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).blog, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode){ + Widget footer; + if(mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center(child: footer,), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoadMore, + child: _buildBody(), + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget _buildBody() { + if (blogs == null) { + return SizedBox.shrink(); + } + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.only( + top: 12.0, + bottom: 16.0, + left: 8.0, + right: 8.0, + ), + child: blogs == null ? Text('') : + (blogs.length > 0 ? + Wrap( + children: blogs.map((a) => GestureDetector( + child: _getBlog(a), + onTap: () { + Routes.router.navigateTo(context, '/view-blog/${a.id}'); + }, + )).toList(), + ) : + Center( + child: Text(S.of(context).no_blog_yet), + )), + ), + Container( + width: sideSpace, + ), + ], + ); + return SingleChildScrollView( + child: row, + ); + } + + Widget _getBlog(Blog blog) { + return Container( + width: (mainSpace - 16.0) / division, + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ), + top: BorderSide( + color: Colors.black12, + width: 0.6, + ), + left: BorderSide( + color: Colors.black12, + width: 0.6, + ), + right: BorderSide( + color: Colors.black12, + width: 0.6, + ), + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + (blog.thumbUrl != null) ? + Container( + padding: EdgeInsets.only(right: 10.0), + child: Util.showImage( + 'https:${blog.thumbUrl}', + width: 80, + height: 80, + ), + ) : SizedBox.shrink(), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + blog.title, + style: TextStyle( + fontSize: 19.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt,), + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + @override + void dispose() { + _refreshController?.dispose(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + blogs = null; + }); + } + _refreshController.requestRefresh(); + }); + } + + void loadBlogs(bool isRefresh) { + _loadingFinish = false; + HttpUtil.httpGet( + 'v1/blogs', + businessId: widget.businessId, + queryParameters: { + 'page': _page.toString(), + 'size': Constants.BLOG_PER_PAGE_DESKTOP.toString() + } + ).then((value) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshCompleted(); + } else { + _refreshController.loadComplete(); + } + + if (int.parse(value['currentPage'].toString()) >= int.parse(value['pageCount'].toString())) { + _loadingFinish = true; + } + if (_loadingFinish) { + _refreshController.loadNoData(); + } + _page = int.parse(value['currentPage'].toString()); + _pageCount = int.parse(value['pageCount'].toString()); + + setState(() { + if (blogs == null) { + blogs = []; + } + blogs.addAll((value['blogs'] as List).map((e) => Blog.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshFailed(); + } else { + _refreshController.loadFailed(); + } + _isLoading = false; + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_buy_service.dart b/lib/widgets/desktop/desktop_buy_service.dart new file mode 100644 index 0000000..df9fc9f --- /dev/null +++ b/lib/widgets/desktop/desktop_buy_service.dart @@ -0,0 +1,225 @@ + +import 'package:flutter/material.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/key_value.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopBuyService extends StatefulWidget { + final Map data; + + const DesktopBuyService(this.data, {Key key}) + : super(key: key); + + @override + State createState() => DesktopBuyServiceState(); +} + +class DesktopBuyServiceState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + List plans = []; + KeyValue selectedPlan; + double price = 0.0; + double tax = 0.0; + double paymentAmount = 0.0; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + Column col1 = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 16), + child: Text( + S.of(context).purchase_renew_service, + style: TextStyle( + fontSize: 28, + ), + ), + ), + ], + ); + + col1.children.add( + Utils.buildLine(S.of(context).service_descritpion, widget.data['service_selections']['description'], valueSize: 16), + ); + + col1.children.add( + Utils.buildLine(S.of(context).your_group, widget.data['group']['name'], valueSize: 16), + ); + + if (widget.data['exists_service'] == null) { + col1.children.add( + Utils.buildLine(S.of(context).current_plan, 'N/A', valueSize: 16), + ); + } else { + col1.children.add( + Utils.buildLine(S.of(context).current_plan, widget.data['exists_service']['description'], valueSize: 16), + ); + col1.children.add( + Utils.buildLine(S.of(context).expiration_date, + Utils.utcDatetimeStringToLocalDatetimeString(context, + widget.data['exists_service']['expiration_date']), + valueSize: 16 + ), + ); + } + + col1.children.add( + Utils.buildLine(S.of(context).select_a_plan, DropdownButton( + items: plans.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.name), + ); + }).toList(), + isExpanded: true, + hint: Text( + S.of(context).select_a_plan, + ), + onChanged: (newValue) { + print('newValue $newValue'); + setState(() { + selectedPlan = newValue; + price = selectedPlan.value['price']; + tax = selectedPlan.value['price'] * selectedPlan.value['tax']; + paymentAmount = price + tax; + }); + }, + value: selectedPlan, + ), valueSize: 16), + ); + col1.children.add( + Utils.buildLine(S.of(context).price, price.toStringAsFixed(2), valueSize: 16) + ); + col1.children.add( + Utils.buildLine(S.of(context).tax, tax.toStringAsFixed(2), valueSize: 16) + ); + col1.children.add( + Utils.buildLine(S.of(context).total, paymentAmount.toStringAsFixed(2), valueSize: 32) + ); + + Column col2 = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16, right: 16), + child: ElevatedButton( + child: Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), + child: Text( + S.of(context).pay_now, + style: TextStyle( + fontSize: 20, + ), + ), + ), + onPressed: (paymentAmount > 0) ? () => _submit() : null, + ), + ), + ], + ); + + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col1, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + width: 1, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col2, + ), + ) + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).purchase_renew_service, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: row,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + void _submit() { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/buy-service/${widget.data['group']['id']}/${widget.data['service_selections']['code']}'); + return; + } + Map newData = widget.data; + newData['selected_plan'] = selectedPlan.value; + HttpUtil.httpPost('v1/create-service-buy-renewal-invoice', + (response) { + Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true); + }, + body: newData, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } + + @override + void initState() { + super.initState(); + List o = (widget.data['service_selections']['options'] as List); + for (int i = 0; i < o.length; i++) { + Map o1 = o[i]; + plans.add(new KeyValue(o1['name'], o1)); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_change_mobile_or_email.dart b/lib/widgets/desktop/desktop_change_mobile_or_email.dart new file mode 100644 index 0000000..fae755a --- /dev/null +++ b/lib/widgets/desktop/desktop_change_mobile_or_email.dart @@ -0,0 +1,421 @@ + + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopChangeMobileOrEmail extends StatefulWidget { + final bool isMobile; + + const DesktopChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopChangeMobileOrEmailState(); + } + +} + +class DesktopChangeMobileOrEmailState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget view = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: widget.isMobile ? TextInputType.phone : TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: widget.isMobile ? S.of(context).mobile_number : S.of(context).email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + if (widget.isMobile) { + return S + .of(context) + .mobile_is_required; + } else { + return S + .of(context) + .email_is_required; + } + } + if (widget.isMobile && value.trim() == store.state.user.mobile) { + return S.of(context).the_mobile_number_is_same_as_current; + } + if (!widget.isMobile && value.trim() == store.state.user.email) { + return S.of(context).the_email_is_same_as_current; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 14.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit_to_change, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + + Widget v = SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + widget.isMobile ? S.of(context).change_mobile : S.of(context).change_email, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: Text( + widget.isMobile ? S.of(context).change_mobile_desc : S.of(context).change_email_desc, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: view, + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + + return v; + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Utils.showMessageDialog(context, Exception(S.of(context).update_success), title: S.of(context).success, onOk: () { + Navigator.of(context).pop(); + Routes.router.navigateTo(context, '/me', replace: true, clearStack: true); + }); + }, + queryParameters: { + 'action': 'change-mobile-email', + }, + isFormData: true, + body: { + 'id': store.state.user.id, + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.isNotEmpty && + ((widget.isMobile && usernameController.text.trim() != store.state.user.mobile) || + (!widget.isMobile && usernameController.text.trim() != store.state.user.email))) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + }, + queryParameters: { + 'action': 'change_mobile_email_send_code' + }, + body: { + 'id': store.state.user.id, + 'mobile': usernameController.text, + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + String errorMsg = ''; + if (widget.isMobile && usernameController.text.trim().isEmpty) { + errorMsg = S.of(context).mobile_is_required; + } else if (!widget.isMobile && usernameController.text.trim().isEmpty) { + errorMsg = S.of(context).email_is_required; + } else if (widget.isMobile && usernameController.text.trim() == store.state.user.mobile) { + errorMsg = S.of(context).the_mobile_number_is_same_as_current; + } else if (!widget.isMobile && usernameController.text.trim() == store.state.user.email) { + errorMsg = S.of(context).the_email_is_same_as_current; + } + Fluttertoast.showToast( + msg: errorMsg, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_change_password.dart b/lib/widgets/desktop/desktop_change_password.dart new file mode 100644 index 0000000..4263e4b --- /dev/null +++ b/lib/widgets/desktop/desktop_change_password.dart @@ -0,0 +1,348 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +class DesktopChangePassword extends StatefulWidget { + const DesktopChangePassword({Key key}) : super(key: key); + + @override + State createState() { + return DesktopChangePasswordState(); + } + +} + +class DesktopChangePasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final oldPasswordController = TextEditingController(); + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget view = SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.only(top: 16.0, bottom: 20.0), + child: Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).change_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0), + child: Text( + S.of(context).change_password_desc, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + Expanded( + child: Column( + children: [ + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: oldPasswordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).old_password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).current_password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 16.0, top: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).password_has_been_changed), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'change_password', + }, + isFormData: true, + body: { + 'id': store.state.user.id, + 'old_password': oldPasswordController.text.trim(), + 'password': passwordController.text.trim(), + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_checkout.dart b/lib/widgets/desktop/desktop_checkout.dart new file mode 100644 index 0000000..b7badd4 --- /dev/null +++ b/lib/widgets/desktop/desktop_checkout.dart @@ -0,0 +1,2268 @@ + +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/general/navigationbar.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:intl/intl.dart'; +import 'package:toggle_switch/toggle_switch.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/address.dart'; +import '../../models/booking_date_time.dart'; +import '../../models/booking_time.dart'; +import '../../models/cart_info.dart'; +import '../../models/cart_line_item.dart'; +import '../../models/coupon.dart'; +import '../../models/error_message.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/shipping_rate.dart'; +import '../../models/text_value.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/sliding_up_panel.dart'; + +class DesktopCheckout extends StatefulWidget { + final Key key; + final int businessId; + + const DesktopCheckout(this.businessId, {this.key,}) : super(key: key); + + @override + State createState() { + return DesktopCheckoutState(); + } + +} + +class DesktopCheckoutState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { + CartInfo cartInfo; + Address shipAddress; + bool canSubmit; + List errorMessages; + List bookingTimeList; + List bookingDateTimeList; + List paymentPlatforms; + TextValue durationInTraffic; + int selectedCoupon; + double couponDiscountAmount = 0; + List coupons; + + int peopleCount = 2; + + final TextEditingController remarkController = new TextEditingController(); + String orderRemark = ''; + + int deliveryMethodIndex = 0; + String deliveryMethod; + List shippingRates = []; + ShippingRate selectedShippingRate; + + List shippingMethodLabels = []; + List shippingMethodIcons = []; + + GlobalKey _scaffoldKey = GlobalKey(); + + int bookingDateIndex; + int bookingTimeIndex; + int paymentPlatformIndex; + + GlobalKey slidingUpPanelKey = GlobalKey(); + SlidingUpPanel slidingUpPanel; + PanelController panelController = PanelController(); + Widget panel; + + double subtotal; + + TextEditingController newCouponController = TextEditingController(); + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + super.build(context); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + store.dispatch(UpdateContext(context)); + + if (cartInfo == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (cartInfo.businessInfo.deliveryPickup == false && cartInfo.businessInfo.deliveryCanadaPost == false && cartInfo.businessInfo.deliveryStoreDelivery == false) { + return Scaffold( + body: Container( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + S.of(context).no_delivery_method, + ), + RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).ok, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ), + ); + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if(errorMessages.length > 0) { + print('error: ${errorMessages.toString()}'); + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } + }); + + if (panel != null) { + slidingUpPanel = SlidingUpPanel( + key: slidingUpPanelKey, + minHeight: 0.0, + maxHeight: 400.0, + backdropEnabled: true, + backdropTapClosesPanel: false, + isDraggable: false, + controller: panelController, + panel: panel, + body: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: mainBody(),), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + Scaffold scaffold = Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).checkout, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: WillPopScope( + child: slidingUpPanel, + onWillPop: () async { + if (panelController != null && panelController.isPanelOpen) { + panelController.close(); + return false; + } + return true; + }, + ), + bottomNavigationBar: Container( + padding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 0.0, right: 0.0), + height: 55.0, +// color: Colors.black87, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: Container( + height: 55.0, + color: Colors.black87, + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(top: 12.0, bottom: 12.0, left: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '\$${(cartInfo.totalPrice - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 20.0, + color: Colors.white, + ), + ), + cartInfo.businessInfo.isPublic ? SizedBox.shrink() : Container( + padding: EdgeInsets.only(top: 2.0, bottom: 2.0, left: 5.0, right: 5.0), + width: 100.0, + color: Colors.red, + child: Text( + S.of(context).under_renovation, + style: TextStyle( + color: Colors.white, + fontSize: 10.0, + ), + maxLines: 3, + softWrap: true, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + Expanded( + flex: 1, + child: GestureDetector( + child: Container( + height: 55.0, + color: canSubmit ? Colors.lightGreen : Colors.grey, + child: Center( + child: Text( + S.of(context).pay_now, + style: TextStyle( + color: Colors.white, + fontSize: 20.0, + ), + ), + ), + ), + onTap: () { + confirmOrder(); + }, + ), + ), + ], + ), + ), + ); + + return scaffold; + } + + String getShippingMethod(int index) { + if (shippingMethodLabels[index] == S.of(context).delivery) { + return 'store-delivery'; + } else if (shippingMethodLabels[index] == S.of(context).pickup) { + return 'pickup'; + } else if (shippingMethodLabels[index] == S.of(context).canada_post) { + return 'canada-post'; + } + return 'store-delivery'; + } + + Widget mainBody() { + return ListView.builder( + itemCount: 6, + addAutomaticKeepAlives: true, + itemBuilder: (BuildContext context, int position) { + var deliveryTimeInSeconds = cartInfo.businessInfo.shippingTime * 60 + (durationInTraffic != null ? durationInTraffic.value : 0); + print('aaa: $deliveryTimeInSeconds'); + DateTime now = DateTime.now(); + var formatter = DateFormat('H:mm'); + String deliveryAt = formatter.format(now.add(Duration(seconds: deliveryTimeInSeconds))); + + Widget peopleCountSelection = Container( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).table_token(store.state.tableNumber), + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(right: 10.0), + child: Text(S.of(context).number_of_people), + ), + DropdownButton( + value: peopleCount, + items: [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + ].map((value) { + return DropdownMenuItem( + value: value, + child: Text(value.toString()), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + peopleCount = newValue; + }); + } + }, + hint: Text(S.of(context).number_of_people), + isExpanded: false, + ) + ], + ), + ), + ], + ), + ); + + ToggleSwitch toggleSwitch = ToggleSwitch( + minWidth: 160.0, + cornerRadius: 20, + activeBgColor: [Colors.green], + activeFgColor: Colors.white, + inactiveBgColor: Colors.grey, + inactiveFgColor: Colors.white, + labels: shippingMethodLabels, + icons: shippingMethodIcons, + onToggle: (index) { + selectedShippingRate = null; + deliveryMethod = getShippingMethod(index); + check(); + }, + initialLabelIndex: deliveryMethodIndex, + ); + switch (position) { + case 0: + if (cartInfo.businessInfo.deliveryPickup) { + return Container( + padding: EdgeInsets.only( + top: 16.0, bottom: 16.0, left: 16.0, right: 16.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 10.0, + color: Colors.transparent, + ), + ), + ), + child: Container( + alignment: Alignment.centerLeft, + child: store.state.deviceId != null && store.state.deviceId.isNotEmpty ? ( + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty ? + peopleCountSelection : + SizedBox.shrink() + ) : Center(child: toggleSwitch,), + ), + ); + } else { + return Container( + child: SizedBox(), + ); + } + break; + case 1: + if (store.state.deviceId != null && store.state.deviceId.isNotEmpty || + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty) { + return SizedBox.shrink(); + } + if (deliveryMethod == 'pickup') { + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).pickup_at, + style: TextStyle( + fontSize: 20.0, + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Text( + cartInfo.businessInfo.name, + style: TextStyle( + fontSize: 17.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 5.0), + child: Text( + cartInfo.businessInfo.address.addressLine1, + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ), + ), + Container( + child: cartInfo.businessInfo.address.addressLine2 != null + && cartInfo.businessInfo.address.addressLine2.length > 0 ? + Text(cartInfo.businessInfo.address.addressLine2, + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ) : SizedBox.shrink(), + ), + Container( + child: Text( + '${cartInfo.businessInfo.address.city}, ${cartInfo.businessInfo.address.state}, ${cartInfo.businessInfo.address.zip}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 5.0), + child: Text( + 'Tel: ${cartInfo.businessInfo.phone}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black54, + ) + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ) + ), + ), + ); + } + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Container( + padding: EdgeInsets.only(bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + shipAddress != null ? shipAddress.fullAddress : S.of(context).enter_delivery_address, + style: TextStyle( + fontSize: 16.0 + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: EdgeInsets.only(top: 6.0), + child: Text( + shipAddress != null ? shipAddress.contactName + ' ' + shipAddress.phone : '', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ) + ], + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/my-addresses/${cartInfo.businessInfo.id}', replace: true); + }, + ); + break; + case 2: + if (deliveryMethod == 'pickup' || store.state.deviceId != null && store.state.deviceId.isNotEmpty || + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty) { + return SizedBox.shrink(); + } + if (deliveryMethod == 'canada-post') { + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + margin: EdgeInsets.only(right: 10.0), + child: Text( + S.of(context).canada_post_delivery, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 1, + child: Container( + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(right: 5.0), + alignment: Alignment.centerRight, + child: Text( + selectedShippingRate != null ? + '${selectedShippingRate.name} \$${selectedShippingRate.price.toStringAsFixed(2)}' : + S.of(context).please_select, + style: TextStyle( + fontSize: 15.0, + color: Colors.black38, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ), + ], + ), + ), + ), + ], + ), + ), + onTap: () { + onCanadaPostTapped(); + }, + ); + } + if (!cartInfo.businessInfo.instanceDelivery) { + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + child: Text(S.of(context).no_instance_delivery_desc), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ), + ); + } + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: Text( + bookingTimeList.length > 0 || bookingDateTimeList.length > 0 ? S.of(context).delivery_now : S.of(context).delivery_unavailable, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + bookingTimeList.length > 0 ? '${Utils.timestampToString(context, bookingTimeList[bookingTimeIndex].unixTime)}' + : ((bookingTimeList.length == 0 && bookingDateTimeList.length == 0) ? '' + : bookingDateTimeList[bookingDateIndex].viewDate + ' ' + (bookingDateTimeList[bookingDateIndex].bookTimes.length > 0 ? bookingDateTimeList[bookingDateIndex].bookTimes[bookingTimeIndex].viewTime : '')), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 18.0, + color: Colors.grey, + ), + ), + ], + ), + ), + ], + ), + ), + onTap: onDeliveryTap, + ); + case 3: + return SizedBox.shrink(); + // disable payment panel + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black12, + ), + ), + ), + child: GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).payment_method, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + paymentPlatforms.length == 0 ? S.of(context).pay_on_deliery : PaymentPlatform.getPaymentPlatformName(context, paymentPlatforms[paymentPlatformIndex].code), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 18.0, + color: Colors.grey, + ), + ) + ], + ), + ), + ], + ), + onTap: () { + onPaymentMethodTapped(); + }, + ), + ); + break; + case 4: + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + column.children.add(Container( + width: double.infinity, + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + cartInfo.businessInfo.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.5, + ), + ), + ), + ),); + + subtotal = 0.0; + + for (var i = 0; i < cartInfo.productList.length; i++) { + subtotal += cartInfo.productList[i].totalPrice; + column.children.add(lineItem(cartInfo.productList[i])); + } + column.children.add(GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 8.0), + padding: EdgeInsets.only(top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + child: Text( + S.of(context).credit_coupon, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + getSelectedCouponName(), + style: TextStyle( + color: Colors.grey, + fontSize: 14.0, + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ) + ], + ) + ], + ), + ), + onTap: () { + onCouponsTapped(); + }, + )); + + column.children.add(Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).subtotal, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${(subtotal - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + )); + + if (cartInfo.extraFeeList.length > 0) { + for (var i = 0; i < cartInfo.extraFeeList.length; i++) { + column.children.add(Container( + padding: EdgeInsets.only(bottom: 16.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.centerRight, + child: Text( + S.of(context).extra_fee_token(cartInfo.extraFeeList[i].name, cartInfo.extraFeeList[i].rate), + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${cartInfo.extraFeeList[i].price.toStringAsFixed(2)}' + ), + ), + ], + ), + )); + } + } + + column.children.add(Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).total, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${(cartInfo.totalPrice - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 19.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ), + ), + ), + child: column, + ); + break; + case 5: + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.transparent, + width: 160.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).order_remark, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(right: 5.0), + child: Text( + orderRemark, + maxLines: 1, + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.grey, + size: 18.0, + ) + ], + ), + ), + ], + ), + ), + onTap: () { + onRemarkTapped(); + }, + ); + break; + default: + return SizedBox(); + } + }, + ); + } + + Widget lineItem(CartLineItem cartLineItem) { + return Container( + padding: EdgeInsets.only(top: 16.0, bottom: 0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(5.0), + child: Util.showImage('${cartLineItem.product.imagePath}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 80.0, color: Colors.transparent,), + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + Utils.knownName(context, cartLineItem.name), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + cartLineItem.description, + style: TextStyle( + fontSize: 12.0, + color: Colors.black38, + ), + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + ) + ], + ), + ), + Container( + alignment: Alignment.centerRight, + margin: EdgeInsets.only(right: 10.0), + child: Text( + 'x${cartLineItem.quantity.round()}', + ), + ), + Container( + width: 60.0, + alignment: Alignment.centerRight, + child: Text( + '${cartLineItem.totalPrice.toStringAsFixed(2)}', + ), + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + canSubmit = false; + errorMessages = []; + bookingTimeList = []; + bookingDateTimeList = []; + paymentPlatforms = []; + bookingDateIndex = 0; + bookingTimeIndex = 0; + paymentPlatformIndex = 0; + deliveryMethodIndex = 0; + deliveryMethod = ''; + panel = Container( + child: SizedBox(), + ); + remarkController.addListener(remarkChangeListener); + check(); + } + + void check() { + if (cartInfo != null) { + Utils.showLoadingDialog(context); + } + CartInfo ci = Utils.getCartInfoByBusinessId(store.state.cartInfos, widget.businessId); + HttpUtil.httpPost('v1/orders/check', (response) { + if (cartInfo != null) { + Navigator.of(context).pop(); + } + Utils.jsonPrettyPrint(response.data); + if (mounted) { + shippingMethodLabels.clear(); + shippingMethodIcons.clear(); + setState(() { + cartInfo = CartInfo.fromJson(response.data['cart_info']); + shipAddress = response.data['last_address'] != null ? Address.fromJson(response.data['last_address']) : null; + canSubmit = response.data['can_submit']; + errorMessages = (response.data['error_messages'] as List).map((e) => ErrorMessage.fromJson(e)).toList(); + bookingTimeList = (response.data['booking_time_list'] as List).map((e) => BookingTime.fromJson(e)).toList(); + bookingDateTimeList = (response.data['booking_date_time_list'] as List).map((e) => BookingDateTime.fromJson(e)).toList(); + paymentPlatforms = (response.data['payment_platforms'] as List).map((e) => PaymentPlatform.fromJson(e)).toList(); + durationInTraffic = response.data['duration_in_traffic'] != null ? TextValue.fromJson(response.data['duration_in_traffic']) : null; + selectedCoupon = response.data['selected_coupon']; + coupons = (response.data['coupons'] as List).map((e) => Coupon.fromJson(e)).toList(); + deliveryMethod = response.data['delivery']; + shippingRates = (response.data['shipping_rates'] as List).map((e) => ShippingRate.fromJson(e)).toList(); + selectedShippingRate = (response.data['selected_shipping_rate'] as String).length > 0 ? ShippingRate.fromJson(json.decode(response.data['selected_shipping_rate'])) : null; + int i = 0; + if (cartInfo.businessInfo.deliveryStoreDelivery) { + shippingMethodLabels.add(S.of(context).delivery); + shippingMethodIcons.add(Icons.directions_car); + if (deliveryMethod == 'store-delivery') { + deliveryMethodIndex = i; + } + i++; + } + if (cartInfo.businessInfo.deliveryCanadaPost) { + shippingMethodLabels.add(S.of(context).canada_post); + shippingMethodIcons.add(Icons.local_shipping); + if (deliveryMethod == 'canada-post') { + deliveryMethodIndex = i; + } + i++; + } + if (cartInfo.businessInfo.deliveryPickup) { + shippingMethodLabels.add(S.of(context).pickup); + shippingMethodIcons.add(Icons.store); + if (deliveryMethod == 'pickup') { + deliveryMethodIndex = i; + } + i++; + } + }); + } + }, + businessId: widget.businessId, + body: { + 'product_list': ci.productList.toString(), + 'extra_data': ci.extraData, + 'business_id': widget.businessId, + 'pay_method': getPaymentMethod(), + 'delivery': deliveryMethod, + 'selected_coupon': selectedCoupon, + 'selected_shipping_rate': selectedShippingRate != null ? selectedShippingRate.toString() : '', + 'shipping_rates': shippingRates.toString(), + 'device_id': store.state.deviceId != null ? store.state.deviceId : '', + 'table_number': store.state.tableNumber != null ? store.state.tableNumber : '', + }, + isFormData: true, + ).catchError((error, stacktrace) { + if (cartInfo != null) { + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + + int getPaymentMethod() { + int method = 0; + if (paymentPlatforms.length > 0 && paymentPlatformIndex < paymentPlatforms.length) { + PaymentPlatform paymentPlatform = paymentPlatforms[paymentPlatformIndex]; + if (paymentPlatform.method == Constants.PAYMENT_METHOD_PAY_ON_DELIVERY) { + return 1; + } + } + return method; + } + + void afterBuild(Duration time) { + if(errorMessages.length > 0) { + print('error: ${errorMessages.toString()}'); + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } + } + + SnackBar errorSnackBar(List messages) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + for (var i = 0; i < messages.length; i++) { + column.children.add(Container( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 10.0, bottom: 0.0), + child: Text( + Utils.errorMsg2(context, messages[i].code, messages[i].string), + ), + )); + } + return SnackBar( + content: Container( + height: 60.0 * messages.length, + child: column, + ), + action: SnackBarAction( + label: S.of(context).ok, + onPressed: () { + // _scaffoldKey.currentState.hideCurrentSnackBar(); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ); + } + + Widget getBookingTimeWidget() { + Widget widget; + if (bookingTimeList.length > 0) { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: ListView.builder( + itemCount: bookingTimeList.length, + itemBuilder: (BuildContext context, int position) { + BookingTime bookingTime = bookingTimeList[position]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 10.0, top: 10.0), + child: Text( + '${Utils.timestampToString(context, bookingTime.unixTime)}', + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.3, + color: Colors.black12, + ) + ) + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingDateIndex = 0; + bookingTimeIndex = position; + }); + } + panelController.close(); + }, + ); + }, + ), + ) + ], + ); + } else if (bookingDateTimeList.length > 0) { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + width: 150.0, + color: new Color(0xFFF5F5F5), + child: SizedBox.expand( + child: ListView.builder( + itemCount: bookingDateTimeList.length, + itemBuilder: (BuildContext context, int position) { + BookingDateTime bookingDateTime = bookingDateTimeList[position]; + return GestureDetector( + child: Container( + color: bookingDateIndex == position ? Colors.white : Colors.transparent, + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + child: Center( + child: Text( + '${bookingDateTime.viewDate} (${Utils.getWeekDayName(context, bookingDateTime.unixTime, true)})', + ), + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingDateIndex = position; + panel = getBookingTimeWidget(); + }); + } + }, + ); + }, + ), + ), + ), + Expanded( + child: SizedBox.expand( + child: ListView.builder( + itemCount: bookingDateTimeList[bookingDateIndex].bookTimes.length, + itemBuilder: (BuildContext context, int position) { + BookingDateTime bookingDateTime = bookingDateTimeList[bookingDateIndex]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0, bottom: 12.0), + child: Text( + bookingDateTime.bookTimes[position].viewTime, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.3, + ), + ), + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingTimeIndex = position; + }); + } + panelController.close(); + }, + ); + }, + ), + ), + ), + ], + ), + ) + ], + ); + } else { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).close), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: Container( + padding: EdgeInsets.all(20.0), + child: Center( + child: Text( + S.of(context).shipping_time_will_schedule, + ), + ), + ), + ) + ], + ); + } + return widget; + } + + Widget getPaymentMethods() { + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_a_payment_method, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + + if (paymentPlatforms.length == 0) { + widget.children.add(Center( + child: Container( + padding: EdgeInsets.all(16.0), + child: Text( + S.of(context).payment_method_not_set, + ), + ), + )); + } else { + widget.children.add( + Expanded( + child:ListView.builder( + itemCount: paymentPlatforms.length, + itemBuilder: (BuildContext context, int position) { + PaymentPlatform paymentPlatform = paymentPlatforms[position]; + if (paymentPlatform.code == Constants.PAYMENT_METHOD_CODE_SQUARE && + (paymentPlatform.squareAppId == null || paymentPlatform.squareAppId.isEmpty) && + (paymentPlatform.squareAccessToken == null || paymentPlatform.squareAccessToken.isEmpty) && + (paymentPlatform.squareLocationId == null || paymentPlatform.squareLocationId.isEmpty) + ) { + return SizedBox.shrink(); + } + if (paymentPlatform.code == Constants.PAYMENT_METHOD_CODE_CHASE && + (paymentPlatform.xLogin == null || paymentPlatform.xLogin.isEmpty) && + (paymentPlatform.transactionKey == null || paymentPlatform.transactionKey.isEmpty) && + (paymentPlatform.responseKey == null || paymentPlatform.responseKey.isEmpty) + ) { + return SizedBox.shrink(); + } + Widget paymentWidget = GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: paymentPlatformIndex == position ? BoxDecoration( + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.3, + color: Colors.black12, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Util.showImage(paymentPlatform.icon, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 50.0, color: Colors.transparent,), + fit: BoxFit.cover, + width: 50.0, + height: 50.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 10.0), + child: Text( + PaymentPlatform.getPaymentPlatformName(context, paymentPlatform.code), + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + Container( + child: Icon( + Icons.check, + size: 32.0, + color: paymentPlatformIndex == position ? Colors.blue : Colors.grey, + ), + ), + ], + ), + ), + onTap: () { + setState(() { + paymentPlatformIndex = position; + }); + panelController.close(); + }, + ); + return paymentWidget; + } + ), + ), + ); + } + return widget; + } + + String getSelectedCouponName() { + if (selectedCoupon == null) { + if (coupons.length > 0) { + return S + .of(context) + .please_select; + } else { + return S + .of(context) + .no_coupon_available; + } + } else if (selectedCoupon == 0) { + return S.of(context).dont_use; + } else { + for (var i = 0; i < coupons.length; i++) { + if (selectedCoupon == coupons[i].id) { + if (coupons[i].isPercentage) { + return S.of(context).percentage_discount_token2(couponDiscountAmount.toStringAsFixed(2), coupons[i].valueAmount); + } else { + return S.of(context).discount_amount_token(couponDiscountAmount.toStringAsFixed(2)); + } + } + } + return 'N/A'; + } + } + + Widget getCouponList() { + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).pick_a_coupon, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + + widget.children.add( + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 5.0, + color: Colors.black26, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: TextFormField( + controller: newCouponController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).enter_coupon_code, + ), + validator: (String value) { + return null; + }, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: RaisedButton( + child: Text( + S.of(context).get_coupon, + ), + onPressed: () { + if (newCouponController.text.trim().isEmpty) { + Fluttertoast.showToast( + msg: S.of(context).please_enter_coupon_code, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white, + ); + } else { + _checkCouponCode(newCouponController.text.trim()); + } + }, + ), + ), + ], + ), + ), + ); + + if (paymentPlatforms.length == 0) { + widget.children.add(Center( + child: Container( + padding: EdgeInsets.all(16.0), + child: Text( + S.of(context).no_coupon_available, + ), + ), + )); + } else { + widget.children.add(Expanded( + child: ListView.builder( + itemCount: coupons.length + 1, + itemBuilder: (BuildContext context, int position) { + if (position == 0) { + return GestureDetector( + child: Container( + decoration: selectedCoupon == 0 ? BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Text( + S.of(context).dont_use, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedCoupon == 0 ? Colors.lightBlue : Colors.black26, + ), + ), + ], + ), + ), + onTap: () { + if (mounted) { + setState(() { + selectedCoupon = 0; + couponDiscountAmount = 0; + }); + panelController.close(); + } + }, + ); + } else { + Coupon coupon = coupons[position - 1]; + return GestureDetector( + child: Container( + decoration: selectedCoupon == coupon.id ? BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: subtotal > coupon.minAmount ? Colors + .transparent : Colors.black26, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: coupon.isPercentage ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 30.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S + .of(context) + .percent_discount, + style: TextStyle( + fontSize: 12.0, + color: Colors.redAccent, + ), + ), + ) + ], + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Text( + '\$', + style: TextStyle( + fontSize: 12.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 30.0, + color: Colors.redAccent, + ), + ), + ) + ], + ), + ), + Expanded( + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + child: Text( + '${coupon.description}', + style: TextStyle( + fontSize: 11.0, + color: Colors.black45, + ), + ), + ), + Container( + child: Text( + coupon.expirationDate != null ? + S.of(context).expiration_date_token( + coupon.expirationDate) : + S.of(context) + .no_expiration, + style: TextStyle( + fontSize: 11.0, + color: Colors.black26, + ), + ), + ), + Container( + child: Text( + coupon.minAmount > 0 ? + S.of(context).min_order_amount_token( + coupon.minAmount) : + S.of(context) + .no_restriction, + style: TextStyle( + fontSize: 11.0, + color: Colors.black26, + ), + ), + ) + ], + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedCoupon == coupon.id ? Colors.lightBlue : Colors.black26, + ), + ) + ], + ), + ), + onTap: () { + if (subtotal > coupon.minAmount && + mounted) { + setState(() { + selectedCoupon = coupon.id; + if (coupon.isPercentage) { + couponDiscountAmount = subtotal * coupon.valueAmount / 100.0; + } else { + couponDiscountAmount = coupon.valueAmount; + } + }); + panelController.close(); + } + }, + ); + } + }, + ), + ),); + } + return widget; + } + + remarkChangeListener() { + orderRemark = remarkController.text; + } + + Widget remarkPanel() { + Column quickInputs = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + if (cartInfo.businessInfo.quickInputs.length > 0) { + Wrap w = Wrap( + children: [], + ); + quickInputs.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).quick_input, + style: TextStyle( + fontSize: 18.0, + color: Colors.black54, + ), + ), + )); + for (int i = 0; i < cartInfo.businessInfo.quickInputs.length; i++) { + String qi = cartInfo.businessInfo.quickInputs[i].value; + w.children.add(FlatButton( + child: Text( + qi, + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + onPressed: () { + orderRemark += ' ' + qi; + remarkController.text = orderRemark; + }, + )); + } + quickInputs.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0), + child: w, + )); + } + + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).order_remark, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).save), + onPressed: () { + if (mounted) { + setState(() { + orderRemark = remarkController.text; + }); + } + panelController.close(); + }, + ), + ), + ], + ), + Expanded( + child: ListView( + children: [ + Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 16.0, right: 16.0), + child: TextField( + controller: remarkController, + keyboardType: TextInputType.multiline, + maxLines: 5, + maxLength: 100, + decoration: new InputDecoration( + border: new OutlineInputBorder( + borderRadius: const BorderRadius.all( + const Radius.circular(12.0), + ), + ), + filled: true, + hintStyle: new TextStyle(color: Colors.grey[800]), + hintText: S.of(context).type_your_order_remark, + fillColor: Colors.white70, + ), + ), + ), + quickInputs, + ], + ), + ), + ], + ); + return widget; + } + + Widget canadaPostPanel() { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).choose_a_shipping_rate, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + column.children.add(Expanded( + child: ListView.builder( + itemCount: shippingRates.length, + itemBuilder: (BuildContext context, int position) { + ShippingRate shippingRate = shippingRates[position]; + return GestureDetector( + child: Container( + decoration: selectedShippingRate == shippingRate ? BoxDecoration( + color: Colors.transparent, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: Colors.transparent, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Container( + child: Text( + shippingRate.name, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.centerRight, + child: Text( + '${shippingRate.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + color: Colors.black38, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedShippingRate == shippingRate ? Colors.lightBlue : Colors.black26, + ), + ) + ], + ), + ), + onTap: () { + selectedShippingRate = shippingRate; + panelController.close(); + check(); + }, + ); + } + ), + ),); + + return column; + } + + void onDeliveryTap() { + panelController.open(); + setState(() { + panel = getBookingTimeWidget(); + }); + } + + void onPaymentMethodTapped() { + panelController.open(); + setState(() { + panel = getPaymentMethods(); + }); + } + + void onCouponsTapped() { + panelController.open(); + setState(() { + panel = getCouponList(); + }); + } + + void onRemarkTapped() { + panelController.open(); + setState(() { + panel = remarkPanel(); + }); + } + + void onCanadaPostTapped() { + panelController.open(); + setState(() { + panel = canadaPostPanel(); + }); + } + + confirmOrder() { + if (!canSubmit) { + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } else { + Utils.showSubmitDialog(context); + HttpUtil.httpPost('v1/orders', (response) { + Navigator.of(context).pop(); + store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(store.state.cartInfos, cartInfo))); + eventBus.fire(OnCartInfoUpdated()); + Order order = Order.fromJson(response.data); + PaymentPlatform paymentPlatform = paymentPlatforms[paymentPlatformIndex]; + Routes.router.navigateTo(context, '/paynow/${order.id}', replace: true); + }, + businessId: widget.businessId, + body: { + 'cart_id': cartInfo.id, + 'remark': orderRemark, + 'booked_at': bookingTimeList.length > 0 + ? bookingTimeList[bookingTimeIndex].unixTime + : ( + (bookingTimeList.length == 0 && bookingDateTimeList.length == 0) ? + 0 : + bookingDateTimeList[bookingDateIndex].bookTimes[bookingTimeIndex] + .unixTime + ), + 'delivery': deliveryMethod, + 'selected_coupon': selectedCoupon, + 'delivery_method': deliveryMethod, + 'coupon_discount_amount': couponDiscountAmount, + 'device_id': store.state.deviceId != null ? store.state.deviceId : '', + 'table_number': store.state.tableNumber != null ? store.state.tableNumber : '', + 'people_count': peopleCount, + }, + isFormData: true, + ).catchError((error) { + Navigator.of(context).pop(); + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + } + + @override + bool get wantKeepAlive => true; + + _checkCouponCode(String code) { + Utils.showSubmitDialog(context); + HttpUtil.httpGet('v1/check-coupon-code', + queryParameters: { + 'coupon_code': code, + 'store_id': widget.businessId, + }).then((data) { + Navigator.of(context).pop(); + if (mounted) { + setState(() { + newCouponController.text = ''; + coupons = (data as List).map((e) => Coupon.fromJson(e)).toList(); + panel = getCouponList(); + }); + } + }).catchError((error) { + Navigator.of(context).pop(); + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_contact_us.dart b/lib/widgets/desktop/desktop_contact_us.dart new file mode 100644 index 0000000..99f0aff --- /dev/null +++ b/lib/widgets/desktop/desktop_contact_us.dart @@ -0,0 +1,357 @@ + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/text_link.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:universal_io/io.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class DesktopContactUs extends StatefulWidget { + final Business business; + + const DesktopContactUs(this.business, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopContactUsState(); + } + +} + +class DesktopContactUsState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + String mapUrl = 'https://goo.gl/maps/M365MF5AW35n9ij67'; + + Completer _controller = Completer(); + LatLng _lastMapPosition; + final Set _markers = {}; + final Set _polyLine = {}; + + void _onMapCreated(GoogleMapController controller) { + _controller.complete(controller); + } + + void _onCameraMove(CameraPosition position) { + _lastMapPosition = position.target; + } + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Column col = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + col.children.add( + Container( + padding: EdgeInsets.only(top: 20.0, left: 16, right: 16, bottom: 20), + child: Center( + child: Text( + S.of(context).contact_us, + style: TextStyle( + fontSize: 36, + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0, bottom: 20), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 20), + child: Util.showImage( + '${widget.business.picUrl}', + width: 100, + height: 100, + ), + ), + Container( + child: Text( + '${widget.business.name}', + style: TextStyle( + fontSize: 20, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black38, + ), + ), + ), + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.mail_outline, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).by_email, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + 'support@wisetronic.com', + 'support@wisetronic.com', + isEmail: true, + ), + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.phone, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).by_phone, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + '905-604-8861', + '905-604-8861', + isPhone: true, + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + '905-604-6681', + '905-604-6681', + isPhone: true, + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Row( + children: [ + Container( + child: Text( + S.of(context).toll_free, + ), + ), + Container( + child: TextLink( + '1-855-278-8026', + '1-855-278-8026', + isPhone: true, + ), + ), + ], + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.location_on, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).address, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.addressLine1}', + ), + ) + ); + if (widget.business.address.addressLine2 != null && widget.business.address.addressLine2.isNotEmpty) { + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.addressLine2}', + ), + ) + ); + } + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.city}, ${widget.business.address.state}', + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.country}, ${widget.business.address.zip}', + ), + ) + ); + if (Platform.isAndroid || Platform.isIOS) { + _markers.clear(); + _markers.add(Marker( + markerId: MarkerId('shop_position'), + position: LatLng(double.parse(widget.business.address.lat), + double.parse(widget.business.address.lng)), + infoWindow: InfoWindow( + title: S + .of(context) + .store, + snippet: '', + ), + )); + col.children.add( + Container( + height: 200, + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), + child: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: LatLng( + double.parse(widget.business.address.lat), + double.parse(widget.business.address.lng)), + zoom: 14.0, + ), + onCameraMove: _onCameraMove, + markers: _markers, + gestureRecognizers: >[ + new Factory(() => new EagerGestureRecognizer(),) + ].toSet(), + ), + ), + ); + } else { + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), + child: TextLink( + S.of(context).view_on_google_map, + mapUrl, + isLink: true, + ), + ) + ); + } + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).contact_us, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: col,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_coupons.dart b/lib/widgets/desktop/desktop_coupons.dart new file mode 100644 index 0000000..67e2929 --- /dev/null +++ b/lib/widgets/desktop/desktop_coupons.dart @@ -0,0 +1,330 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/coupon.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopCoupons extends StatefulWidget { + final int contactId; + final Key key; + const DesktopCoupons(this.contactId, {this.key}); + + @override + State createState() { + return DesktopCouponsState(); + } +} + +class DesktopCouponsState extends State { + List coupons; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (coupons == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget w = ListView.builder( + itemCount: coupons.length > 0 ? coupons.length : 1, + itemBuilder: (BuildContext context, int position) { + if (coupons.length > 0) { + Coupon coupon = coupons[position]; + return Container( + color: Colors.black12, + child: couponWidget(coupon), + ); + } else { + return Center( + child: Container( + padding: EdgeInsets.all(20.0), + child: Text( + S.of(context).no_coupon_available, + ), + ), + ); + } + } + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).coupons, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: w,), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + Widget couponWidget(Coupon coupon) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: coupon.store != null ? + Util.showImage('${coupon.store.picUrl}', + fit: BoxFit.fill, + width: 40.0, + ) : + Image.asset( + 'assets/images/ic_launcher.png', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + coupon.store != null ? coupon.store.name : S.of(context).general_coupon, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + coupon.description, + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ) + ], + ), + ), + ), + ], + ); + + Column valueColumn = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: !coupon.isPercentage ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '\$', + style: TextStyle( + fontSize: 15.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 28.0, + color: Colors.redAccent, + ), + ), + ), + ], + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 28.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).percent_discount, + style: TextStyle( + fontSize: 15.0, + color: Colors.redAccent, + ), + ), + ), + ], + ), + ), + Container( + child: Text( + coupon.minAmount > 0 ? + S.of(context).available_for_order_over_token(coupon.minAmount) : + S.of(context).no_restriction, + style: TextStyle( + fontSize: 10.0, + color: Colors.black26, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + + row.children.add(valueColumn); + column.children.add(row); + column.children.add(Container( + width: double.infinity, + margin: EdgeInsets.only(top: 10.0, bottom: 0.0), + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 0.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12 + ) + ) + ), + child: SizedBox.shrink(), + )); + + column.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + coupon.expirationDate == null || coupon.expirationDate.length == 0 ? + S.of(context).no_expiration : + S.of(context).expiration_date_token(coupon.expirationDate), + style: TextStyle( + color: Colors.black26, + fontSize: 15.0, + ), + ), + ), + Container( + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).redeem_coupon, + style: TextStyle( + color: Colors.white, + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + onPressed: () { + if (coupon.store != null) { + Routes.router.navigateTo(context, '/shop/${coupon.store.id}/na/na/na'); + } else { + Routes.router.navigateTo(context, '/businesses'); + } + }, + ), + ), + ], + ), + ); + + return Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0, bottom: 5.0), + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 10.0, right: 10.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + child: column, + ); + } + + @override + void initState() { + super.initState(); + coupons = null; + HttpUtil.httpGet( + 'v1/coupons' + ).then((data) { + if (mounted) { + setState(() { + coupons = + (data as List).map((e) => Coupon.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_download_apps.dart b/lib/widgets/desktop/desktop_download_apps.dart index 3c4b941..efb6c1d 100644 --- a/lib/widgets/desktop/desktop_download_apps.dart +++ b/lib/widgets/desktop/desktop_download_apps.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; import '../../widgets/general/download_item.dart'; import '../../store/store.dart'; import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; @@ -34,8 +36,10 @@ class DesktopDownloadAppsState extends State { } Column col = Column( children: [ - Util.showImage( - 'https:${widget.data['download-image']['image']}' + Container( + child: Util.showImage( + 'https:${widget.data['download-image']['image']}' + ), ), ], mainAxisAlignment: MainAxisAlignment.start, @@ -50,20 +54,33 @@ class DesktopDownloadAppsState extends State { wrap.children.add(apps[i]); } col.children.add(wrap); - return Row( - children: [ - Container( - width: sideSpace, - ), - Container( - width: mainSpace, - child: col, - ), - Container( - width: sideSpace, - ), - ], + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: col, + ), + Container( + width: sideSpace, + ), + ], + ), ); + // return Column( + // mainAxisAlignment: MainAxisAlignment.start, + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // BreadCrumbs(true, breadCrumbs: [ + // BreadCrumb(S.of(context).downloads, null) + // ],), + // Expanded(child: view), + // ], + // ); + return view; } List _getApps() { diff --git a/lib/widgets/desktop/desktop_edit_address.dart b/lib/widgets/desktop/desktop_edit_address.dart new file mode 100644 index 0000000..8da8630 --- /dev/null +++ b/lib/widgets/desktop/desktop_edit_address.dart @@ -0,0 +1,589 @@ + + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:gender_selection/gender_selection.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/address.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopEditAddress extends StatefulWidget { + final Key key; + final Address address; + final int businessId; + const DesktopEditAddress(this.address, {this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + State createState() { + return DesktopEditAddressState(); + } + +} + +class DesktopEditAddressState extends State { + final GlobalKey _formKey = new GlobalKey(); + + final contactNameController = TextEditingController(); + final phoneController = TextEditingController(); + final streetLine1Controller = TextEditingController(); + final streetLine2Controller = TextEditingController(); + final cityController = TextEditingController(); + final postalCodeController = TextEditingController(); + final emailController = TextEditingController(); + final faxController = TextEditingController(); + + String country; + Gender _selectedGender; + + String _selectedProvince; + + bool showLoading; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (showLoading) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + Widget form = SingleChildScrollView( + padding: EdgeInsets.only( + left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: contactNameController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .contact_name, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .contact_name_is_required; + } + return null; + }, + ), + ), + GenderSelection( + maleText: S + .of(context) + .mr, + femaleText: S + .of(context) + .ms, + selectedGenderIconBackgroundColor: Colors.indigo, + checkIconAlignment: Alignment.bottomRight, + selectedGenderCheckIcon: Icons.check, + onChanged: (Gender gender) { + _selectedGender = gender; + }, + equallyAligned: true, + animationDuration: Duration(milliseconds: 400), + isCircular: true, + isSelectedGenderIconCircular: true, + opacityOfGradient: 0.6, + padding: const EdgeInsets.all(3.0), + size: 50, + selectedGender: _selectedGender, + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + keyboardType: TextInputType.phone, + controller: phoneController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .mobile_phone_number, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .mobile_phone_number_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine1Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .street_line_1, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .street_line_1_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine2Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .street_line_2, + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: cityController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .city, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .city_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: DropdownButton( + value: _selectedProvince, + items: [ + 'Ontario', + 'Quebec', + 'British Columbia', + 'Alberta', + 'Manitoba', + 'Saskatchewan', + 'Nova Scotia', + 'New Brunswich', + 'Newfoundland and Labrador', + 'Prince Edward Island', + 'Northwest Territories', + 'Nunavut', + 'Yukon', + ].map((value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + _selectedProvince = newValue; + }); + } + }, + hint: Text(S + .of(context) + .province), + isExpanded: true, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: postalCodeController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .postal_code, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .postal_code_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 32.0, bottom: 0.0), + child: Text( + S + .of(context) + .optional_information, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 0.0, bottom: 0.0), + child: TextFormField( + controller: emailController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .email, + ), + validator: (String value) { + if (value.isNotEmpty && !EmailValidator.validate(value)) { + return S + .of(context) + .email_is_not_valid; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: TextFormField( + controller: faxController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .fax, + ), + ), + ), + ], + ), + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).edit_address, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).edit_address, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Form( + key: _formKey, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: form, + ), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: Container( + padding: EdgeInsets.all(8.0), + color: Theme + .of(context) + .buttonColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlatButton( + child: Text( + S + .of(context) + .delete, + ), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S + .of(context) + .warning), + content: Text(S + .of(context) + .are_you_sure_to_delete_the_address), + actions: [ + FlatButton( + child: Text(S + .of(context) + .cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S + .of(context) + .yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _deleteAddress(); + }, + ) + ], + ); + } + ); + }, + ), + FlatButton( + child: Text( + S + .of(context) + .save + ), + onPressed: () { + _saveEditAddress(); + }, + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + showLoading = false; + _selectedProvince = widget.address.state; + cityController.text = widget.address.city; + postalCodeController.text = widget.address.zip; + streetLine1Controller.text = widget.address.addressLine1; + streetLine2Controller.text = widget.address.addressLine2; + _selectedGender = widget.address.gender == 1 ? Gender.Male : Gender.Female; + country = widget.address.country; + contactNameController.text = widget.address.contactName; + phoneController.text = widget.address.phone; + emailController.text = widget.address.email; + faxController.text = widget.address.fax; + }); + } + + void _saveEditAddress() { + final FormState form = _formKey.currentState; + if (form.validate()) { + if (mounted) { + setState(() { + showLoading = true; + }); + } + HttpUtil.httpPatch('v1/addresses/${widget.address.id}', (response) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + eventBus.fire(OnAddressesUpdated()); + if (widget.businessId > 0) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + } else { + Navigator.of(context).pop(); + } + }, + body: { + 'id': widget.address.id, + 'name': contactNameController.text.trim(), + 'address_line1': streetLine1Controller.text.trim(), + 'address_line2': streetLine2Controller.text.trim(), + 'city': cityController.text.trim(), + 'state': _selectedProvince, + 'zip': postalCodeController.text.trim(), + 'phone': phoneController.text.trim(), + 'gender': _selectedGender == Gender.Male ? true : false, + 'email': emailController.text, + 'fax': faxController.text, + 'country': country, + } + ).catchError((error) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } + } + + void _deleteAddress() { + if (mounted) { + setState(() { + showLoading = true; + }); + } + HttpUtil.httpDelete('v1/addresses/${widget.address.id}', (response) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Fluttertoast.showToast( + msg: S.of(context).the_address_has_been_deleted, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + eventBus.fire(OnAddressesUpdated()); + Navigator.of(context).pop(); + }).catchError((error) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_forgot_password.dart b/lib/widgets/desktop/desktop_forgot_password.dart new file mode 100644 index 0000000..06c8989 --- /dev/null +++ b/lib/widgets/desktop/desktop_forgot_password.dart @@ -0,0 +1,380 @@ + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopForgotPassword extends StatefulWidget { + const DesktopForgotPassword({Key key}) : super(key: key); + + @override + State createState() { + return DesktopForgotPasswordState(); + } + +} + +class DesktopForgotPasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget view = Column( + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 0.0), + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).mobile_or_email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_or_email_is_required; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 12.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).verify, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + return SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).forgot_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: Text( + S.of(context).forgot_password_description, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: view, + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Routes.router.navigateTo(context, '/reset-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true); + }, + queryParameters: { + 'action': 'forgot_password_verify_code' + }, + isFormData: true, + body: { + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.trim().isNotEmpty) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + }, + queryParameters: {'action': 'forgot_password'}, + body: { + 'mobile': usernameController.text.trim(), + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).enter_mobile_or_email, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_igoshow_learn_more.dart b/lib/widgets/desktop/desktop_igoshow_learn_more.dart new file mode 100644 index 0000000..28cc732 --- /dev/null +++ b/lib/widgets/desktop/desktop_igoshow_learn_more.dart @@ -0,0 +1,156 @@ + +import 'package:flutter/material.dart'; +import 'package:youtube_player_iframe/youtube_player_iframe.dart'; + +import '../../generated/l10n.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +class DesktopiGoShowLearnMore extends StatefulWidget { + final Map data; + const DesktopiGoShowLearnMore(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopiGoShowLearnMoreState(); + } + +} + +class DesktopiGoShowLearnMoreState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + Column col = Column( + children: [ + Util.showImage( + 'https:${widget.data['image-top']['image']}' + ), + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ); + List w = _getContent(); + Wrap wrap = Wrap( + children: [], + ); + for (int i = 0; i < w.length; i++) { + wrap.children.add(w[i]); + } + col.children.add(wrap); + + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: col, + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } + + @override + void initState() { + super.initState(); + } + + List _getContent() { + List widgets = []; + for (int i = 0; i < (widget.data['sections'] as List).length; i++) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + if ((widget.data['sections'] as List)[i]['image'] != null) { + col.children.add( + Container( + padding: EdgeInsets.only( + top: 10.0, left: 8.0, right: 8.0, bottom: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Util.showImage( + 'https:${(widget + .data['sections'] as List)[i]['image']['image']}', + fit: BoxFit.contain, + ), + ), + ), + ); + } else if (((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 0) { + YoutubePlayerController _controller = YoutubePlayerController( + initialVideoId: '${((widget.data['sections'] as List)[i]['youtube_videos'] as List)[0] as String}', + params: YoutubePlayerParams( + playlist: ((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 1 ? + ((widget.data['sections'] as List)[i]['youtube_videos'] as List).map((e) { + return '$e'; + }).toList() : [], // Defining custom playlist + startAt: Duration(seconds: 0), + showControls: true, + showFullscreenButton: false, + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only( + top: 10.0, left: 8.0, right: 8.0, bottom: 8.0, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: YoutubePlayerIFrame( + controller: _controller, + aspectRatio: 1.7778, + ), + ), + ), + ); + } + col.children.add(Container( + margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0), + child: Text( + '${(widget.data['sections'] as List)[i]['title']}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + color: Colors.black87, + ), + ), + )); + col.children.add(Container( + margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0), + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + '${(widget.data['sections'] as List)[i]['description']}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + )); + widgets.add(Container( + width: mainSpace / 2, + child: col, + )); + } + return widgets; + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_index_main_content_1.dart b/lib/widgets/desktop/desktop_index_main_content_1.dart index 221812f..92994e1 100644 --- a/lib/widgets/desktop/desktop_index_main_content_1.dart +++ b/lib/widgets/desktop/desktop_index_main_content_1.dart @@ -46,7 +46,7 @@ class DesktopIndexMainContent1State extends State { ), ), decoration: BoxDecoration( - color: Colors.lightGreen, + color: Color(0xFF4FB0C6), borderRadius: BorderRadius.circular(10), ), ), diff --git a/lib/widgets/desktop/desktop_index_main_content_2.dart b/lib/widgets/desktop/desktop_index_main_content_2.dart index 0f34d7b..3a03ad7 100644 --- a/lib/widgets/desktop/desktop_index_main_content_2.dart +++ b/lib/widgets/desktop/desktop_index_main_content_2.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/generated/l10n.dart'; +import '../../generated/l10n.dart'; +import '../../widgets/general/text_link.dart'; import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; class DesktopIndexMainContent2 extends StatefulWidget { @@ -79,7 +80,7 @@ class DesktopIndexMainContent2State extends State { Container( padding: EdgeInsets.symmetric(horizontal: 10.0), child: Util.showImage( - 'http:${widget.content['minipos_image']['image']}', + 'https:${widget.content['minipos_image']['image']}', fit: BoxFit.fitWidth ), ), @@ -126,7 +127,7 @@ class DesktopIndexMainContent2State extends State { Container( padding: EdgeInsets.symmetric(horizontal: 10.0), child: Util.showImage( - 'http:${widget.content['igoshow_image']['image']}', + 'https:${widget.content['igoshow_image']['image']}', fit: BoxFit.fitWidth ), ), @@ -175,16 +176,12 @@ class DesktopIndexMainContent2State extends State { )); } col.children.add( - GestureDetector( - child: Container( - padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0), - child: Text( - S.of(context).learn_more, - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ), + Container( + alignment: Alignment.centerRight, + child: TextLink( + S.of(context).learn_more, + '/minipos-learn-more', + fontWeight: FontWeight.bold, ), ), ); @@ -225,16 +222,12 @@ class DesktopIndexMainContent2State extends State { )); } col.children.add( - GestureDetector( - child: Container( - padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0), - child: Text( - S.of(context).learn_more, - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ), + Container( + alignment: Alignment.centerRight, + child: TextLink( + S.of(context).learn_more, + '/igoshow-learn-more', + fontWeight: FontWeight.bold, ), ), ); diff --git a/lib/widgets/desktop/desktop_index_main_content_3.dart b/lib/widgets/desktop/desktop_index_main_content_3.dart index 6f53b41..d9bde1f 100644 --- a/lib/widgets/desktop/desktop_index_main_content_3.dart +++ b/lib/widgets/desktop/desktop_index_main_content_3.dart @@ -34,148 +34,325 @@ class DesktopIndexMainContent3State extends State { return Container( width: MediaQuery.of(context).size.width, margin: EdgeInsets.only(top: 20.0), - padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0), + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 0.0), decoration: BoxDecoration( color: Color(0xff262626), ), - child: Column( + child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - child: Text( - S.of(context).information, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - color: Colors.white70, - ), - ), + width: sideSpace, ), Container( - child: Row( - children: [ - TextLink(S.of(context).service_policy, '/service_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).return_policy, '/return_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).privacy_policy, '/privacy_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).license_agreement, '/license_agreement', paddingVertical: 5.0, paddingHorizontal: 10.0,) - ], - ), + width: mainSpace, + child: _getBody2(), ), Container( - margin: EdgeInsets.only(top: 12.0), - child: Text( - S.of(context).support, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - color: Colors.white70, - ), - ), - ), - Container( - child: Row( - children: [ - TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).support_ticket, '/support_ticket', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).contact_us, '/contact_us', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).about_us, '/about_us', paddingVertical: 5.0, paddingHorizontal: 10.0,), - TextLink(S.of(context).renew_license, '/renew_license', paddingVertical: 5.0, paddingHorizontal: 10.0,) - ], - ), - ), - Container( - margin: EdgeInsets.only(top: 12.0), - child: Text( - S.of(context).developer_of, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - color: Colors.white70, - ), - ), - ), - Container( - child: Row( - children: [ - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_GOOGLE, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_ALEXA, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_APPLE, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_EBAY, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_QUICKBOOKS, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - Container( - padding: EdgeInsets.all(10.0), - child: Icon( - IconData( - Constants.FONT_SHOPIFY, - fontFamily: 'wisetronic', - fontPackage: null - ), - color: Colors.white24, - size: 48.0, - ), - ), - ], - ), + width: sideSpace, ), ], ), ); } + Column _getBody() { + Column col; + col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).information, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + Container( + child: Row( + children: [ + TextLink(S.of(context).service_policy, '/service-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).return_policy, '/return-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).privacy_policy, '/privacy-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).license_agreement, '/end-user-license-agreement', paddingVertical: 5.0, paddingHorizontal: 10.0,) + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + child: Text( + S.of(context).support, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + Container( + child: Row( + children: [ + TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 10.0,), + TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 10.0,) + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + child: Text( + S.of(context).developer_of, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + Container( + child: Row( + children: [ + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_GOOGLE, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_ALEXA, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_APPLE, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_EBAY, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_QUICKBOOKS, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_SHOPIFY, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + ], + ), + ), + ], + ); + return col; + } + + Row _getBody2() { + Row row; + row = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 10 * 3, + padding: EdgeInsets.only(left: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).information, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + TextLink(S.of(context).service_policy, '/service-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).return_policy, '/return-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).privacy_policy, '/privacy-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).license_agreement, '/end-user-license-agreement', paddingVertical: 5.0, paddingHorizontal: 0.0,) + ], + ), + ), + Container( + width: mainSpace / 10 * 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 0.0), + child: Text( + S.of(context).support, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 0.0,), + TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 0.0,) + ], + ), + ), + Container( + width: mainSpace / 10 * 4, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 0.0), + child: Text( + S.of(context).developer_of, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: Colors.white70, + ), + ), + ), + Wrap( + children: [ + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_GOOGLE, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_ALEXA, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_APPLE, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_EBAY, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_QUICKBOOKS, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + Container( + padding: EdgeInsets.all(10.0), + child: Icon( + IconData( + Constants.FONT_SHOPIFY, + fontFamily: 'wisetronic', + fontPackage: null + ), + color: Colors.white24, + size: 48.0, + ), + ), + ], + ), + ], + ), + ), + ], + ); + return row; + } + } \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_login.dart b/lib/widgets/desktop/desktop_login.dart new file mode 100644 index 0000000..d850323 --- /dev/null +++ b/lib/widgets/desktop/desktop_login.dart @@ -0,0 +1,336 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopLogin extends StatefulWidget { + final Key key; + + const DesktopLogin({this.key}) : super(key: key); + + @override + State createState() { + return DesktopLoginState(); + } +} + +class DesktopLoginState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + final passwordController = TextEditingController(); + bool passwordVisible; + + bool onSubmitting; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + passwordVisible = true; + onSubmitting = false; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (onSubmitting) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: + EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Text( + S.of(context).please_login, + style: TextStyle(fontSize: 24.0, color: Colors.black), + ), + ), + Container( + padding: EdgeInsets.only( + top: 12.0, left: 16.0, right: 16.0, bottom: 12.0), + child: Text( + S.of(context).login_instruction, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: Column( + children: [ + Form( + key: _formKey, + child: Column( + children: [ + Container( + padding: EdgeInsets.only( + top: 16.0, + left: 16.0, + right: 16.0, + bottom: 0.0), + child: TextFormField( + controller: usernameController, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: + S.of(context).mobile_email_username, + ), + style: TextStyle(fontSize: 18.0), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + top: 0.0, + left: 16.0, + right: 16.0, + bottom: 5.0), + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible + ? Icons.visibility_off + : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle(fontSize: 18.0), + obscureText: passwordVisible, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric( + vertical: 12.0, horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlatButton( + child: Text( + S.of(context).new_user_question, + style: TextStyle( + fontSize: 14.0, + ), + ), + onPressed: () { + newUser(); + }, + ), + Expanded( + child: Text(''), + ), + Container( + child: FlatButton( + child: Text( + S.of(context).forgot_password_question, + style: TextStyle( + fontSize: 14.0, + ), + ), + onPressed: () { + forgotPassword(); + }, + ), + ), + ], + ), + ), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only( + top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).login, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + signIn(); + }, + ), + ), + ), + ], + ), + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } + + void newUser() { + Routes.router.navigateTo(context, '/new-user'); + } + + void forgotPassword() { + Routes.router.navigateTo(context, '/forgot-password'); + } + + void signIn() { + final FormState form = _formKey.currentState; + if (form.validate()) { + if (mounted) { + setState(() { + onSubmitting = true; + }); + } + + HttpUtil.httpPost( + 'v1/oauth-wisetronic/access_token', + (response) { + User user = User.fromJson(response.data['user']); + store.dispatch(UpdateCurrentUser(user)); + Utils.getBox().then((box) { + box.put(Constants.KEY_USER_ID, response.data['user_id']); + box.put(Constants.KEY_ACCESS_TOKEN, response.data['access_token']); + if (store.state.redirectRoute != null) { + Routes.router.navigateTo(context, store.state.redirectRoute, + replace: true); + store.dispatch(UpdateRedirectRoute(null)); + } else { + Routes.router + .navigateTo(context, '/me', replace: true, clearStack: false); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + }, + queryParameters: { + 'client_id': '${Utils.getPlatformName()}', + 'grant_type': 'password' + }, + body: { + 'username': usernameController.text.trim(), + 'password': passwordController.text.trim(), + 'fcm_token': store.state.fcmToken != null ? store.state.fcmToken : '' + }, + isFormData: true, + businessId: Constants.BUSINESS_ID, + ).catchError((error) { + if (mounted) { + setState(() { + onSubmitting = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } + } +} diff --git a/lib/widgets/desktop/desktop_me.dart b/lib/widgets/desktop/desktop_me.dart new file mode 100644 index 0000000..43df18e --- /dev/null +++ b/lib/widgets/desktop/desktop_me.dart @@ -0,0 +1,501 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + +MediaQueryData mediaQuery; +double statusBarHeight; +double screenHeight; + +class DesktopMe extends StatefulWidget { + final Key key; + + const DesktopMe({this.key}) : super(key: key); + + @override + State createState() { + return DesktopMeState(); + } +} + +class DesktopMeState extends State { + int userId; + String accessToken; + + bool isLoading; + User _user; + + double sideSpace = 0; + double mainSpace = 1200; + + double division = 3; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (isLoading) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + mediaQuery ??= MediaQuery.of(context); + screenHeight ??= mediaQuery.size.height; + statusBarHeight ??= mediaQuery.padding.top; + + Widget userInfo = GestureDetector( + child: Container( + padding: + EdgeInsets.only(left: 16.0, right: 16.0, top: 12.0, bottom: 5.0), + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(right: 5.0), + child: _user != null && _user.avatarUrl.isNotEmpty + ? Util.showImage( + 'https:${_user.avatarUrl}', + width: 60, + height: 60, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon( + Icons.account_circle, + size: 60.0, + color: Colors.blue, + ), + ) + : Icon( + Icons.person_outline, + size: 60.0, + color: Colors.black38, + ), + ), + Container( + child: Text( + _user != null ? _user.nickname : S.of(context).please_login, + style: TextStyle( + color: Colors.black38, + fontSize: 18.0, + ), + ), + ), + Visibility( + visible: _user != null, + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.phone_iphone, + color: Colors.blue, + size: 20.0, + ), + ), + Container( + child: Text( + _user != null + ? Utils.safePhoneNumber(_user.mobile) + : '', + style: TextStyle( + color: Colors.black38, + fontSize: 14.0, + ), + ), + ) + ], + ), + ), + ), + ], + ), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo(context, '/user-profile'); + } else { + Routes.router.navigateTo(context, '/login'); + } + }, + ); + + Widget widget = SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: userInfo, + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, + bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null + ? '${_user.wallet.toStringAsFixed(2)}' + : '0.00', + style: TextStyle( + fontSize: 24.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).wallet, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + right: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, + bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null ? '${_user.coupon}' : '0', + style: TextStyle( + fontSize: 24.0, + color: Colors.orangeAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).red_coupon, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + right: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo( + context, '/coupons/${_user.id}'); + } else { + _pleaseLoginToast(); + } + }, + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, + bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null ? '${_user.points}' : '0', + style: TextStyle( + fontSize: 24.0, + color: Colors.lightGreen, + ), + ), + ), + Container( + child: Text( + S.of(context).point, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + ), + ), + ), + ), + ], + ), + Wrap( + children: [ + getLinkItem( + mainSpace / division, + 200.0, + Icon( + Icons.lock, + size: 60.0, + color: Colors.lightBlueAccent, + ), + Text(S.of(context).change_password), + () { + if (_user != null) { + Routes.router.navigateTo( + context, + '/change-password', + ); + } else { + _pleaseLoginToast(); + } + }, + ), + getLinkItem( + mainSpace / division, + 200.0, + Icon( + Icons.sticky_note_2_outlined, + size: 60.0, + color: Colors.lightBlueAccent, + ), + Text(S.of(context).my_orders), + () { + if (_user != null) { + Routes.router.navigateTo(context, '/orders'); + } else { + _pleaseLoginToast(); + } + }, + ), + getLinkItem( + mainSpace / division, + 200.0, + Icon( + Icons.location_on, + size: 60.0, + color: Colors.lightBlueAccent, + ), + Text(S.of(context).my_addresses), + () { + if (_user != null) { + Routes.router + .navigateTo(context, '/my-addresses/-1'); + } else { + _pleaseLoginToast(); + } + }, + ), + getLinkItem( + mainSpace / division, + 200.0, + Icon( + Icons.credit_card, + size: 60.0, + color: Colors.lightBlueAccent, + ), + Text(S.of(context).my_cards), + () { + if (_user != null) { + Routes.router.navigateTo(context, '/my-cards'); + } else { + _pleaseLoginToast(); + } + }, + ), + getLinkItem( + mainSpace / division, + 200.0, + Icon( + Icons.support_agent, + size: 60.0, + color: Colors.lightBlueAccent, + ), + Text(S.of(context).my_support), + () { + if (_user != null) { + Routes.router.navigateTo(context, + '/my-support/${Constants.BUSINESS_ID}'); + } else { + _pleaseLoginToast(); + } + }, + ), + ], + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ], + ), + ); + + return widget; + } + + @override + void initState() { + super.initState(); + setState(() { + isLoading = true; + _user = null; + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + isLoading = false; + _user = store.state.user; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + isLoading = false; + _user = null; + }); + } + }); + Utils.getCurrentUser(); + } + + void _toLogin() { + Routes.router + .navigateTo(context, '/login', replace: true, clearStack: true); + } + + _pleaseLoginToast() { + Fluttertoast.showToast( + msg: S.of(context).please_login, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white); + } + + Widget getLinkItem( + double width, double height, Icon icon, Text label, Function tap) { + Widget widget = MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + width: width, + height: height, + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: icon, + ), + Container( + child: label, + ), + ], + ), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: Colors.black12, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + ), + ), + onTap: tap, + ), + ); + return widget; + } +} diff --git a/lib/widgets/desktop/desktop_minipos_learn_more.dart b/lib/widgets/desktop/desktop_minipos_learn_more.dart new file mode 100644 index 0000000..2e3c979 --- /dev/null +++ b/lib/widgets/desktop/desktop_minipos_learn_more.dart @@ -0,0 +1,124 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +class DesktopMiniPosLearnMore extends StatefulWidget { + final Map data; + const DesktopMiniPosLearnMore(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopMiniPosLearnMoreState(); + } + +} + +class DesktopMiniPosLearnMoreState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + Column col = Column( + children: [ + Util.showImage( + 'https:${widget.data['image-top']['image']}' + ), + ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + ); + List w = _getContent(); + Wrap wrap = Wrap( + children: [], + ); + for (int i = 0; i < w.length; i++) { + wrap.children.add(w[i]); + } + col.children.add(wrap); + + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: col, + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } + + @override + void initState() { + super.initState(); + } + + List _getContent() { + List widgets = []; + for (int i = 0; i < (widget.data['sections'] as List).length; i++) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + col.children.add( + Container( + padding: EdgeInsets.only(top: 10.0, left: 8.0, right: 8.0, bottom: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Util.showImage( + 'https:${(widget.data['sections'] as List)[i]['image']['image']}', + fit: BoxFit.contain, + ), + ), + ), + ); + col.children.add(Container( + margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0), + child: Text( + '${(widget.data['sections'] as List)[i]['title']}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + color: Colors.black87, + ), + ), + )); + col.children.add(Container( + margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0), + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + '${(widget.data['sections'] as List)[i]['description']}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + )); + widgets.add(Container( + width: mainSpace / 2, + child: col, + )); + } + return widgets; + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_my_addresses.dart b/lib/widgets/desktop/desktop_my_addresses.dart new file mode 100644 index 0000000..d57c442 --- /dev/null +++ b/lib/widgets/desktop/desktop_my_addresses.dart @@ -0,0 +1,269 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/address.dart'; +import '../../pages/edit_address.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopMyAddresses extends StatefulWidget { + final int businessId; + const DesktopMyAddresses({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return DesktopMyAddressesState(); + } + +} + +class DesktopMyAddressesState extends State { + List
addresses; + + double sideSpace = 0; + double mainSpace = 1200; + + double division = 2; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (addresses == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + BuildContext mainContext = context; + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).my_addresses, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).my_addresses, null), + BreadCrumb(S.of(context).add_new_address, + '/search-place/${widget.businessId}', + ), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.only( + top: 12.0, + bottom: 16.0, + left: 8.0, + right: 8.0, + ), + child: addresses == null ? Text('') : + (addresses.length > 0 ? + Wrap( + children: addresses.map((a) => _getAddress(a)).toList(), + ) : + Center( + child: Text(S.of(context).no_address_yet), + )), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget _getAddress(Address address) { + + return Container( + width: (mainSpace - 16.0) / division, + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ), + top: BorderSide( + color: Colors.black12, + width: 0.6, + ), + left: BorderSide( + color: Colors.black12, + width: 0.6, + ), + right: BorderSide( + color: Colors.black12, + width: 0.6, + ), + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + address.addressLine1, + style: TextStyle( + fontSize: 19.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + address.addressLine2, + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Text( + '${address.contactName} ${Utils.safePhoneNumber(address.phone)}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + ), + ) + ], + ), + ), + Container( + child: widget.businessId == 0 ? + IconButton( + icon: Icon( + Icons.edit, + color: Colors.grey, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => new EditAddress(address, businessId: 0,), + )); + }, + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 5.0), + child: IconButton( + icon: Icon( + Icons.edit, + color: Colors.grey, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => new EditAddress(address, businessId: widget.businessId,), + )); + }, + ), + ), + Container( + child: widget.businessId > 0 ? IconButton( + icon: Icon( + Icons.check, + color: Colors.grey, + ), + onPressed: () { + HttpUtil.httpPut('v1/select-address/${address.id}', (response) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + }, + ) : SizedBox.shrink(), + ) + ], + ), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + loadAddresses(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + addresses = null; + }); + } + loadAddresses(); + }); + } + + void loadAddresses() { + HttpUtil.httpGet( + 'v1/addresses', + ).then((value) { + if (mounted) { + setState(() { + addresses = (value as List).map((e) => Address.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_my_cards.dart b/lib/widgets/desktop/desktop_my_cards.dart new file mode 100644 index 0000000..4e91346 --- /dev/null +++ b/lib/widgets/desktop/desktop_my_cards.dart @@ -0,0 +1,208 @@ + + +import 'package:flutter/material.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../models/user.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopMyCards extends StatefulWidget { + final Key key; + const DesktopMyCards({this.key}); + + @override + State createState() { + return MyCardsState(); + } + +} + +class MyCardsState extends State { + User _user; + + bool isSubmitting; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).my_cards, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: ListView.builder( + itemCount: _user.stripePaymentMethods.length, + itemBuilder: (BuildContext context, int position) { + StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position]; + return cardWidget(paymentMethod); + } + ), + ), + Container( + width: sideSpace, + ), + ], + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget cardWidget(StripePaymentMethod paymentMethod) { + return Container( + padding: EdgeInsets.only( + top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + paymentMethod.cardBrand == 'visa' ? + Image.asset( + 'assets/images/visa.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : (paymentMethod.cardBrand == 'mastercard' ? + Image.asset( + 'assets/images/master.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : Icon( + Icons.credit_card, size: 50.0, color: Colors.black38,)), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + '**** ${paymentMethod.cardLast4}', + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).expire_token(paymentMethod.cardExpMonth, paymentMethod.cardExpYear), + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + IconButton( + icon: Icon(Icons.clear, color: Colors.black26,), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text( + S.of(context).are_you_sure_to_remove_the_card, + ), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _removeCard(paymentMethod); + }, + ) + ], + ); + } + ); + }, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ); + } + + _removeCard(StripePaymentMethod paymentMethod) { + HttpUtil.httpDelete('v1/stripe-card/${paymentMethod.id}', (response) { + _user = User.fromJson(response.data); + store.dispatch(UpdateCurrentUser(_user)); + eventBus.fire(OnCurrentUserUpdated()); + if (mounted) { + Navigator.of(context).pop(); + setState(() { + isSubmitting = false; + }); + } + }).catchError((error) { + if (isSubmitting) { + Navigator.of(context).pop(); + isSubmitting = false; + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + @override + void initState() { + super.initState(); + setState(() { + isSubmitting = false; + _user = store.state.user; + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_my_support.dart b/lib/widgets/desktop/desktop_my_support.dart new file mode 100644 index 0000000..18b6e50 --- /dev/null +++ b/lib/widgets/desktop/desktop_my_support.dart @@ -0,0 +1,343 @@ + + +import 'package:flutter/material.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/ticket.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/double_back_to_close_app_wrapper.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopMySupport extends StatefulWidget { + final int businessId; + const DesktopMySupport({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return DesktopMySupportState(); + } + +} + +class DesktopMySupportState extends State { + List tickets; + + double division = 3; + + int _page = 1; + int _pageCount = 1; + + bool _isLoading = false; + bool _loadingFinish = false; + + RefreshController _refreshController = RefreshController(initialRefresh: true); + + void _onRefresh() { + _page = 1; + if (tickets != null) { + tickets.clear(); + } else { + tickets = []; + } + _refreshController.resetNoData(); + loadTicketes(true); + } + + void _onLoadMore() { + // if failed,use loadFailed(),if no data return,use LoadNodata() + if (_pageCount > _page) { + _page += 1; + loadTicketes(false); + } else { + _refreshController.loadNoData(); + } + } + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/my-support/${widget.businessId}'); + return; + } + }); + + BuildContext mainContext = context; + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).my_support, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).my_support, null), + BreadCrumb(S.of(context).add_new_ticket, + '/new-ticket/${widget.businessId}', + icon: Icons.add, + ), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: DoubleBackToCloseAppWrapper( + child: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode){ + Widget footer; + if(mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center(child: footer,), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoadMore, + child: _buildBody(), + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget _buildBody() { + if (tickets == null) { + return SizedBox.shrink(); + } + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.only( + top: 12.0, + bottom: 16.0, + left: 8.0, + right: 8.0, + ), + child: tickets == null ? Text('') : + (tickets.length > 0 ? + Wrap( + children: tickets.map((a) => _getTicket(a)).toList(), + ) : + Center( + child: Text(S.of(context).no_ticket_yet), + )), + ), + Container( + width: sideSpace, + ), + ], + ); + return SingleChildScrollView( + child: row, + ); + } + + @override + void dispose() { + _refreshController?.dispose(); + super.dispose(); + } + + Widget _getTicket(Ticket ticket) { + return Container( + width: (mainSpace - 16.0) / division, + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ), + top: BorderSide( + color: Colors.black12, + width: 0.6, + ), + left: BorderSide( + color: Colors.black12, + width: 0.6, + ), + right: BorderSide( + color: Colors.black12, + width: 0.6, + ), + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + ticket.issue.msg, + style: TextStyle( + fontSize: 19.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt, withTime: true), + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ticket.isClosed ? + Container( + padding: EdgeInsets.only(right: 10.0), + child: Icon(Icons.lock, color: Colors.green, size: 16.0,), + ) : + SizedBox.shrink(), + Expanded( + child: Text( + S.of(context).followups_token(ticket.followUps.length), + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ) + ], + ), + ), + Container( + child: IconButton( + icon: Icon( + Icons.remove_red_eye, + color: Colors.grey, + ), + onPressed: () { + Routes.router.navigateTo(context, '/view-ticket/${ticket.id}'); + }, + ), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + tickets = null; + }); + } + _refreshController.requestRefresh(); + }); + } + + void loadTicketes(bool isRefresh) { + HttpUtil.httpGet( + 'v1/mysupport', + businessId: widget.businessId, + queryParameters: { + 'page': _page.toString(), + 'size': Constants.TICKET_PER_PAGE_DESKTOP.toString(), + } + ).then((value) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshCompleted(); + } else { + _refreshController.loadComplete(); + } + + if (int.parse(value['_meta']['currentPage'].toString()) >= int.parse(value['_meta']['pageCount'].toString())) { + _loadingFinish = true; + } + if (_loadingFinish) { + _refreshController.loadNoData(); + } + _page = int.parse(value['_meta']['currentPage'].toString()); + _pageCount = int.parse(value['_meta']['pageCount'].toString()); + setState(() { + if (tickets == null) { + tickets = []; + } + tickets.addAll((value['tickets'] as List).map((e) => Ticket.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshFailed(); + } else { + _refreshController.loadFailed(); + } + _isLoading = false; + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_navigationbar.dart b/lib/widgets/desktop/desktop_navigationbar.dart index 9d3df95..71cf412 100644 --- a/lib/widgets/desktop/desktop_navigationbar.dart +++ b/lib/widgets/desktop/desktop_navigationbar.dart @@ -1,12 +1,29 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/generated/l10n.dart'; -import 'package:flutter_wisetronic/widgets/general/navigationbar_logo.dart'; -import 'package:flutter_wisetronic/widgets/general/text_link.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_appbar_menu.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +import '../../constants.dart'; +import '../../dialog/logout_dialog.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/navigationbar_logo.dart'; +import '../../widgets/general/text_link.dart'; class DesktopNavigationBar extends StatefulWidget { - const DesktopNavigationBar({Key key}) : super(key: key); + final bool hasBack; + final List breadCrumbs; + final Widget shoppingCart; + + const DesktopNavigationBar({Key key, this.hasBack, this.breadCrumbs, + this.shoppingCart}) : super(key: key); @override State createState() { @@ -16,92 +33,201 @@ class DesktopNavigationBar extends StatefulWidget { } class DesktopNavigationBarState extends State { + User _user; @override Widget build(BuildContext context) { - String currentRoute = ModalRoute.of(context).settings.name; - return Stack( - children: [ - Container( - color: Colors.blue, - height: 80.0, - ), - Container( - height: 80.0, - padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - NavigationBarLogo(), - Expanded( - child: Container( - alignment: Alignment.centerRight, - padding: EdgeInsets.only(left: 8.0, right: 16.0), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 20.0,), - TextLink( - S.of(context).home, - '/', - color: Colors.white, - selected: currentRoute == '/', - clearStack: true, - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).download, - '/download', - color: Colors.white, - selected: currentRoute == '/download', - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).tutorials, - '/tutorials', - color: Colors.white, - selected: currentRoute == '/tutorials', - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).support, - '/support', - color: Colors.white, - selected: currentRoute == '/support', - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).shop, - '/shop', - color: Colors.white, - selected: currentRoute == '/shop', - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).blog, - '/blog', - color: Colors.white, - selected: currentRoute == '/blog', - ), - SizedBox(width: 15.0,), - TextLink( - S.of(context).login, - '/login', - color: Colors.white, - selected: currentRoute == '/login', - ), - ], - ), - ), - ), + // String currentRoute = ModalRoute.of(context).settings.name; + // Stack stack = Stack( + // children: [ + // Container( + // color: Colors.blue, + // height: 80.0, + // ), + // Container( + // height: 80.0, + // padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // NavigationBarLogo(), + // Expanded( + // child: Container( + // alignment: Alignment.centerRight, + // padding: EdgeInsets.only(left: 8.0, right: 16.0), + // child: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // SizedBox(width: 20.0,), + // TextLink( + // S.of(context).home, + // '/', + // color: Colors.white, + // selected: currentRoute == '/', + // clearStack: true, + // ), + // SizedBox(width: 15.0,), + // TextLink( + // S.of(context).download, + // '/download', + // color: Colors.white, + // selected: currentRoute == '/download', + // ), + // SizedBox(width: 15.0,), + // TextLink( + // S.of(context).tutorials, + // '/tutorials', + // color: Colors.white, + // selected: currentRoute == '/tutorials', + // ), + // SizedBox(width: 15.0,), + // TextLink( + // S.of(context).support, + // '/my-support/${Constants.BUSINESS_ID}', + // color: Colors.white, + // selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}', + // ), + // SizedBox(width: 15.0,), + // TextLink( + // S.of(context).shop, + // '/shop', + // color: Colors.white, + // selected: currentRoute == '/shop', + // ), + // SizedBox(width: 15.0,), + // TextLink( + // S.of(context).blog, + // '/blog/${Constants.BUSINESS_ID}', + // color: Colors.white, + // selected: currentRoute == '/blog/${Constants.BUSINESS_ID}', + // ), + // SizedBox(width: 15.0,), + // _user != null ? + // (currentRoute == '/me') ? + // MouseRegion( + // cursor: SystemMouseCursors.click, + // child: GestureDetector( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.start, + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Container( + // padding: EdgeInsets.only(right: 4.0), + // child: Text( + // S.of(context).logout, + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // ), + // Icon( + // Icons.logout, + // color: Colors.white, + // size: 16.0, + // ) + // ], + // ), + // onTap: () { + // showDialog( + // context: context, + // builder: (BuildContext context) { + // return logoutDialog(context); + // } + // ); + // }, + // ), + // ) : IconButton( + // icon: Icon( + // Icons.account_circle, + // color: Colors.white, + // ), + // onPressed: () { + // Routes.router.navigateTo(context, '/me'); + // }, + // ) : + // TextLink( + // S.of(context).login, + // '/login', + // color: Colors.white, + // selected: currentRoute == '/login', + // ), + // ], + // ), + // ), + // ), + // ), + // ], + // ), + // ), + // ], + // ); + Widget sc = SizedBox.shrink(); + if (widget.shoppingCart != null) { + sc = widget.shoppingCart; + } + Widget breadCrumbBar = SizedBox.shrink(); + if (widget.breadCrumbs != null && widget.breadCrumbs.length > 0) { + breadCrumbBar = Container( + width: MediaQuery.of(context).size.width, + height: 38.0, + color: Color(0xFF4FB0C6), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: BreadCrumbs( + widget.hasBack ?? false, + breadCrumbs: widget.breadCrumbs, ), - ], - ), + ), + sc, + Container( + width: 1.0, + ), + ], ), + ); + } + // stack.children.add( + // Positioned( + // top: 90.0, + // child: breadCrumbBar, + // ), + // ); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DesktopAppBarMenu(), + breadCrumbBar, ], ); } + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = null; + }); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Utils.getCurrentUser(); + }); + } } \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_new_address.dart b/lib/widgets/desktop/desktop_new_address.dart new file mode 100644 index 0000000..a43b79c --- /dev/null +++ b/lib/widgets/desktop/desktop_new_address.dart @@ -0,0 +1,424 @@ + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:gender_selection/gender_selection.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/located_address.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopNewAddress extends StatefulWidget { + final Key key; + final LocatedAddress locatedAddress; + final int businessId; + const DesktopNewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key); + + @override + State createState() { + return DesktopNewAddressState(); + } + +} + +class DesktopNewAddressState extends State { + final GlobalKey _formKey = new GlobalKey(); + + final contactNameController = TextEditingController(); + final phoneController = TextEditingController(); + final streetLine1Controller = TextEditingController(); + final streetLine2Controller = TextEditingController(); + final cityController = TextEditingController(); + final postalCodeController = TextEditingController(); + final emailController = TextEditingController(); + final faxController = TextEditingController(); + + String country = 'CA'; + Gender _selectedGender; + + String _selectedProvince; + + double sideSpace = 0; + double mainSpace = 1200; + + List provinces = [ + 'Ontario', + 'Quebec', + 'British Columbia', + 'Alberta', + 'Manitoba', + 'Saskatchewan', + 'Nova Scotia', + 'New Brunswich', + 'Newfoundland and Labrador', + 'Prince Edward Island', + 'Northwest Territories', + 'Nunavut', + 'Yukon', + ]; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget form = SingleChildScrollView( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: contactNameController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).contact_name, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).contact_name_is_required; + } + return null; + }, + ), + ), + GenderSelection( + maleText: S.of(context).mr, + femaleText: S.of(context).ms, + selectedGenderIconBackgroundColor: Colors.indigo, + checkIconAlignment: Alignment.bottomRight, + selectedGenderCheckIcon: Icons.check, + onChanged: (Gender gender) { + _selectedGender = gender; + }, + equallyAligned: true, + animationDuration: Duration(milliseconds: 400), + isCircular: true, + isSelectedGenderIconCircular: true, + opacityOfGradient: 0.6, + padding: const EdgeInsets.all(3.0), + size: 50, + selectedGender: _selectedGender, + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + keyboardType: TextInputType.phone, + controller: phoneController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).mobile_phone_number, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_phone_number_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine1Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).street_line_1, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).street_line_1_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine2Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).street_line_2, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: cityController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).city, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).city_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: DropdownButton( + value: _selectedProvince, + items: provinces.map((value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + _selectedProvince = newValue; + }); + } + }, + hint: Text(S.of(context).province), + isExpanded: true, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: postalCodeController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).postal_code, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).postal_code_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 32.0, bottom: 0.0), + child: Text( + S.of(context).optional_information, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 0.0), + child: TextFormField( + controller: emailController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).email, + ), + validator: (String value) { + if (value.isNotEmpty && !EmailValidator.validate(value)) { + return S.of(context).email_is_not_valid; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: TextFormField( + controller: faxController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).fax, + ), + ), + ), + ], + ), + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).new_address, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).new_address, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Form( + key: _formKey, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: form, + ), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: Container( + padding: EdgeInsets.all(8.0), + color: Theme.of(context).buttonColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + FlatButton( + child: Text( + S.of(context).save + ), + onPressed: () { + _saveNewAddress(); + }, + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + if (widget.locatedAddress != null) { + if (provinces.contains(widget.locatedAddress.province)) { + _selectedProvince = widget.locatedAddress.province; + } + cityController.text = widget.locatedAddress.city; + postalCodeController.text = widget.locatedAddress.postalCode; + streetLine1Controller.text = (widget.locatedAddress.streetNumber != null + && widget.locatedAddress.streetNumber.isNotEmpty + ? widget.locatedAddress.streetNumber + ' ' : '') + + widget.locatedAddress.streetName; + } else { + _selectedProvince = 'Ontario'; + } + streetLine2Controller.text = ''; + _selectedGender = Gender.Male; + }); + } + + void _saveNewAddress() { + final FormState form = _formKey.currentState; + if (form.validate()) { + + HttpUtil.httpPost('v1/addresses', (response) { + if (widget.businessId > 0) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + } else { + Routes.router.navigateTo(context, '/my-addresses/${widget.businessId}', replace: true); + } + }, + body: { + 'name': contactNameController.text.trim(), + 'address_line1': streetLine1Controller.text.trim(), + 'address_line2': streetLine2Controller.text.trim(), + 'city': cityController.text.trim(), + 'state': _selectedProvince, + 'zip': postalCodeController.text.trim(), + 'phone': phoneController.text.trim(), + 'gender': _selectedGender == Gender.Male ? 1 : 0, + 'email': emailController.text, + 'fax': faxController.text, + 'country': country, + } + ); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_new_comment.dart b/lib/widgets/desktop/desktop_new_comment.dart new file mode 100644 index 0000000..ba9629b --- /dev/null +++ b/lib/widgets/desktop/desktop_new_comment.dart @@ -0,0 +1,406 @@ + + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/general/navigationbar.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:smooth_star_rating/smooth_star_rating.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/comment.dart'; +import '../../models/product_image.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + + +class DesktopNewComment extends StatefulWidget { + final Key key; + final int orderId; + const DesktopNewComment(this.orderId, {this.key}); + + @override + State createState() { + return DesktopNewCommentState(); + } + +} + +class DesktopNewCommentState extends State { + Comment comment; + + bool _showProgress; + + double _progress; + + double rating; + + bool isSubmitting = false; + + final TextEditingController commentController = new TextEditingController(); + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).new_comment, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).pay_now, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: _buildBody(context), + ); + } + + @override + void initState() { + super.initState(); + _showProgress = false; + _progress = 0.0; + setState(() { + rating = 5.0; + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _showProgress = event.showProgress; + _progress = event.progress; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + comment = event.comment; + }); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + Widget _buildBody(BuildContext context) { + + ListView listView = ListView.builder( + itemCount: 4, + itemBuilder: (BuildContext context, int position) { + + switch(position) { + case 0: + return Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 16.0, bottom: 16.0), + child: SmoothStarRating( + allowHalfRating: false, + onRated: (v) { + setState(() { + rating = v; + }); + }, + starCount: 5, + rating: rating, + size: 40.0, + filledIconData: Icons.star, + color: Colors.green, + borderColor: Colors.green, + spacing: 0.0, + ), + ); + break; + case 1: + return Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 16.0, right: 16.0), + child: TextField( + controller: commentController, + keyboardType: TextInputType.multiline, + maxLines: 10, + maxLength: 500, + decoration: new InputDecoration( + border: new OutlineInputBorder( + borderRadius: const BorderRadius.all( + const Radius.circular(12.0), + ), + ), + filled: true, + hintStyle: new TextStyle(color: Colors.grey[800]), + hintText: S.of(context).input_your_comment, + fillColor: Colors.white70, + ), + ), + ); + break; + case 2: + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: commentImages(context), + ); + break; + case 3: + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: SizedBox( + width: 150.0, + height: 36.0, + child: RaisedButton( + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + color: Theme.of(context).primaryColor, + onPressed: () { + _submit(); + }, + ), + ), + ), + ); + } + return null; + } + ); + return Row( + children: [ + Container(width: sideSpace,), + Expanded(child: listView,), + Container(width: sideSpace,), + ], + ); + } + + Widget commentImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + + if (comment != null && comment.images.length > 0) { + for (ProductImage image in comment.images) { + row.children.add( + Container( + padding: EdgeInsets.only(left: 10.0), + child: Badge( + position: BadgePosition.topEnd(top: -10.0, end: -6.0), + badgeContent: Container( + child: Text( + '-', + style: TextStyle( + fontSize: 14.0, + color: Colors.white, + ), + ), + ), + child: GestureDetector( + child: Container( + child: Util.showImage('https:${image.image}', + width: 60.0, + height: 60.0, + fit: BoxFit.fill, + errorWidget: (mainContext, url, error) => Icon( + Icons.image, + size: 60.0, + color: Colors.grey, + ), + ), + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + color: Theme.of(context).primaryColor, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _deleteImage(image.id); + }, + ), + ], + ); + } + ); + }, + ), + ), + ), + ); + } + } + + row.children.add( + GestureDetector( + child: Container( + margin: EdgeInsets.only(left: 10,), + child: Icon( + Icons.add, + size: 60.0, + color: comment == null || comment.images.length < 3 ? Colors.lightBlue : Colors.black12, + ), + decoration: BoxDecoration( + color: Colors.white70, + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + if (comment == null || comment.images.length < 3) { + showDialog( + context: mainContext, + barrierDismissible: true, + builder: (BuildContext context) { + return Util().getPicture(mainContext, store.state.user, + commentId: comment != null ? comment.id : 0, + orderId: widget.orderId); + } + ); + } + }, + ), + ); + + return Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: -100.0, + child: Visibility( + visible: _showProgress, + child: LinearPercentIndicator( + lineHeight: 10.0, + percent: _progress, + backgroundColor: Colors.transparent, + progressColor: Colors.blue, + linearStrokeCap: LinearStrokeCap.butt, + ), + ), + ), + Positioned( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 16.0), + child: Text( + S.of(context).add_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black38, + ), + ), + ), + row, + ], + ), + ), + ], + ); + } + + void _deleteImage(int imageId) { + HttpUtil.httpDelete('v1/comment-image/${imageId}', (response) { + if (mounted) { + setState(() { + comment = Comment.fromJson(response.data); + }); + } + }, + queryParameters: { + 'comment_id': comment != null ? comment.id : 0, + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + + void _submit() { + if (commentController.text.isEmpty) { + Utils.showMessageDialog(context, Exception(S.of(context).comment_empty)); + } else { + HttpUtil.httpPost('v1/add-comment', (response) { + if (isSubmitting) { + isSubmitting = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, + Exception(S.of(context).thank_you_for_your_comment), + title: S.of(context).success, + onOk: () { + Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true); + } + ); + }, + isFormData: true, + body: { + 'order_id': widget.orderId, + 'comment_id': comment != null ? comment.id : 0, + 'content': commentController.text, + 'rating': rating.round(), + }, + ).catchError((error) { + if (isSubmitting) { + isSubmitting = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + }); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_new_ticket.dart b/lib/widgets/desktop/desktop_new_ticket.dart new file mode 100644 index 0000000..86d58de --- /dev/null +++ b/lib/widgets/desktop/desktop_new_ticket.dart @@ -0,0 +1,459 @@ + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:percent_indicator/circular_percent_indicator.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class DesktopNewTicket extends StatefulWidget { + final Key key; + final int businessId; + + const DesktopNewTicket({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + State createState() { + return DesktopNewTicketState(); + } +} + +class DesktopNewTicketState extends State { + final GlobalKey _formKey = GlobalKey(); + + final issueMsgController = TextEditingController(); + + bool onSubmitting = false; + double submitProgress = 0.0; + + final List> galleryImagesFlag = [ + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + ]; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + onSubmitting = false; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + BuildContext mainContext = context; + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Row row = Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Text( + S.of(context).add_new_ticket, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 12.0, left: 16.0, right: 16.0, bottom: 12.0), + child: Text( + S.of(context).add_new_ticket_desc, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: Column( + children: [ + Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: TextFormField( + controller: issueMsgController, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + decoration: new InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1.0, + color: Colors.blue, + ), + ), + labelText: S.of(context).your_question_issue, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + maxLines: 5, + maxLength: 1000, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0), + child: Text( + S.of(context).attach_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).attach_pictures_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ), + Container( + height: 120.0, + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: galleryImages(mainContext), + ), + ), + ], + ), + ), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).login, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ), + ], + ), + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ); + + Widget view = SingleChildScrollView( + child: Stack( + children: [ + Visibility( + visible: onSubmitting, + child: Container( + height: MediaQuery.of(context).size.height - 100.0, + color: Colors.grey.withOpacity(0.6), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 10, + backgroundColor: Colors.green, + valueColor: new AlwaysStoppedAnimation(Colors.red), + value: submitProgress, + ), + Container( + margin: EdgeInsets.only(top: 16.0), + child: Text( + S.of(context).submitting_please_wait, + style: TextStyle( + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ), + Visibility( + visible: !onSubmitting, + child: row, + ), + ], + ), + ); + return view; + } + + Widget galleryImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < 3; i++) { + Map image = galleryImagesFlag[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + '${image['path']}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Visibility( + visible: galleryImagesFlag[i]['show'], + child: Container( + padding: EdgeInsets.only(right: 20.0), + child: CircularPercentIndicator( + radius: 60.0, + lineWidth: 10.0, + percent: galleryImagesFlag[i]['progress'], + center: new Text( + '${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15.0, + color: Colors.white, + ), + ), + circularStrokeCap: CircularStrokeCap.round, + progressColor: Colors.orange, + ), + ), + ), + ], + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + return Util().getPicture2(mainContext, i, (int imageId, String path){ + setState(() { + galleryImagesFlag[imageId]['path'] = path; + }); + }); + return null; + } + ); + }, + ); + + row.children.add(Badge( + position: BadgePosition.topEnd(top: -10.0, end: 10.0), + badgeContent: Container( + child: GestureDetector( + child: Icon( + Icons.clear, + color: Colors.white, + size: 14.0, + ), + onTap: () { + _deleteImage(mainContext, i); + }, + ), + ), + showBadge: image['path'].isNotEmpty ? true : false, + child: imageWidget, + )); + } + return row; + } + + void _deleteImage(BuildContext mainContext, int imageId) { + showDialog(context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + RaisedButton( + child: Text(S.of(context).yes_i_am_sure), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + setState(() { + galleryImagesFlag[imageId]['path'] = ''; + }); + }, + ), + ], + ); + }, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + onSubmitting = event.showProgress; + submitProgress = event.progress; + }); + } + }); + } + + void _submit() { + final FormState form = _formKey.currentState; + if (form.validate()) { + setState(() { + onSubmitting = true; + }); + Util().createTicket(context, issueMsgController.text.trim(), + galleryImagesFlag, (response){ + showDialog(context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).ticket_created_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/my-support/${widget.businessId}', replace: true); + }, + ), + ], + ); + }); + }, (error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_new_user.dart b/lib/widgets/desktop/desktop_new_user.dart new file mode 100644 index 0000000..f99ab70 --- /dev/null +++ b/lib/widgets/desktop/desktop_new_user.dart @@ -0,0 +1,379 @@ + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopNewUser extends StatefulWidget { + const DesktopNewUser({Key key}) : super(key: key); + + @override + State createState() { + return DesktopNewUserState(); + } + +} + +class DesktopNewUserState extends State { + + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget view = Column( + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).mobile_or_email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_or_email_is_required; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 14.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).register, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + return SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).user_registration, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: Text( + S.of(context).user_registration_desc, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: view, + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Routes.router.navigateTo(context, '/set-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true); + }, + queryParameters: { + 'action': 'verify_code' + }, + isFormData: true, + body: { + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + 'store_id': Constants.BUSINESS_ID + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.isNotEmpty) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + }, + queryParameters: {'action': 'send_code'}, + body: { + 'mobile': usernameController.text, + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).enter_mobile_or_email, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_order_detail.dart b/lib/widgets/desktop/desktop_order_detail.dart new file mode 100644 index 0000000..634f698 --- /dev/null +++ b/lib/widgets/desktop/desktop_order_detail.dart @@ -0,0 +1,1219 @@ + +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/cart_line_item.dart'; +import '../../models/extra_fee.dart'; +import '../../models/fulfillment.dart'; +import '../../models/order.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopOrderDetail extends StatefulWidget { + final Key key; + final int orderId; + final bool fromOrders; + const DesktopOrderDetail(this.orderId, {this.key, this.fromOrders}); + + @override + State createState() { + return DesktopOrderDetailState(); + } + +} + +class DesktopOrderDetailState extends State { + Order order; + + LatLng _lastMapPosition; + LatLng customerLatLng; + LatLng deliveryLatLng; + LatLng storeLatLng; + final Set _markers = {}; + final Set _polyLine = {}; + + BitmapDescriptor homeIcon; + BitmapDescriptor deliveryIcon; + BitmapDescriptor shopIcon; + + double sideSpace = 0; + double mainSpace = 1200; + + Completer _controller = Completer(); + + void _onMapCreated(GoogleMapController controller) { + _controller.complete(controller); + } + + void _onCameraMove(CameraPosition position) { + _lastMapPosition = position.target; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (order == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget orderBody = ListView.builder( + itemCount: 4, + itemBuilder: (BuildContext context, int position) { + + switch (position) { + case 0: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: GestureDetector( + child: Container( + width: double.infinity, + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + child: Text( + order.cartInfo.businessInfo.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/shop/${order.businessId}/na/na/na'); + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 5.0), + margin: EdgeInsets.only(left: 5.0), + child: GestureDetector( + child: Icon( + Icons.phone, + ), + onTap: () { + Utils.launchURL('tel:${order.businessInfo.phone}'); + }, + ), + ), + ], + ) + ); + + if (!kIsWeb) { + if (order.shippingMethod == 'store-delivery' && order.status != Constants.STATUS_COMPLETE && order.status != Constants.STATUS_CANCELLED) { + col.children.add(Container( + height: 200.0, + child: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: new LatLng(double.parse(order.shippingAddress.lat), double.parse(order.shippingAddress.lng)), + zoom: 11.0, + ), + onCameraMove: _onCameraMove, + markers: _markers, + polylines: _polyLine, + gestureRecognizers: >[ + new Factory(() => new EagerGestureRecognizer(),) + ].toSet(), + ), + )); + if (order.deliveryDistance != null && order.deliveryDistance.distance != null) { + col.children.add(Container( + padding: EdgeInsets.only(top: 6.0, bottom: 6.0), + margin: EdgeInsets.only(bottom: 6.0), + child: Text( + S.of(context).delivery_distance_token( + order.deliveryDistance.distance.text, + order.deliveryDistance.duration.text + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + } + } + + for (CartLineItem lineItem in order.cartInfo.productList) { + + col.children.add(Container( + padding: EdgeInsets.only(top: 16.0, bottom: 0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Util.showImage('${lineItem.product.imagePath}', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 40.0, color: Colors.transparent,), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(left: 5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${lineItem.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.0, + ), + ), + Text( + '${lineItem.description}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.0, + color: Colors.black26, + ), + ) + ], + ), + ), + ), + Container( + width: 30.0, + alignment: Alignment.centerRight, + child: Text( + 'x${lineItem.quantity.round()}', + style: TextStyle( + fontSize: 14.0, + ), + ), + ), + Container( + width: 60.0, + alignment: Alignment.centerRight, + child: Text( + '${lineItem.getTotalPrice().toStringAsFixed(2)}', + style: TextStyle( + fontSize: 14.0, + ), + ), + ), + ], + ), + )); + } + col.children.add(Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + S.of(context).subtotal, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${order.getSubtotal().toStringAsFixed(2)}', + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ], + ), + ); + for (var i = 0; i < order.cartInfo.extraFeeList.length; i++) { + ExtraFee extraFee = order.cartInfo.extraFeeList[i]; + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + '${extraFee.name}(${extraFee.rate}%)', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${extraFee.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ], + ), + ); + } + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + S.of(context).total, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0, bottom: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 1: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + col.children.add(Container( + width: double.infinity, + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + child: Text( + S.of(context).delivery_info, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ), + ); + if (order.shippingMethod != 'pickup') { + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).delivery_address, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + child: Text( + '${order.address}, ${order.consignee}, ${order.phone}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } else { + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).pickup_address, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + '${order.cartInfo.businessInfo.fullAddress}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).schedule_delivery, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + '${Utils.timestampToString(context, order.bookedAt, withTime: true)}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).delivery_method, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + flex: 1, + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + order.shippingMethod == 'pickup' ? S.of(context).pickup : S.of(context).store_delivery, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 2: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + col.children.add(Container( + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).order_info, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + Text(''), + ], + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ),); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).order_number, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + '${order.orderNum}', + style: TextStyle( + color: Colors.black38, + ), + ), + Container( + margin: EdgeInsets.only(left: 10.0, right: 10.0), + child: Text(''), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 0.5, + color: Colors.black12 + ), + ), + ), + ), + GestureDetector( + child: Text( + S.of(context).copy, + ), + onTap: () { + Clipboard.setData(ClipboardData(text: '${order.orderNum}')); + Fluttertoast.showToast( + msg: S.of(context).order_number_copied_to_clipboard, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + }, + ), + ], + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).payment_method, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + order.payMethod == 0 ? S.of(context).online_payment : S.of(context).pay_on_deliery, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).payment_status, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + order.paymentStatus == Constants.PAYMENT_STATUS_PAID ? S.of(context).paid : S.of(context).unpaid, + style: TextStyle( + color: Colors.black38, + ), + ), + Container( + margin: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? EdgeInsets.only(left: 10.0, right: 10.0) : EdgeInsets.only(left: 0.0, right: 0.0), + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? Text('') : SizedBox.shrink(), + decoration: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? BoxDecoration( + border: Border( + left: BorderSide( + width: 0.5, + color: Colors.black12 + ), + ), + ) : null, + ), + GestureDetector( + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE ? Text( + S.of(context).pay_now, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ) : SizedBox.shrink(), + onTap: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + ), + ], + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).order_datetime, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + Utils.timestampToString(context, order.createdAt, withTime: true), + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 3: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + col.children.add(Container( + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).order_fulfillment, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + Text( + Utils.getOrderStatus(context, order.status), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + color: Colors.orange, + ), + ), + ], + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ),); + + for (Fulfillment fulfillment in order.fulfillments) { + col.children.add(Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: fulfillment.shippingMethod.isNotEmpty && fulfillment.trackingNumber != null && fulfillment.trackingNumber.isNotEmpty ? + Text( + '${fulfillment.shippingMethod} ${fulfillment.trackingNumber}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ) : (fulfillment.shippingMethod.isNotEmpty ? Text( + '${fulfillment.shippingMethod}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ) : SizedBox.shrink()), + ), + Container( + child: fulfillment.note != null && fulfillment.note.isNotEmpty ? + Text( + '${fulfillment.note}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black38, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ) : SizedBox.shrink(), + ), + Container( + child: Text( + '${Utils.utcDatetimeStringToLocalDatetimeString(context, fulfillment.createdAt, withTime: true)}', + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + default: + return SizedBox(); + } + }, + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).order_detail, null), + BreadCrumb('#${order.orderNum}', null) + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: WillPopScope( + onWillPop: () async { + if (widget.fromOrders != null && widget.fromOrders) { + return true; + } else { + Routes.router.navigateTo( + context, '/orders', replace: true, clearStack: true); + } + return false; + }, + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: orderBody,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: Container( + padding: new EdgeInsets.only(left: 16.0, right: 16.0, top: 20.0, bottom: 20.0), + color: new Color(0xFFA8A8FF), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 10.0), + child: order.status == Constants.STATUS_PENDING && order.paymentStatus == Constants.PAYMENT_STATUS_UNPAID ? + FlatButton( + child: Text( + S.of(context).cancel_order, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + onPressed: () { + _cancelOrder(context); + }, + ) : SizedBox.shrink(), + ), + Container( + child: order.status == Constants.STATUS_COMPLETE && !order.hasComment ? RaisedButton( + child: Text( + S.of(context).comment, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + onPressed: () { + Routes.router.navigateTo(context, '/new-comment/${order.id}'); + }, + ) : SizedBox.shrink(), + ), + ], + ), + Container( + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE ? + FlatButton( + child: Text( + S.of(context).pay_now, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.yellow, + ), + ), + onPressed: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + ) : FlatButton( + child: Text( + S.of(context).order_again, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + ), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + if (!kIsWeb) { + Util.getBytesFromAsset('assets/images/home.png', 100).then((value) { + homeIcon = BitmapDescriptor.fromBytes(value); + }); + Util.getBytesFromAsset('assets/images/delivery.png', 80).then((value) { + deliveryIcon = BitmapDescriptor.fromBytes(value); + }); + Util.getBytesFromAsset('assets/images/shop.png', 100).then((value) { + shopIcon = BitmapDescriptor.fromBytes(value); + }); + } + _loadOrder(); + } + + _loadOrder() { + HttpUtil.httpGet('v1/orders/${widget.orderId}', + queryParameters: { + 'expand': 'cart_info,business_info', + }, + ).then((data) { + if (mounted) { + setState(() { + order = Order.fromJson(data); + + if (!kIsWeb) { + if (order.shippingMethod == 'store-delivery' && order.status != Constants.STATUS_COMPLETE && order.status != Constants.STATUS_CANCELLED) { + storeLatLng = LatLng(double.parse(order.businessInfo.address.lat), + double.parse(order.businessInfo.address.lng)); + customerLatLng = LatLng(double.parse(order.shippingAddress.lat), + double.parse(order.shippingAddress.lng)); + deliveryLatLng = + LatLng(order.shipperPosition.lat, order.shipperPosition.lng); + + _polyLine.clear(); + _polyLine.add( + Polyline( + polylineId: PolylineId('shipper-customer'), + color: Colors.green, + patterns: [ + PatternItem.dash(20.0), + PatternItem.gap(10), + ], + width: 3, + points: [ + order.shipperPosition.lat != 0.0 ? deliveryLatLng : storeLatLng, + customerLatLng, + ] + ) + ); + + _markers.clear(); + _markers.add(Marker( + markerId: MarkerId('shop_position'), + position: storeLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .store, + snippet: '', + ), + icon: shopIcon, + )); + _markers.add(Marker( + markerId: MarkerId('customer_position'), + position: customerLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .customer, + snippet: order.shippingAddress.addressLine1, + ), + icon: homeIcon, + )); + if (order.shipperPosition.lat != 0.0 && + order.shipperPosition.lng != 0.0) { + _markers.add(Marker( + markerId: MarkerId('shipper_position'), + position: deliveryLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .delivery_guy, + snippet: '', + ), + icon: deliveryIcon, + )); + } + } + } + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } + + _cancelOrder(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_cancel_the_order), + actions: [ + FlatButton( + child: Text( + S.of(context).no, + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + S.of(context).yes_i_am_sure, + ), + onPressed: () { + Navigator.of(context).pop(); + _processCancelOrder(); + if (mounted) { + setState(() { + order = null; + }); + } + }, + ), + ], + ); + } + ); + } + + _processCancelOrder() { + HttpUtil.httpGet( + 'v1/order-cancel/${widget.orderId}' + ).then((data) { + if (mounted) { + setState(() { + order = Order.fromJson(data); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_orders.dart b/lib/widgets/desktop/desktop_orders.dart new file mode 100644 index 0000000..59308df --- /dev/null +++ b/lib/widgets/desktop/desktop_orders.dart @@ -0,0 +1,467 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../pages/order_detail.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/double_back_to_close_app.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + + +class DesktopOrders extends StatefulWidget { + final Key key; + const DesktopOrders({this.key}); + + @override + State createState() { + return DesktopOrdersState(); + } + +} + +class DesktopOrdersState extends State with SingleTickerProviderStateMixin { + GlobalKey _scaffoldKey = GlobalKey(); + + List orders; + + bool _isLoading = false; + + int _page = 1; + int _pageCount = 1; + + double sideSpace = 0; + double mainSpace = 1200; + + ScrollController scrollController = ScrollController(); + + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + store.dispatch(UpdateRedirectRoute('/orders')); + Routes.router.navigateTo(context, '/login', replace: true); + return; + } + }); + + if (orders == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget body = NotificationListener( + onNotification: (ScrollNotification scrollInfo) { + if (!_isLoading && scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + if (_pageCount > _page) { + setState(() { + _page += 1; + _isLoading = true; + }); + _loadData(); + return true; + } + } + return false; + }, + child: _buildBody(), + ); + + return Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).my_orders, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: DoubleBackToCloseApp( + snackBar: SnackBar( + content: Text(S.of(context).tap_back_again_to_exit), + ), + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: body, + ), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget _buildBody() { + + return ListView.builder( + controller: scrollController, + itemCount: orders != null && orders.length > 0 ? orders.length + 1 : 1, + itemBuilder: (BuildContext context, int position) { + Order order; + if (orders != null && orders.length > 0 && position < orders.length) { + order = orders[position]; + } + + if (order == null && orders.length <= 0) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text( + S.of(context).you_have_no_orders_yet, + ), + ), + ); + } else if (position >= orders.length) { + if (_pageCount > _page) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } else { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Center( + child: Text( + S.of(context).no_more_record, + ), + ), + ), + ); + } + } + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + row.children.add(Expanded( + child: Container( + child: Text( + order.cartInfo.productList[0].name, + style: TextStyle( + fontSize: 14.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + )); + if (order.cartInfo.productList.length > 1) { + row.children.add(Container( + child: Text( + S.of(context).and_more_item_token(Utils.getProductLineInOrder(order.cartInfo)), + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + )); + } + row.children.add(Container( + width: 80.0, + alignment: Alignment.centerRight, + child: Text( + '\$${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + )); + + Row row3 = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + RaisedButton( + child: Text( + S.of(context).detail, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + ], + ); + if (order.status == Constants.STATUS_COMPLETE && !order.hasComment) { + row3.children.add( + Padding( + padding: EdgeInsets.only(left: 10.0), + child: RaisedButton( + child: Text( + S.of(context).comment, + ), + onPressed: () { + Routes.router.navigateTo(context, '/new-comment/${order.id}'); + }, + ), + ), + ); + } + + Row row2 = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID) { + row2.children.add(row3); + if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE) { + row2.children.add(RaisedButton( + child: Text( + S.of(context) + .pay_now, + style: TextStyle( + color: Colors.white, + ), + ), + color: Colors.redAccent, + onPressed: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + )); + } else { + row2.children.add(RaisedButton( + child: Text( + S.of(context).order_again, + style: TextStyle( + color: Colors.white, + ), + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + )); + } + } else { + row2.children.add(row3); + row2.children.add(RaisedButton( + child: Text( + S.of(context).order_again, + style: TextStyle( + color: Colors.white, + ), + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + )); + } + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + child: Util.showImage('${order.cartInfo.businessInfo.picUrl}', + width: 32.0, + height: 32.0, + fit: BoxFit.fill, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(left: 5.0, right: 5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '${order.cartInfo.businessInfo.name}', + style: TextStyle( + fontSize: 20.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + '#${order.orderNum} ${Utils.timestampToString(context, order.createdAt, withTime: true)}', + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + ), + Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: Text( + '${Utils.getOrderStatus(context, order.status)}', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: SizedBox.shrink(), + ), + ], + ), + ), + ], + ), + Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + width: double.infinity, + child: SizedBox(), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 20.0), + child: row, + ), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + Container( + child: row2, + ), + ], + ), + ); + }, + ); + } + + @override + void initState() { + super.initState(); + + _page = 1; + _pageCount = 1; + eventBus.on().listen((event) { + if (mounted) { + setState(() { + orders = null; + }); + } + }); + + _loadData(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + _loadData() { + HttpUtil.httpGet('v1/orders', + queryParameters: { + 'expand': 'cart_info', + 'page': _page.toString(), + 'size': Constants.ORDERS_PER_PAGE.toString(), + }, + ).then((data) { +// Utils.jsonPrettyPrint(data); + _page = int.parse(data['_meta']['currentPage'].toString()); + _pageCount = int.parse(data['_meta']['pageCount'].toString()); + + if (mounted) { + setState(() { + _isLoading = false; + if (orders == null) { + orders = []; + } + orders.addAll((data['items'] as List).map((e) => Order.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if(mounted) { + setState(() { + _isLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_pay_now.dart b/lib/widgets/desktop/desktop_pay_now.dart new file mode 100644 index 0000000..ecacd7f --- /dev/null +++ b/lib/widgets/desktop/desktop_pay_now.dart @@ -0,0 +1,396 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/general/navigationbar.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/payment_verification_code_dialog.dart'; + +class DesktopPayNow extends StatefulWidget { + final Key key; + final int orderId; + const DesktopPayNow(this.orderId, {this.key}); + + @override + State createState() { + return DesktopPayNowState(); + } + +} + +class DesktopPayNowState extends State { + Order order; + List paymentPlatforms; + User _user; + + bool nativePay; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (order == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + BuildContext mainContext = context; + + ListView listView = ListView.builder( + itemCount: nativePay ? paymentPlatforms.length + 3 : paymentPlatforms.length + 2, + itemBuilder: (BuildContext context, int position) { + if (position == 0) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only(top: 32.0, right: 16.0, left: 16.0, bottom: 32.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).payment_amount, + style: TextStyle( + fontSize: 15.0, + color: Colors.black26, + ), + ), + Text( + '\$${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ) + ) + ), + ), + store.state.deviceId != null && store.state.deviceId.isNotEmpty || store.state.tableNumber != null && store.state.tableNumber.isNotEmpty ? + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.local_cafe, + size: 50.0, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).pay_later, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).pay_after_meal, + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ) + ) + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true); + }, + ) : + SizedBox.shrink() + ], + ); + } + PaymentPlatform paymentPlatform; + if (position == 1) { + if (_user.stripePaymentMethods.length > 0) { + paymentPlatform = paymentPlatforms[position - 1]; + Column column = Column( + children: [], + ); + column.children.add( + Container( + padding: EdgeInsets.all(16.0), + width: double.infinity, + child: Text( + S.of(context).pay_with_existing_cards, + textAlign: TextAlign.left, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, + color: Colors.black26, + ), + ), + ), + ), + ); + for (StripePaymentMethod stripePaymentMethod in _user.stripePaymentMethods) { + column.children.add( + GestureDetector( + child: Container( + padding: EdgeInsets.only( + top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + stripePaymentMethod.cardBrand == 'visa' ? + Image.asset( + 'assets/images/visa.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : (stripePaymentMethod.cardBrand == 'mastercard' ? + Image.asset( + 'assets/images/master.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : Icon( + Icons.credit_card, size: 50.0, color: Colors.black38,)), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + '**** ${stripePaymentMethod.cardLast4}', + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).expire_token(stripePaymentMethod.cardExpMonth, stripePaymentMethod.cardExpYear), + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + return PaymentVerificationCodeDialog(_user, () { + Util.goPayment(context, order, paymentPlatform, + stripePaymentMethod: stripePaymentMethod); + }, () { + + }); + }, + ); + }, + ), + ); + } + column.children.add( + Container( + width: double.infinity, + child: SizedBox.shrink(), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black26, + ), + ), + ), + ) + ); + return column; + } else { + return SizedBox.shrink(); + } + } + if (position == 2 && nativePay) { + paymentPlatform = paymentPlatforms[position - 2]; + return Util().getNativePay(mainContext, order, paymentPlatform); + } + paymentPlatform = nativePay ? paymentPlatforms[position - 3] : paymentPlatforms[position - 2]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 10.0), + child: Util.showImage(paymentPlatform.icon, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 50.0, color: Colors.transparent,), + fit: BoxFit.cover, + width: 50.0, + height: 50.0, + ), + ), + Expanded( + child: Text( + S.of(context).pay_with_token(PaymentPlatform.getPaymentPlatformName(context, paymentPlatform.code)), + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black26, + ), + ), + ), + ), + onTap: () { + Util.goPayment(context, order, paymentPlatform); + }, + ); + } + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).pay_now, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Row( + children: [ + Container(width: sideSpace,), + Expanded(child: listView,), + Container(width: sideSpace,), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + HttpUtil.httpGet( + 'v1/${widget.orderId}/paymentplatforms', + ).then((data) { + paymentPlatforms = (data['payment_platforms'] as List).map((e) => + PaymentPlatform.fromJson(e)).toList(); + PaymentPlatform pp = paymentPlatforms[0]; + if (Constants.ENABLE_NATIVE_PAY && pp.publishableKey != null + && pp.publishableKey.isNotEmpty && pp.merchantId != null + && pp.merchantId.isNotEmpty) { + nativePay = true; + } else { + nativePay = false; + } + _user = User.fromJson(data['contact']); + store.dispatch(UpdateCurrentUser(_user)); + eventBus.fire(OnCurrentUserUpdated()); + setState(() { + order = Order.fromJson(data['order']); + }); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } +} + +// 198.54.117.10 \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_plain_page.dart b/lib/widgets/desktop/desktop_plain_page.dart new file mode 100644 index 0000000..9749e9e --- /dev/null +++ b/lib/widgets/desktop/desktop_plain_page.dart @@ -0,0 +1,88 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_wisetronic/widgets/general/bottom_nav.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/blog.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopPlainPage extends StatefulWidget { + final Blog blog; + + const DesktopPlainPage(this.blog, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopPlainPageState(); + } + +} + +class DesktopPlainPageState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Column col = Column( + children: [], + ); + col.children.add(Container( + padding: EdgeInsets.only(top: 20, left: 16.0, right: 16.0, bottom: 16.0), + child: Center( + child: Text( + widget.blog.title, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ), + )); + col.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 20.0), + child: MarkdownBody( + shrinkWrap: true, + data: widget.blog.body, + ), + )); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(widget.blog.title, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: col,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_product_detail_page.dart b/lib/widgets/desktop/desktop_product_detail_page.dart new file mode 100644 index 0000000..bafb8b4 --- /dev/null +++ b/lib/widgets/desktop/desktop_product_detail_page.dart @@ -0,0 +1,479 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/Subproduct.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../models/product_detail.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/carousel.dart'; +import '../../widgets/general/navigationbar.dart'; +import '../../widgets/general/show_price.dart'; +import 'desktop_shopping_cart_widget.dart'; + +class DesktopProductDetailPage extends StatefulWidget { + final Business business; + final Product product; + + const DesktopProductDetailPage( + {@required this.business, @required this.product, Key key}) + : super(key: key); + + @override + State createState() { + return DesktopProductDetailPageState(); + } +} + +class DesktopProductDetailPageState extends State + with SingleTickerProviderStateMixin { + + TabController _tabController; + + final double _tabBarHeight = 50; + + ProductDetail productDetail; + + bool refresh; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (productDetail == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + refresh = false; + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).shop, + back: true, + breadCrumbs: [ + BreadCrumb(productDetail.name, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + // shoppingCart: DesktopShoppingCartWidget(business: widget.business,), + ), + body: SingleChildScrollView( + child: Container( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 30.0, left: 16.0, right: 16.0, bottom: 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getImage(mainSpace * 0.50), + Container( + margin: EdgeInsets.only(top: 10), + child: Text( + 'SKU: ${productDetail.sku}', + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: TextStyle( + color: Colors.black45, + fontSize: 12.0, + ), + ), + ), + Container( + child: Text( + productDetail.name, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: Text( + '${productDetail.description}', + overflow: TextOverflow.ellipsis, + maxLines: 3, + style: TextStyle( + color: Colors.black54, + fontSize: 15, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace * 0.5, + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShowPrice( + productDetail.price, + currencySign: '\$', + fontWeight: FontWeight.bold, + smallFontSize: 24, + largeFontSize: 40, + regularPrice: productDetail.regularPrice, + ), + AddRemoveButton( + product: widget.product, + business: widget.business, + addToBasket: true, + ), + ], + ), + ), + TabBar( + labelColor: new Color(0xFF3190E8), + unselectedLabelColor: new Color(0xFF666666), + indicatorColor: new Color(0xFF3190E8), + indicatorSize: TabBarIndicatorSize.label, + labelStyle: new TextStyle( + fontSize: 16.0, + ), + controller: _tabController, + tabs: [ + Tab( + text: S.of(context).detail, + ), + Tab( + text: S.of(context).specification, + ), + ], + ), + Container( + height: mainSpace / 2, + child: TabBarView( + controller: _tabController, + children: [ + SingleChildScrollView( + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (productDetail.subproducts.length > 0) ? + subProducts(productDetail.subproducts) : + SizedBox.shrink(), + Container( + padding: EdgeInsets.only( + top: 10.0, left: 10.0, right: 10.0, bottom: 16.0), + child: Text( + productDetail.description, + style: TextStyle( + fontSize: 14.0, color: Colors.black54), + ), + ), + Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0), + child: (productDetail.description2 != null && + !productDetail.description2.isEmpty) + ? Text( + '${productDetail.description2}', + style: TextStyle( + fontSize: 14.0, color: Colors.black54), + ) + : SizedBox.shrink(), + ), + productDetail.detailDescription != null + ? Container( + padding: + EdgeInsets.only(left: 10.0, right: 10.0), + child: MarkdownBody( + data: '${productDetail.detailDescription}', + shrinkWrap: true, + ), + ) + : SizedBox.shrink(), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only( + top: 10.0, left: 10.0, right: 10.0, bottom: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).weight_token(productDetail.weight), + style: TextStyle( + fontSize: 12.0, + color: Colors.black54, + ), + ), + ), + Container( + child: Text( + S.of(context).dimentions_token( + productDetail.dimentionsLength, + productDetail.dimentionsWidth, + productDetail.dimentionsHeight), + style: TextStyle( + fontSize: 12.0, + color: Colors.black54, + ), + ), + ) + ], + ), + ) + ], + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + Widget getImage(double width) { + if (productDetail.images.length <= 0) { + return Util.showImage( + productDetail.image, + width: width, + height: width, + ); + } else { + return Carousel( + height: width, + pages: _getProductPictures(context), + autoPlay: true, + duration: Duration(seconds: 10,), + ); + } + } + + Widget subProducts(List subproducts) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 12, top: 10, bottom: 10), + child: Text( + S.of(context).includes, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ) + ], + ); + for (int i = 0; i < subproducts.length; i++) { + col.children.add(subProduct(subproducts[i])); + } + return col; + } + + Widget subProduct(Subproduct subproduct) { + Row row = Row( + children: [], + ); + row.children.add( + Container( + padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 5.0), + child: Util.showImage( + 'https:${subproduct.product.image}', + width: 48, + height: 48, + fit: BoxFit.contain, + ), + ), + ); + row.children.add( + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 12, top: 5), + child: Text( + subproduct.product.name, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ), + Container( + width: 80, + padding: EdgeInsets.only(left: 12, top: 5, right: 12), + child: Text( + '${subproduct.product.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 13, + decoration: TextDecoration.lineThrough, + ), + ), + alignment: Alignment.centerRight, + ), + Container( + width: 60, + padding: EdgeInsets.only(left: 12, top: 5, right: 12), + child: Text( + 'x${subproduct.quantity.toStringAsFixed(0)}', + style: TextStyle( + fontSize: 13, + ), + ), + alignment: Alignment.centerRight, + ), + ], + ), + Container( + padding: EdgeInsets.only(left: 12, top: 12, right: 12), + child: Text( + '${subproduct.product.description}', + style: TextStyle( + fontSize: 12, + color: Colors.black45, + ), + ), + ), + ], + ), + ), + ); + return Container( + padding: EdgeInsets.only(left: 0, right: 0, top: 0, bottom: 0), + child: row, + ); + } + + @override + void initState() { + setState(() { + refresh = false; + }); + super.initState(); + _tabController = TabController(vsync: this, length: 2); + HttpUtil.httpGet( + 'v1/product-detail/${widget.product.id}', + businessId: widget.business.id, + ).then((value) { + print(value); + productDetail = ProductDetail.fromJson(value); + setState(() { + refresh = true; + }); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + refresh = true; + }); + } + }); + } + + List _getProductPictures(BuildContext context) { + var pages = []; + List images = []; + images.add(productDetail.image); + for (var i = 0; i < productDetail.images.length; i++) { + // print('>>https:' + productDetail.images[i].image); + images.add(productDetail.images[i].image); + } + + for (var i = 0; i < images.length; i++) { + pages.add(new GestureDetector( + child: new Container( + child: Util.showImage( + 'https:' + images[i], + ), + ), + onTap: () {}, + )); + } + return pages; + } + + @override + void dispose() { + _tabController?.dispose(); + super.dispose(); + } +} diff --git a/lib/widgets/desktop/desktop_product_item.dart b/lib/widgets/desktop/desktop_product_item.dart new file mode 100644 index 0000000..ac22e13 --- /dev/null +++ b/lib/widgets/desktop/desktop_product_item.dart @@ -0,0 +1,230 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/show_price.dart'; + +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../pages/product_detail_page.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/style.dart'; +import 'desktop_product_detail_page.dart'; + +class DesktopProductItem extends StatefulWidget { + final Product product; + final Business business; + final bool horizontal; + + DesktopProductItem({this.product, this.business, Key key, this.horizontal = false}) + : super(key: key); + + @override + State createState() { + return DesktopProductItemState(); + } +} + +class DesktopProductItemState extends State { + int _qty = 0; + + @override + Widget build(BuildContext context) { + return new Container( + width: 160.0, + height: widget.horizontal ? 160 : 268.0, + padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0).copyWith(bottom: 5.0), + decoration: new BoxDecoration( + color: Style.backgroundColor, + border: new Border( + bottom: new BorderSide( + color: new Color(0xFFEBEBEB), + ) + ), + ), + child: new SizedBox.expand( + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + child: widget.horizontal ? getHorizontal() : getVertial(), + ), + ), + ); + } + + Widget getHorizontal() { + return Row( + children: [ + Container( + margin: new EdgeInsets.all(10.0), + width: 110.0, + height: 110.0, + child: GestureDetector( + child: Util.showImage('${widget.product.imagePath}', + fit: BoxFit.fill, + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: GestureDetector( + child: Text( + '${widget.product.name}', +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + style: new TextStyle( + fontSize: 15.0, + ), + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + Text( + widget.product.description, +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: new TextStyle( + fontSize: 12.0, + color: new Color(0xFF999999), + ), + ), + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + child: widget.business.showMonthlySold ? + Text( + S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)), + style: new TextStyle( + fontSize: 9.0 + ), + ) : SizedBox.shrink(), + ), + ShowPrice( + widget.product.price, + regularPrice: widget.product.regularPrice, + currencySign: '\$', + fontWeight: FontWeight.bold, + ), + ], + ), + new AddRemoveButton(product: widget.product, business: widget.business, addOnly: false,), + ], + ), + ], + ), + ), + ], + ); + } + + Widget getVertial() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Container( + margin: new EdgeInsets.all(10.0), + width: 110.0, + height: 110.0, + child: GestureDetector( + child: Util.showImage('${widget.product.imagePath}', + fit: BoxFit.fill, + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: GestureDetector( + child: Text( + '${widget.product.name}', +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + style: new TextStyle( + fontSize: 15.0, + ), + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + Container( + child: Text( + widget.product.description, +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: new TextStyle( + fontSize: 12.0, + color: new Color(0xFF999999), + ), + ), + ), + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + child: widget.business.showMonthlySold ? + Text( + S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)), + style: new TextStyle( + fontSize: 9.0 + ), + ) : SizedBox.shrink(), + ), + ShowPrice( + widget.product.price, + regularPrice: widget.product.regularPrice, + currencySign: '\$', + fontWeight: FontWeight.bold, + ), + ], + ), + new AddRemoveButton(product: widget.product, business: widget.business, addOnly: false,), + ], + ), + ], + ), + ), + ], + ); + } + + void _showProductDetail() { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDetailPage( + product: widget.product, + business: widget.business, + )), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_renew_license.dart b/lib/widgets/desktop/desktop_renew_license.dart new file mode 100644 index 0000000..912b584 --- /dev/null +++ b/lib/widgets/desktop/desktop_renew_license.dart @@ -0,0 +1,209 @@ + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopRenewLicense extends StatefulWidget { + final int businessId; + + const DesktopRenewLicense(this.businessId, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopRenewLicenseState(); + } + +} + +class DesktopRenewLicenseState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + TextEditingController groupNumberController = TextEditingController(); + + bool canSubmit = false; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 20, bottom: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 4.0), + child: Text( + S.of(context).renew_license, + style: TextStyle( + fontSize: 28, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4, bottom: 4), + child: Text( + S.of(context).group_number_can_be_found, + style: TextStyle( + color: Colors.black45, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4), + child: Image.asset( + 'assets/images/group_number.png', + width: 400, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + width: 1, + color: Colors.black12, + ), + ), + ), + ), + ), + ); + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 20, bottom: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 4,), + child: Text( + S.of(context).group_number, + style: TextStyle( + fontSize: 28, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4,), + child: TextFormField( + controller: groupNumberController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).group_number, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).please_enter_group_number; + } + return null; + }, + onChanged: (string) { + if (string.isNotEmpty) { + canSubmit = true; + } else { + canSubmit = false; + } + setState(() {}); + }, + ), + ), + Container( + padding: EdgeInsets.only(top: 20), + child: ElevatedButton( + child: Text( + S.of(context).submit, + ), + onPressed: canSubmit ? () { + _submit(); + } : null, + ), + ), + ], + ), + ), + ), + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).renew_license, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: row,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + void _submit() { + HttpUtil.httpPost('v1/get-license-renewal', + (response) { + Routes.router.navigateTo(context, '/renew-minioffice/${response.data['id']}', replace: true); + }, + body: { + 'group_name': groupNumberController.text.trim(), + }, + isFormData: true, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_renew_minioffice.dart b/lib/widgets/desktop/desktop_renew_minioffice.dart new file mode 100644 index 0000000..8ee51ab --- /dev/null +++ b/lib/widgets/desktop/desktop_renew_minioffice.dart @@ -0,0 +1,179 @@ + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + +class DesktopRenewMiniOffice extends StatefulWidget { + final Map data; + + const DesktopRenewMiniOffice(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return DesktopRenewMiniOfficeState(); + } + +} + +class DesktopRenewMiniOfficeState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + Column col1 = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 16), + child: Text( + S.of(context).group_license_renewal, + style: TextStyle( + fontSize: 28, + ), + ), + ), + ], + ); + col1.children.add( + Utils.buildLine(S.of(context).group_number, widget.data['group']['name'], valueSize: 18), + ); + col1.children.add( + Utils.buildLine(S.of(context).expiration_date, + Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['expiration_date']), + valueSize: 18 + ), + ); + col1.children.add( + Utils.buildLine(S.of(context).after_renewed, + Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['renewed_expiration_date']), + valueSize: 18 + ), + ); + col1.children.add( + Utils.buildLine(S.of(context).renewal_fee, '\$${widget.data['renewal_fee']}', valueSize: 18), + ); + col1.children.add( + Utils.buildLine(S.of(context).tax, '\$${widget.data['tax']}', valueSize: 18), + ); + col1.children.add( + Utils.buildLine(S.of(context).total, '\$${widget.data['renewal_total']}', valueSize: 38), + ); + + Column col2 = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16, right: 16), + child: ElevatedButton( + child: Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), + child: Text( + S.of(context).pay_amount_token(widget.data['renewal_total']), + style: TextStyle( + fontSize: 20, + ), + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ], + ); + + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col1, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + width: 1, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + row.children.add( + Expanded( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col2, + ), + ) + ); + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).renew_license, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded(child: row,), + Container( + width: sideSpace, + ), + ], + ), + ), + bottomNavigationBar: BottomNav(), + ); + } + + void _submit() { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/renew-minioffice/${widget.data['group']['id']}'); + return; + } + HttpUtil.httpPost('v1/create-minioffice-renewal-invoice', + (response) { + Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true); + }, + body: widget.data, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_reset_password.dart b/lib/widgets/desktop/desktop_reset_password.dart new file mode 100644 index 0000000..a9c81a1 --- /dev/null +++ b/lib/widgets/desktop/desktop_reset_password.dart @@ -0,0 +1,304 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopResetPassword extends StatefulWidget { + final String mobile; + final String code; + + const DesktopResetPassword(this.mobile, {this.code, Key key}) : + super(key: key); + + @override + State createState() => + DesktopResetPasswordState(); +} + +class DesktopResetPasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget form = Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + padding: EdgeInsets.only(right: 0.0, top: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ), + ), + ); + + return SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 0.0, right: 16.0), + child: Text( + S.of(context).reset_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 12.0, left: 16.0, right: 16.0, bottom: 12.0), + child: Text( + S.of(context).reset_password_desc, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: form, + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).reset_password_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/login', replace: true, clearStack: false); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'reset_password', + }, + isFormData: true, + body: { + 'mobile': widget.mobile, + 'code': widget.code, + 'password': passwordController.text.trim() + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_search_place.dart b/lib/widgets/desktop/desktop_search_place.dart new file mode 100644 index 0000000..947ffc9 --- /dev/null +++ b/lib/widgets/desktop/desktop_search_place.dart @@ -0,0 +1,153 @@ + +import 'package:dio/dio.dart'; +import 'package:flappy_search_bar/flappy_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart'; +import 'package:flutter_wisetronic/widgets/general/navigationbar.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/located_address.dart'; +import '../../pages/new_address.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopSearchPlace extends StatefulWidget { + final Key key; + final int businessId; + const DesktopSearchPlace(this.businessId, {this.key}) : super(key: key); + + @override + State createState() { + return DesktopSearchPlaceState(); + } + +} + +class DesktopSearchPlaceState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + return Scaffold( + appBar: NavigationBar( + title: S.of(context).search_place, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).search_place, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + padding: EdgeInsets.only(top: 16.0, right: 16.0, bottom: 16.0, left: 16.0), + width: mainSpace, + child: SearchBar( + minimumChars: 6, + hintText: S.of(context).enter_delivery_address, + cancellationWidget: Text( + S.of(context).cancel, + ), + onSearch: search, + onItemFound: (LocatedAddress locatedAddress, int index) { + return ListTile( + title: Text(locatedAddress.streetName), + subtitle: Text(locatedAddress.formattedAddress), + onTap: () { + if (widget.businessId == 0) { + _selectPlace(locatedAddress); + } else { + _selectPlaceAsNew(locatedAddress); + } + }, + dense: true, + + ); + }, + onError: (error) { + return Center( + child: Text("$error"), + ); + }, + emptyWidget: Center( + child: widget.businessId > 0 ? + GestureDetector( + child: Text( + S.of(context).empty_address_change_keyword, + ), + onTap: () { + _selectPlaceAsNew(null); + }, + ) : + Text( + S.of(context).empty_result_change_keyword + ), + ), + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + } + + Future> search(String keyword) async { + var result = await HttpUtil.httpGet('v1/search-places', + queryParameters: { + 'keyword': keyword, + }, + returnError: true, + ); + if (result is DioError) { + if (result.response != null) { + throw RuntimeError(result.response.data['message']); + } else { + throw RuntimeError(result.message); + } + } else if (result != null && result is List) { + return result.map((e) => LocatedAddress.fromJson(e)).toList(); + } + return []; + } + + void _selectPlace(LocatedAddress locatedAddress) { + store.dispatch(UpdateLocatedAddress(locatedAddress)); + eventBus.fire(OnUpdateLocatedAddressSuccess()); + Navigator.of(context).pop(); + } + + void _selectPlaceAsNew(LocatedAddress locatedAddress) { + print('located address: ${locatedAddress.toString()}'); + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => NewAddress(locatedAddress: locatedAddress, businessId: widget.businessId,), + )); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_set_password.dart b/lib/widgets/desktop/desktop_set_password.dart new file mode 100644 index 0000000..5568f41 --- /dev/null +++ b/lib/widgets/desktop/desktop_set_password.dart @@ -0,0 +1,304 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class DesktopSetPassword extends StatefulWidget { + final String mobile; + final String code; + + const DesktopSetPassword(this.mobile, {this.code, Key key}) : + super(key: key); + + @override + State createState() => + DesktopSetPasswordState(); +} + +class DesktopSetPasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget form = Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + padding: EdgeInsets.only(right: 16.0, top: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ), + ), + ); + + return SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 0.0, right: 16.0), + child: Text( + S.of(context).set_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 12.0, left: 16.0, right: 16.0, bottom: 12.0), + child: Text( + S.of(context).set_password_desc, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + ], + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + child: form, + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + } + + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).reset_password_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/login', replace: true, clearStack: false); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'reset_password', + }, + isFormData: true, + body: { + 'mobile': widget.mobile, + 'code': widget.code, + 'password': passwordController.text.trim() + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_shop.dart b/lib/widgets/desktop/desktop_shop.dart new file mode 100644 index 0000000..fba64f0 --- /dev/null +++ b/lib/widgets/desktop/desktop_shop.dart @@ -0,0 +1,2218 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:badges/badges.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/mobile/shopping_cart_bar.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:smooth_star_rating/smooth_star_rating.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/cart_info.dart'; +import '../../models/cart_line_item.dart'; +import '../../models/category_products.dart'; +import '../../models/comment.dart'; +import '../../models/product.dart'; +import '../../models/product_image.dart'; +import '../../pages/product_detail_page.dart'; +import '../../pages/store_product_search.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/shop_scroll_controller.dart'; +import '../../utils/shop_scroll_coordinator.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/bottom_nav.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/carousel.dart'; +import '../../widgets/general/navigationbar.dart'; +import '../../widgets/general/product_item.dart'; +import '../../widgets/general/read_more_text.dart'; +import '../../widgets/general/show_price.dart'; +import '../../widgets/general/sliding_up_panel.dart'; +import '../../widgets/general/style.dart'; +import 'desktop_shopping_cart_widget.dart'; + +class DesktopShop extends StatefulWidget { + final int businessId; + + const DesktopShop({Key key, this.businessId}) : super(key: key); + + @override + State createState() => DesktopShopState(); +} + +MediaQueryData mediaQuery; +double statusBarHeight; +double screenWidth; +double screenHeight; + +class DesktopShopState extends State + with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { + GlobalKey _scaffoldKey = new GlobalKey(); + + Business _business; + List _categoryProducts; + List _featuredProducts; + List _hotSaleProducts; + + List _prompts = []; + bool checkCloseFlag = false; + + bool displayProductByCategoryClick = false; + String displayProductByCategoryClickIndicator = ''; + int categoryId = 0; + int _productCurrentPage = 1; + + static const num _categoryHeight = 50.0; + static const num _categoryDescHeight = 50.0; + static const num _productHeight = 266.0; + + int _categoryIndex = 0; + bool _categoryIndexChange = false; + bool _isLoading = false; + + bool refresh = false; + + double sideSpace = 0; + double mainSpace = 1200; + double rate = 1; + + ShopScrollCoordinator _shopCoordinator; + ShopScrollController _pageScrollController; + TabController _tabController; + final double _sliverAppBarInitHeight = 150.0; + final double _tabBarHeight = 50.0; + double _sliverAppBarMaxHeight; + + ShopScrollController _listScrollController1; + ShopScrollController _listScrollController2; + ShopScrollController _listScrollController3; + + GlobalKey stackKey = GlobalKey(); + GlobalKey endKey = GlobalKey(); + + List comments; + int _commentPage = 1; + int _commentPageCount = 1; + bool _commentLoadingFinish = false; + RefreshController _commentRefreshController = + RefreshController(initialRefresh: true); + + PanelController panelController = PanelController(); + SlidingUpPanel _slidUpShoppingCart; + + SliverPersistentHeader promotHeader; + + Carousel slidingGellery; + + // StreamSubscription onProductWillAddToCartSubscription; + // StreamSubscription onProductWillRemoveFromCartSubscription; + + double lastProductListScrollPositionPixel = 0; + + void _onCommentRefresh() { + if (_commentPage - 1 > 1) { + _commentPage -= 1; + } else { + _commentPage = 1; + } + _commentRefreshController.resetNoData(); + _loadComment(true); + } + + void _onCommentLoadMore() { + if (_commentPageCount > _commentPage) { + _commentPage += 1; + _loadComment(false); + } else { + _commentRefreshController.loadNoData(); + } + } + + _loadComment(bool isRefresh) { + _commentLoadingFinish = false; + HttpUtil.httpGet( + 'v1/get-comments', + queryParameters: { + 'page': _commentPage.toString(), + 'size': Constants.ORDERS_PER_PAGE.toString(), + }, + businessId: _business.id, + ).then((data) { + if (isRefresh) { + _commentRefreshController.refreshCompleted(); + } else { + _commentRefreshController.loadComplete(); + } + _commentPage = int.parse(data['_meta']['currentPage'].toString()); + _commentPageCount = int.parse(data['_meta']['pageCount'].toString()); + + if (mounted) { + setState(() { + comments = + (data['items'] as List).map((e) => Comment.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + + @override + Widget build(BuildContext context) { + if (_business == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + mediaQuery ??= MediaQuery.of(context); + screenWidth ??= mediaQuery.size.width; + screenHeight ??= mediaQuery.size.height; + statusBarHeight ??= mediaQuery.padding.top; + + _sliverAppBarMaxHeight ??= screenHeight - 150.0; + + _pageScrollController ??= _shopCoordinator + .pageScrollController(_sliverAppBarMaxHeight - _sliverAppBarInitHeight); + + _shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () { + return statusBarHeight + kToolbarHeight + _tabBarHeight; + }; + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + rate = mainSpace / 1200; + + refresh = false; + + _slidUpShoppingCart = SlidingUpPanel( + controller: panelController, + minHeight: 0.0, + maxHeight: 250.0, + isDraggable: false, + backdropEnabled: true, + slideDirection: SlideDirection.DOWN, + panel: ShoppingCartBar( + business: _business, + endKey: endKey, + barAtBottom: true, + hasPicture: true, + onEmptyCartListener: () { + Future.delayed(Duration(seconds: 1), () { + panelController.close(); + }); + }, + onPanelOpenCloseRequest: () { + if (panelController.isPanelOpen) { + panelController.close(); + } else { + panelController.open(); + } + }, + ), + ); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (_productCurrentPage == 1) { + _pageScrollController.animateTo( + 0, duration: Duration(milliseconds: 100), curve: Curves.easeInOut); + } + }); + + Row promotRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (var i = 0; i < _business.promoProducts.length; i++) { + promotRow.children.add(Container( + padding: EdgeInsets.only( + left: 10.0, + top: 10.0, + bottom: 10.0, + ), + width: 240.0, + height: 320.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: Container( + child: Util.showImage( + _business.promoProducts[i].imagePath, + ), + ), + onTap: () { + _showProductDetail(_business.promoProducts[i]); + }, + ), + Container( + child: Text( + _business.promoProducts[i].name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14.0), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShowPrice( + _business.promoProducts[i].price, + currencySign: '\$', + fontWeight: FontWeight.bold, + smallFontSize: 15, + largeFontSize: 24, + regularPrice: _business.promoProducts[i].regularPrice, + ), + Container( + child: AddRemoveButton( + product: _business.promoProducts[i], + business: _business, + addOnly: true, + ), + ) + ], + ) + ], + ), + )); + } + + if (promotRow.children.length > 0) { + promotHeader = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 350.0, + maxHeight: 350.0, + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, top: 5.0), + child: Text( + S.of(context).promotions, + style: TextStyle(fontSize: 15.0), + ), + ), + Container( + padding: EdgeInsets.only(right: 10.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: promotRow, + ), + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + ); + } else { + promotHeader = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 0.0, + maxHeight: 0.0, + child: Center( + child: Text(''), + )), + ); + } + + SliverPersistentHeader bulletin; + if (_business.bulletin != null && _business.bulletin.isNotEmpty) { + bulletin = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 30, + maxHeight: 100, + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: Container( + padding: EdgeInsets.only(top: 10, left: 16.0, right: 16.0, bottom: 10.0), + child: Text( + '${_business.bulletin}', + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + decoration: BoxDecoration( + border: Border.all( + color: Colors.red[500], + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + ); + } else { + bulletin = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 0.0, + maxHeight: 0.0, + child: Center( + child: Text(''), + )), + ); + } + + Widget listener = Listener( + onPointerUp: _shopCoordinator.onPointerUp, + onPointerSignal: (ps) { + if (ps is PointerScrollEvent) { + final newOffset = _pageScrollController.offset + ps.scrollDelta.dy; + if (ps.scrollDelta.dy.isNegative) { + _pageScrollController.jumpTo(max(0, newOffset)); + } else { + _pageScrollController + .jumpTo(min(_pageScrollController.position.maxScrollExtent, newOffset)); + } + } + }, + child: CustomScrollView( + controller: _pageScrollController, + physics: ClampingScrollPhysics(), + slivers: [ + SliverPersistentHeader( + pinned: true, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 94, + maxHeight: 94, + child: Column( + children: [ + NavigationBar( + title: S.of(context).shop, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).shop, null), + BreadCrumb(null, null, + item: Container( + margin: EdgeInsets.only(left: 20.0), + child: Row( + children: [ + Container( + child: Icon( + Icons.search, + size: 24, + color: Colors.blue, + ), + ), + Container( + child: Text( + S.of(context).search_product, + ), + ), + ], + ), + ), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (BuildContext context) { + return StoreProductSearch(_business); + })); + }, + ), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + shoppingCart: DesktopShoppingCartWidget( + business: _business, + onTap: () { + if (panelController.isPanelClosed) { + panelController.open(); + } + }, + ), + ), + ], + ), + ), + ), + promotHeader, + bulletin, + SliverPersistentHeader( + pinned: true, + floating: false, + delegate: _SliverAppBarDelegate2( + minHeight: _tabBarHeight, + maxHeight: _tabBarHeight, + child: Container( + color: Colors.white, + child: TabBar( + labelColor: new Color(0xFF3190E8), + unselectedLabelColor: new Color(0xFF666666), + indicatorColor: new Color(0xFF3190E8), + indicatorSize: TabBarIndicatorSize.label, + labelStyle: new TextStyle( + fontSize: 16.0, + ), + controller: _tabController, + tabs: [ + Tab( + text: S.of(context).products, + ), + Tab( + child: Badge( + badgeContent: Text( + '${_business.commentsCount}', + style: TextStyle(color: Colors.white, fontSize: 11.0), + ), + badgeColor: Colors.lightBlueAccent, + child: Text(S.of(context).comments), + ), + ), + ], + ), + ), + ), + ), + SliverFillRemaining( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + _buildMainContent(), + _buildCommentList(), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + ], + ), + ); + + Widget mainBody = listener; + + Widget widget = Scaffold( + key: _scaffoldKey, + body: WillPopScope( + child: mainBody, + onWillPop: () async { + return true; + }, + ), + bottomNavigationBar: BottomNav(), + ); + + List children = [ + Positioned( + top: 0.0, + right: 0.0, + bottom: 0.0, + left: 0.0, + child: widget, + ), + _slidUpShoppingCart, + ]; + + Stack stack = Stack( + key: stackKey, + children: [], + ); + + stack.children.addAll(children); + + if (!_business.isPublic) { + /* todo */ + // stack.children.add( + // Positioned( + // top: 0, + // right: 0, + // left: screenWidth - 100, + // bottom: screenHeight - 100, + // child: Image.asset( + // 'assets/images/under_renovation.png', + // width: 200.0, + // height: 200.0, + // fit: BoxFit.fill, + // ), + // ), + // ); + } + + return stack; + } + + SnackBar commentImageSnackBar(String url) { + return SnackBar( + elevation: 0.0, + action: SnackBarAction( + label: S.of(context).close, + onPressed: () { + _scaffoldKey.currentState.hideCurrentSnackBar(); + }, + ), + content: Container( + height: 260.0, + child: Util.showImage( + '$url', + width: 180.0, + height: 180.0, + fit: BoxFit.fill, + ), + ), + ); + } + + CupertinoActionSheet commentImageActionSheet(String url) { + return CupertinoActionSheet( + message: Container( + height: 280.0, + child: Util.showImage( + '$url', + fit: BoxFit.fill, + ), + ), + cancelButton: CupertinoActionSheetAction( + child: Text(S.of(context).close), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + + Widget _buildCommentList() { + Widget commentWidget = Center( + child: Text(S.of(context).no_comments_yet), + ); + if (comments != null && comments.length > 0) { + commentWidget = ListView.builder( + controller: _listScrollController3, + itemCount: comments.length, + itemBuilder: (BuildContext context, int position) { + Comment comment = comments[position]; + Row imageRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + if (comment.images.length > 0) { + for (ProductImage image in comment.images) { + imageRow.children.add( + GestureDetector( + child: Container( + padding: EdgeInsets.all(5.0), + child: Util.showImage( + 'https:${image.image}', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + ), + ), + onTap: () { +// _scaffoldKey.currentState.showSnackBar(commentImageSnackBar('https:${image.image}')); + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return commentImageActionSheet( + 'https:${image.image}'); + }); + }, + ), + ); + } + } + Widget replyWidget = SizedBox.shrink(); + if (comment.replyFromStore != null && + comment.replyFromStore.isNotEmpty) { + replyWidget = Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 10.0, bottom: 5.0), + child: Text( + S.of(context).response_from_store, + style: TextStyle( + fontSize: 12.0, + color: Colors.black38, + ), + ), + ), + Container( + padding: EdgeInsets.only(bottom: 16.0), + child: ReadMoreText( + comment.replyFromStore, + style: TextStyle( + fontSize: 13.0, + color: Colors.black54, + ), + trimLines: 2, + trimLength: 40, + colorClickableText: Colors.black, + trimMode: TrimMode.Line, + trimCollapsedText: S.of(context).show_more, + trimExpandedText: S.of(context).show_less, + ), + ) + ], + ), + ); + } + return Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 10.0), + margin: EdgeInsets.only(bottom: 5.0), + width: double.infinity, + child: ReadMoreText( + comment.content, + trimLines: 3, + trimLength: 66, + colorClickableText: Colors.blue, + trimMode: TrimMode.Length, + trimCollapsedText: S.of(context).show_more, + trimExpandedText: S.of(context).show_less, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: SmoothStarRating( + starCount: 5, + rating: comment.rating.toDouble(), + size: 12.0, + filledIconData: Icons.star, + color: Colors.green, + ), + ), + Container( + child: Text( + Utils.safeString(comment.contact) + + ' ' + + Utils.utcDatetimeStringToLocalDatetimeString( + context, comment.createdAt), + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Container( + child: imageRow, + ), + replyWidget, + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black12, + ), + ), + ), + ); + }); + } + + return SmartRefresher( + enablePullUp: true, + enablePullDown: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode) { + Widget footer; + if (mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center( + child: footer, + ), + ); + }, + ), + controller: _commentRefreshController, + onRefresh: _onCommentRefresh, + onLoading: _onCommentLoadMore, + child: commentWidget, + ); + } + + Widget _buildAppbar(BuildContext context) { + var addressRow = new Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + addressRow.children.add(new Icon( + Icons.location_on, + size: 14.0, + color: const Color(0xFFEEEEEE), + )); + var addressColumn = new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + addressColumn.children.add(new Text( + _business.address.addressLine1 + + (_business.address.addressLine2.isNotEmpty + ? ' ' + _business.address.addressLine2 + : ''), + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + )); + addressColumn.children.add(new Text( + _business.address.city + + ', ' + + _business.address.state + + ', ' + + _business.address.zip, + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + )); + + addressRow.children.add(addressColumn); + + var distanceRow = new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + if (_business.distanceInfo != null) { + distanceRow.children.add(new Icon( + Icons.directions_car, + size: 14.0, + color: Colors.white, + )); + distanceRow.children.add(new Text( + _business.distanceInfo.distance != null + ? _business.distanceInfo.distance.text + : '***' + ' / ', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + var duration = Duration( + seconds: (_business.distanceInfo.duration != null + ? _business.distanceInfo.duration.value + : 30) + + _business.shippingTime * 60); + var hours = duration.inHours.remainder(60); + var minutes = duration.inMinutes.remainder(60); + distanceRow.children.add(new Text( + hours > 0 ? S.of(context).hour_token(hours) : '', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + distanceRow.children.add(new Text( + minutes > 0 + ? (hours > 0 ? ' ' : '') + S.of(context).minute_token(minutes) + : '', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + distanceRow.children.add( + new Text( + ' / ' + + S.of(context).min_order_amount_token(_business.minPrice) + + ' ', + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ), + ); + } + + Widget widget = new PreferredSize( + preferredSize: new Size.fromHeight(90.0), + child: new Container( + height: 90.0, + padding: new EdgeInsets.all(10.0), + child: new Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + new Container( + width: 72.0, + height: 72.0, + margin: new EdgeInsets.only(right: 10.0), + child: Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: 0.0, + child: GestureDetector( + child: Util.showImage( + '${_business.picUrl}', + width: 72.0, + height: 72.0, + fit: BoxFit.fill, + ), + onTap: () { + // Utils.addRemoveFavorite(_business.id); + }, + ), + ), + Positioned( + top: 0.0, + left: 0.0, + right: -50.0, + bottom: -50.0, + child: Icon( + Icons.favorite, + color: Colors.transparent, + ), + ), + ], + ), + ), + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + addressRow, + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon( + Icons.phone, + size: 14.0, + color: Colors.white, + ), + new Text( + _business.phone, + style: new TextStyle( + fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ) + ], + ), + new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon( + Icons.av_timer, + size: 14.0, + color: Colors.white, + ), + new Text( + _business.openingTime[0].openTime + + ':00 - ' + + _business.openingTime[0].closeTime + + ':00', + style: new TextStyle( + fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ) + ], + ) + ], + ), + distanceRow, + ], + ), + ) + ], + ), + ), + ); + + List galleryPages = _buildBanners(context); + slidingGellery = Carousel( + height: 168.0, + pages: galleryPages, + autoPlay: true, + ); + + Widget column = new Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _business.bulletin.isNotEmpty ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: + EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.announcement, + size: 14.0, + color: Colors.white, + )), + Container( + width: mediaQuery.size.width - 30.0, + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + _business.bulletin.isEmpty ? '' : _business.bulletin, + softWrap: true, + style: new TextStyle( + fontSize: 12.0, color: const Color(0xFFDDDDDD)), +// overflow: TextOverflow.ellipsis, + ), + ), + ], + ) : SizedBox.shrink(), + _business.description.isNotEmpty ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.store, + size: 14.0, + color: Colors.white, + )), + Container( + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + S.of(context).store_introduction, + style: new TextStyle( + fontSize: 13.0, + color: const Color(0xFFDDDDDD), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Container( + padding: + EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), + child: Text( + _business.description, + style: TextStyle( + color: Colors.lightGreen, + fontSize: 12.0, + ), + ), + ), + ], + ) : SizedBox.shrink(), + galleryPages.isNotEmpty ? Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), + child: slidingGellery, + ) : SizedBox.shrink(), + _business.policy.isNotEmpty ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.store, + size: 14.0, + color: Colors.white, + )), + Container( + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + S.of(context).store_policy, + style: new TextStyle( + fontSize: 13.0, + color: const Color(0xFFDDDDDD), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), + child: SingleChildScrollView( + child: Text( + _business.policy, + softWrap: true, + style: TextStyle( + fontSize: 10.0, + color: const Color(0xFFEEEEEE), + ), + ), + ), + ), + ], + ) : SizedBox.shrink(), + ], + ); + + return Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Util.showImage( + _business.bannerImageUrl, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Container( + color: Color(0x88000000), + ), + ), + Positioned( + top: kToolbarHeight + 20.0, + left: 0, + right: 0, + child: widget, + ), + Positioned( + top: kToolbarHeight + 20.0 + 90.0, + left: 0, + right: 0, + bottom: 0, + child: Container( + child: SingleChildScrollView( + child: column, + ), + ), + ), + ], + ); + } + + List _buildBanners(BuildContext context) { + var pages = []; + for (var i = 0; i < _business.slideImages.length; i++) { + pages.add(new GestureDetector( + child: new Container( + child: Util.showImage( + _business.slideImages[i].imageUrl, + fit: BoxFit.cover, + ), + ), + onTap: () {}, + )); + } + return pages; + } + + Widget _buildMainContent() { + return new Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildCategories(), + Expanded( + child: _buildProducts(), + ) + ], + ); + } + + Widget _buildCategories() { + return new Container( + width: 200.0, + color: new Color(0xFFF5F5F5), + child: new SizedBox.expand( + child: Listener( + onPointerSignal: (ps) { + if (ps is PointerScrollEvent) { + final newOffset = _listScrollController2.offset + ps.scrollDelta.dy; + if (ps.scrollDelta.dy.isNegative) { + _listScrollController2.jumpTo(max(0, newOffset)); + } else { + _listScrollController2 + .jumpTo(min(_listScrollController2.position.maxScrollExtent, newOffset)); + } + } + }, + child: ListView.builder( + physics: ClampingScrollPhysics(), + controller: _listScrollController2, + itemCount: _categoryProducts == null ? 0 : _categoryProducts.length, + itemBuilder: (BuildContext context, int i) { + CategoryProducts cp = _categoryProducts[i]; + int qtyInCategory = 0; + CartInfo cartInfo = + Utils.getCartInfoByBusiness(store.state.cartInfos, _business); + if (cartInfo != null && + cartInfo.businessInfo.id == _business.id && + cartInfo.productList != null) { + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.categoryId == cp.id) { + qtyInCategory += cartInfo.productList[i].quantity.ceil(); + } + } + } + Row categoryRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + categoryRow.children.add( + Expanded( + flex: 1, + child: Container( + margin: EdgeInsets.only(top: 5.0, bottom: 5.0), + child: Text( + cp.name, + style: TextStyle( + fontSize: 13.0, + fontWeight: (cp.id == Constants.FEATURED_PRODUCT_ID || + cp.id == Constants.HOT_SALE_ID) + ? FontWeight.bold + : FontWeight.normal, + color: (cp.id == Constants.HOT_SALE_ID) + ? Colors.redAccent + : ((cp.id == Constants.FEATURED_PRODUCT_ID) + ? Colors.lightGreen + : Colors.black87)), +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 2, + ), + ), + ), + ); + if (qtyInCategory > 0) { + categoryRow.children.add( + new Badge( + badgeContent: Text( + '$qtyInCategory', + style: TextStyle( + color: Colors.white, + fontSize: 11.0, + ), + ), + animationType: BadgeAnimationType.scale, + ), + ); + } else { + categoryRow.children.add(SizedBox.shrink()); + } + return new GestureDetector( + child: new Container( + height: 70.0, + padding: new EdgeInsets.symmetric(horizontal: 10.0), + decoration: new BoxDecoration( + color: _categoryIndex == i ? Style.backgroundColor : null, + border: new Border( + bottom: + new BorderSide(color: new Color(0xFFEBEBEB)))), + child: categoryRow, + ), + onTap: () => _selectCategory(i), + ); + } + ), + ), + ), + ); + } + + void _selectCategory(int index) { + if (displayProductByCategoryClick && _categoryProducts[index].id > 0) { + categoryId = _categoryProducts[index].id; + loadProducts(); + return; + } + double height = 0.0; + double d = getProductCountInRow(); + for (int i = 0; i < index; ++i) { + height += _categoryDescHeight + + (_categoryProducts[i].products.length / d).ceil() * _productHeight; + } + if (height > _listScrollController1.position.maxScrollExtent) { + height = _listScrollController1.position.maxScrollExtent; + } + _categoryIndexChange = true; + _listScrollController1 + .animateTo(height, + duration: new Duration( + microseconds: 200, + ), + curve: Curves.linear) + .then((value) { + _categoryIndexChange = false; + }); + print( + 'height: $height, index: $index, ${_categoryProducts[0].products.length}'); + if (mounted) { + setState(() { + _categoryIndex = index; + }); + } + } + + CategoryProducts getCategoryProductByCategoryId(int cid) { + for (CategoryProducts cp in _categoryProducts) { + if (cp.id == cid) { + return cp; + } + } + return null; + } + + void _resetProductListScroll() { + if (_listScrollController1 != null && + _listScrollController1.positions.isNotEmpty) { + _listScrollController1.animateTo( + 0, + duration: new Duration( + microseconds: 200, + ), + curve: Curves.linear, + ); + } + } + + int _getCategoryIndexByRightScrollHeight(double height) { + double cHeight = 0.0; + double d = getProductCountInRow(); + if (height > 0) { + for (int i = 0; i < _categoryProducts.length; ++i) { + double categoryHeight = _categoryDescHeight + + (_categoryProducts[i].products.length / d).round() * _productHeight; + if (height >= cHeight && height < cHeight + categoryHeight) { + return i; + } + cHeight += categoryHeight; + } + } + if (height > cHeight) { + return _categoryProducts.length - 1; + } + return -1; + } + + void _setCategoryByProductScrolledIndex(int index) { + if (index == _categoryIndex) { + return; + } + double height = 0.0; + for (int i = 0; i < index; ++i) { + height += _categoryHeight; + } + if (height > _listScrollController2.position.maxScrollExtent) { + height = _listScrollController2.position.maxScrollExtent; + } + _listScrollController2.animateTo(height, + duration: new Duration( + milliseconds: 200, + ), + curve: Curves.linear); + if (mounted) { + setState(() { + _categoryIndex = index; + }); + } + } + + int numCategoriesHasProducts() { + int num = 0; + for (CategoryProducts cp in _categoryProducts) { + if (cp.products.length > 0) { + num += 1; + } + } + return num; + } + + Widget _buildProducts() { + return new SizedBox.expand( + child: NotificationListener( + onNotification: (ScrollUpdateNotification notification) { + if (!displayProductByCategoryClick) { + if (_categoryIndexChange) { + return; + } + int index = _getCategoryIndexByRightScrollHeight( + notification.metrics.pixels); + _setCategoryByProductScrolledIndex(index); + } + return; + }, + child: Listener( + onPointerSignal: (ps) { + if (ps is PointerScrollEvent) { + final newOffset = _listScrollController1.offset + ps.scrollDelta.dy; + if (ps.scrollDelta.dy.isNegative) { + _listScrollController1.jumpTo(max(0, newOffset)); + } else { + _listScrollController1 + .jumpTo(min(_listScrollController1.position.maxScrollExtent, newOffset)); + } + } + }, + child: ListView.builder( + physics: ClampingScrollPhysics(), + controller: _listScrollController1, + // itemCount: displayProductByCategoryClick ? 1 : (_categoryProducts == null ? 0 : _categoryProducts.length), + itemCount: + _categoryProducts == null ? 0 : numCategoriesHasProducts(), + itemBuilder: (BuildContext context, int i) { + CategoryProducts cp; + cp = _categoryProducts[i]; + + int index = -1; + if (displayProductByCategoryClick) { + if (categoryId > 0) { + for (var j = 0; j < _categoryProducts.length; j++) { + if (_categoryProducts[j].id == categoryId) { + cp = _categoryProducts[j]; + index = j; + break; + } + } + if (cp == null) { + index = 0; + cp = _categoryProducts[0]; + } + } + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _setCategoryByProductScrolledIndex(index); + }); + } + + if (cp.products.length == 0) { + return SizedBox.shrink(); + } + + Column col = new Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [], + ); + col.children.add(new Container( + height: _categoryDescHeight, + padding: new EdgeInsets.symmetric(horizontal: 10.0), + color: new Color(0xFFF5F5F5), + child: new Row( + children: [ + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + textBaseline: TextBaseline.alphabetic, + children: [ + new Padding( + padding: new EdgeInsets.only(right: 5.0), + child: new Text( + cp.name, + style: new TextStyle( + fontSize: 16.0, + fontWeight: + (cp.id == Constants.FEATURED_PRODUCT_ID || + cp.id == Constants.HOT_SALE_ID) + ? FontWeight.bold + : FontWeight.normal, + color: (cp.id == Constants.HOT_SALE_ID) + ? Colors.redAccent + : ((cp.id == + Constants.FEATURED_PRODUCT_ID) + ? Colors.lightGreen + : Colors.black87)), + overflow: TextOverflow.ellipsis, + ), + ), + new Visibility( + visible: cp.description.isNotEmpty, + child: new Text( + cp.description, + overflow: TextOverflow.ellipsis, + style: new TextStyle( + fontSize: 10.0, + color: new Color(0xFF999999)), + ), + replacement: const SizedBox.shrink()), + ], + )) + ], + ))); + + var it = cp.products.iterator; + for (int i = 0; i < cp.products.length; i++) { + var r1 = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + double d = getProductCountInRow(); + int c = int.parse(d.toString()); + while (d > 0) { + if (it.moveNext()) { + r1.children.add( + Container( + width: (mainSpace - 200) / c, + child: ProductItem( + product: it.current, + business: _business, + ), + ), + ); + } + d -= 1; + } + col.children.add(r1); + } + + // for (var p in cp.products) { + // var pStack = new Stack( + // children: [], + // ); + // + // pStack.children.add(new ProductItem( + // product: p, + // business: _business, + // )); + // + // col.children.add(pStack); + // } + if (displayProductByCategoryClick) { + if (displayProductByCategoryClickIndicator.isNotEmpty) { + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + displayProductByCategoryClickIndicator, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } else if (categoryId > 0) { + if (cp.products.length < Constants.ORDERS_PER_PAGE) { + lastProductListScrollPositionPixel = 0; + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + S + .of(context) + .end_of_the_list, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } else { + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + S + .of(context) + .pull_up_to_load_more, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } + } + } + return col; + } + ), + ), + ), + ); + } + + double getProductCountInRow() { + double d = 2; + if (mainSpace >= 800 && mainSpace < 1000) { + d = 3; + } else if (mainSpace >= 1000) { + d = 4; + } + return d; + } + + @override + void initState() { + super.initState(); + + _shopCoordinator = ShopScrollCoordinator(); + _tabController = TabController(vsync: this, length: 2); + + refresh = false; + + _listScrollController1 = _shopCoordinator.newChildScrollController(); + _listScrollController2 = _shopCoordinator.newChildScrollController(); + _listScrollController3 = _shopCoordinator.newChildScrollController(); + + _listScrollController1.addListener(() { + if (_listScrollController1.position.atEdge) { + if (_listScrollController1.position.pixels == 0) { + print('product list at top'); + } else { + print('product list at bottom, $displayProductByCategoryClick, ' + '$_productCurrentPage, ' + 'pixels: ${_listScrollController1.position.pixels}'); + + if (displayProductByCategoryClick && _productCurrentPage != 0 + && _listScrollController1.position.pixels > lastProductListScrollPositionPixel) { + lastProductListScrollPositionPixel = _listScrollController1.position.pixels; + loadMoreProducts(); + } + } + } + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + refresh = true; + }); + } + }); + + // onProductWillAddToCartSubscription = + // eventBus.on().listen((event) { + // if (mounted) { + // + // CartInfo cartInfo = + // Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + // + // if (cartInfo == null || cartInfo.productList.length == 0) { + // cartInfo = CartInfo(); + // cartInfo.id = 0; + // cartInfo.amountPaid = 0.0; + // cartInfo.businessInfo = event.business; + // cartInfo.extraFeeList = []; + // cartInfo.discountList = []; + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList = [lineItem]; + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // if (event.product.productAttributes.length > 0) { + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList.add(lineItem); + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // int found = -1; + // for (var i = 0; i < cartInfo.productList.length; i++) { + // if (event.product.id == cartInfo.productList[i].product.id) { + // found = i; + // break; + // } + // } + // if (found == -1) { + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList.add(lineItem); + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // if (cartInfo.productList[found].quantity + 1.0 > + // event.product.leftNum) { + // Fluttertoast.showToast( + // msg: S.of(context).product_insufficient, + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // backgroundColor: Colors.red, + // textColor: Colors.white); + // } else { + // cartInfo.productList[found].quantity += 1; + // store.dispatch(new UpdateCartInfo( + // Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } + // } + // } + // } + // } + // }); + // onProductWillRemoveFromCartSubscription = + // eventBus.on().listen((event) { + // if (mounted) { + // CartInfo cartInfo = + // Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + // if (cartInfo != null) { + // if (cartInfo.productList.length > 0) { + // if (event.productListIndex != -1) { + // if (cartInfo.productList[event.productListIndex].quantity <= 1) { + // cartInfo.productList.removeAt(event.productListIndex); + // } else { + // cartInfo.productList[event.productListIndex].quantity -= 1; + // } + // } else { + // int productListIndex = -1; + // for (var i = 0; i < cartInfo.productList.length; i++) { + // if (cartInfo.productList[i].product.id == event.product.id) { + // productListIndex = i; + // break; + // } + // } + // if (productListIndex != -1) { + // if (cartInfo.productList[productListIndex].quantity <= 1) { + // cartInfo.productList.removeAt(productListIndex); + // } else { + // cartInfo.productList[productListIndex].quantity -= 1; + // } + // } + // } + // } + // + // if (cartInfo.productList.length <= 0) { + // store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList( + // store.state.cartInfos, cartInfo))); + // } else { + // store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // } + // eventBus.fire(new OnCartInfoUpdated()); + // } + // } + // }); + + loadProducts(); + } + + void loadProducts() { + if (categoryId < 0) { + return; + } + if (_categoryProducts != null) { + _isLoading = true; + Utils.showLoadingDialog(context, message: S.of(context).loading); + } + lastProductListScrollPositionPixel = 0; + _productCurrentPage = 1; + Utils.loadProducts( + widget.businessId, categoryId, _productCurrentPage, false, + (value) { + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + if (mounted) { + setState(() { + displayProductByCategoryClick = + value['display_product_by_category_click']; + displayProductByCategoryClickIndicator = ''; + _business = Business.fromJson(value['business']); + _categoryProducts = (value['category_products'] as List) + .map((i) => CategoryProducts.fromJson(i)) + .toList(); + + _featuredProducts = (value['featured_products'] as List) + .map((i) => Product.fromJson(i)) + .toList(); + _hotSaleProducts = (value['hot_sale_products'] as List) + .map((i) => Product.fromJson(i)) + .toList(); + _prompts = value['prompt'] as List; + + if (_hotSaleProducts.length > 0) { + CategoryProducts hs = CategoryProducts( + Constants.HOT_SALE_ID, + widget.businessId, + S.of(context).hot_sale, + '', + '', + _hotSaleProducts); + _categoryProducts.insert(0, hs); + } + + if (_featuredProducts.length > 0) { + CategoryProducts fe = CategoryProducts( + Constants.FEATURED_PRODUCT_ID, + widget.businessId, + S.of(context).featured_product, + '', + '', + _featuredProducts); + _categoryProducts.insert(0, fe); + } + + checkActionAndClose(context); + + if (displayProductByCategoryClick) { + _resetProductListScroll(); + } + }); + } + }, + (error) { + print('error: $error'); + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + } + ); + } + + void loadMoreProducts() { + if (_productCurrentPage == 0 || categoryId <= 0) { + return; + } + _productCurrentPage += 1; + Utils.loadProducts(widget.businessId, categoryId, _productCurrentPage, true, + (value) { + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + if (mounted) { + setState(() { + List moreCategoryProducts = + (value as List).map((i) => CategoryProducts.fromJson(i)).toList(); + if (moreCategoryProducts.isEmpty) { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } else { + if (moreCategoryProducts[0].products.length < Constants.ORDERS_PER_PAGE) { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } else { + displayProductByCategoryClickIndicator = + S.of(context).pull_up_to_load_more; + } + CategoryProducts currentCp = + getCategoryProductByCategoryId(categoryId); + if (currentCp != null) { + currentCp.products.addAll(moreCategoryProducts[0].products); + } else { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } + } + }); + } + }, + (error) { + print('error: $error'); + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + } + ); + } + + CartLineItem _newCartLineItem( + {int id, + double price, + Product product, + String name, + String description, + double quantity}) { + CartLineItem lineItem = CartLineItem(); + lineItem.unitPrice = price; + lineItem.product = product; + lineItem.name = product.name; + lineItem.description = description; + lineItem.quantity = quantity; + return lineItem; + } + + @override + void dispose() { + print('Shop disposed!!!'); + // onProductWillAddToCartSubscription?.cancel(); + // onProductWillRemoveFromCartSubscription?.cancel(); + _tabController?.dispose(); + _pageScrollController?.dispose(); + _commentRefreshController?.dispose(); + _listScrollController1?.dispose(); + _listScrollController2?.dispose(); + _listScrollController3?.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + void _showProductDetail(Product p) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDetailPage( + product: p, + business: _business, + )), + ); + } + + void checkActionAndClose(BuildContext mainContext) { + // if (widget.action == Constants.ACTION_COUPON && store.state.referrerContactId != null && store.state.referrerContactId > 0) { + // showDialog( + // context: context, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text(S.of(context).coupon), + // content: Text( + // S.of(context).a_friend_send_you_a_coupon + // ), + // actions: [ + // FlatButton( + // child: Text(S.of(context).no_thanks), + // onPressed: () { + // Navigator.of(context).pop(); + // checkClose(mainContext, _business); + // }, + // ), + // RaisedButton( + // child: Text( + // S.of(context).yes, + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // color: Theme.of(context).primaryColor, + // onPressed: () { + // Utils.showLoadingDialog(mainContext, message: S.of(context).processing); + // HttpUtil.httpGet('v1/get-referrer-coupon', + // businessId: _business.id, + // queryParameters: { + // 'referrer_contact_id': store.state.referrerContactId, + // } + // ).then((data) { + // Navigator.of(context).pop(); + // Coupon coupon = Coupon.fromJson(data); + // showDialog( + // context: mainContext, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text( + // S.of(context).congratulation, + // ), + // content: Text( + // S.of(context).got_a_coupon_token(coupon.valueAmount) + // ), + // actions: [ + // FlatButton( + // child: Text( + // S.of(context).close, + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // Navigator.of(context).pop(); + // }, + // ), + // ], + // ); + // } + // ); + // }).catchError((error) { + // Navigator.of(context).pop(); + // Navigator.of(context).pop(); + // if (error is DioError) { + // if (error.response.data['code'] == 1013) { + // showDialog( + // context: mainContext, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text(S.of(context).error), + // content: Text(error.response.data['message']), + // actions: [ + // FlatButton( + // child: Text(S.of(context).cancel), + // onPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // RaisedButton( + // color: Theme.of(context).primaryColor, + // child: Text( + // S.of(context).login, + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // store.dispatch(UpdateRedirectRoute('/shop/${widget.businessId}/${widget.deviceId}/${widget.tableNo}/${widget.action}')); + // Routes.router.navigateTo(context, '/login'); + // }, + // ), + // ], + // ); + // } + // ); + // } else { + // Utils.showMessageDialog(mainContext, error); + // } + // } else { + // Utils.showMessageDialog(mainContext, error); + // } + // }); + // }, + // ) + // ], + // ); + // } + // ); + // } else { + // checkClose(context, _business); + // } + } + + void checkClose(BuildContext context, Business business) { + if (checkCloseFlag) { + return; + } + checkCloseFlag = true; + if (business.forceClose) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text( + S.of(context).store_closed, + ), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(0); + }, + ) + ], + ); + }); + } else { + DateTime now = DateTime.now(); + if (!(now.hour >= business.todayOpen && + now.hour <= business.todayClose)) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( +// title: Text( +// S.of(context).warning, +// ), + content: Container( + height: 170.0, + child: Center( + child: Column( + children: [ + Container( + child: Icon( + Icons.add_shopping_cart, + size: 100.0, + color: Colors.black12, + ), + ), + Container( + child: Text( + S.of(context).closed, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: Text( + S.of(context).book_now_delivery_later_token( + business.todayOpen), + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + ), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(0); + }, + ) + ], + ); + }); + } else { + showPrompt(0); + } + } + } + + void showPrompt(int idx) { + if (_prompts.length > idx) { + Map _prompt = _prompts[idx] as Map; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(_prompt['title']), + content: Text(_prompt['message']), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(idx + 1); + }, + ), + ], + ); + }); + } + } + + @override + bool get wantKeepAlive => true; +} + +class _SliverAppBarDelegate2 extends SliverPersistentHeaderDelegate { + _SliverAppBarDelegate2({ + @required this.minHeight, + @required this.maxHeight, + @required this.child, + }); + + final double minHeight; + final double maxHeight; + final Widget child; + + @override + double get minExtent => this.minHeight; + + @override + double get maxExtent => max(maxHeight, minHeight); + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return SizedBox.expand(child: child); + } + + @override + bool shouldRebuild(_SliverAppBarDelegate2 oldDelegate) { + return maxHeight != oldDelegate.maxHeight || + minHeight != oldDelegate.minHeight || + child != oldDelegate.child; + } +} diff --git a/lib/widgets/desktop/desktop_shopping_cart_widget.dart b/lib/widgets/desktop/desktop_shopping_cart_widget.dart new file mode 100644 index 0000000..db3f00a --- /dev/null +++ b/lib/widgets/desktop/desktop_shopping_cart_widget.dart @@ -0,0 +1,67 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../models/business.dart'; +import '../../models/cart_info.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; + +class DesktopShoppingCartWidget extends StatefulWidget { + final Business business; + final Function onTap; + + DesktopShoppingCartWidget({@required this.business, this.onTap}); + + @override + State createState() => DesktopShoppingCartWidgetState(); +} + +class DesktopShoppingCartWidgetState extends State { + CartInfo cartInfo; + double totalPrice = 0.0; + + @override + Widget build(BuildContext context) { + totalPrice = 0.0; + cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business); + if (cartInfo != null && cartInfo.businessInfo.id == widget.business.id) { + totalPrice = cartInfo.getTotalPrice(); + } + Row row = Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 4.0, top: 8.0), + child: Icon( + Icons.shopping_basket_outlined, + size: 24, + color: Colors.blue, + ), + ), + Container( + padding: EdgeInsets.only(left: 4.0, right: 16.0, top: 8.0), + child: Text( + '\$${totalPrice.toStringAsFixed(2)}', + style: TextStyle( + color: totalPrice > 0 ? Colors.red : Colors.black45, + fontWeight: totalPrice > 0 ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ], + ); + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + child: row, + width: 160.0, + ), + onTap: widget.onTap, + ), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_store_product_search.dart b/lib/widgets/desktop/desktop_store_product_search.dart new file mode 100644 index 0000000..d70ec08 --- /dev/null +++ b/lib/widgets/desktop/desktop_store_product_search.dart @@ -0,0 +1,173 @@ + +import 'package:dio/dio.dart'; +import 'package:flappy_search_bar/flappy_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_wisetronic/pages/product_detail_page.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_product_item.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; + +class DesktopStoreProductSearch extends StatefulWidget { + final Key key; + final Business business; + + const DesktopStoreProductSearch(this.business, {this.key}); + + @override + State createState() { + return DesktopStoreProductSearchState(); + } + +} + +class DesktopStoreProductSearchState extends State { + + double sideSpace = 0; + double mainSpace = 1200; + double rate = 1; + + int page = 1; + int numPerPage = 10; + int lastResultSize = 0; + double lastBottomPosition = 0; + String lastKeyword = ''; + + SearchBarController _controller = SearchBarController(); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + rate = mainSpace / 1200; + + return SafeArea( + child: Container( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.all(16.0), + child: NotificationListener( + child: SearchBar( + searchBarController: _controller, + minimumChars: 2, + hintText: S.of(context).enter_product_keyword, + cancellationWidget: Text( + S.of(context).cancel, + ), + onSearch: search, + onItemFound: (Product searchProduct, int index) { + return DesktopProductItem( + product: searchProduct, + business: widget.business, + horizontal: true, + ); + }, + onError: (error) { + return Center( + child: Text("$error"), + ); + }, + emptyWidget: Center( + child: Text( + S.of(context).empty_result_change_keyword, + ), + ), + ), + onNotification: (notification) { + if (notification.metrics.atEdge) { + if (page != 0 && lastResultSize >= numPerPage && notification.metrics.pixels != 0) { + if (notification.metrics.pixels > lastBottomPosition) { + lastBottomPosition = notification.metrics.pixels; + page += 1; + _controller.replayLastSearch(); + } + } + } + return true; + }, + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ), + ); + } + + Future> search(String keyword) async { + if (lastKeyword != keyword) { + page = 1; + } + lastKeyword = keyword; + var result = await HttpUtil.httpGet('v1/search-store-product2', + queryParameters: { + 'store_id': widget.business.id, + 'keyword': keyword, + 'page': page, + 'num_per_page': numPerPage, + }, + returnError: true, + ); + if (result is DioError) { + if (result.response != null) { + throw RuntimeError(result.response.data); + } else { + throw RuntimeError(result.message); + } + } else if (result != null && result is List) { + lastBottomPosition = 0; + var r = result.map((e) => Product.fromJson(e)).toList(); + lastResultSize = r.length; + if (lastResultSize < numPerPage) { + page = 1; + } + return r; + } + return []; + } + + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + setState(() {}); + } + }); + } + + void _showProductDetail(Product p) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDetailPage( + product: p, + business: widget.business, + )), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_stripe_pay_web.dart b/lib/widgets/desktop/desktop_stripe_pay_web.dart new file mode 100644 index 0000000..0131180 --- /dev/null +++ b/lib/widgets/desktop/desktop_stripe_pay_web.dart @@ -0,0 +1,250 @@ + + +import 'package:flutter/material.dart'; +import 'package:stripe_sdk/stripe_sdk.dart'; +import 'package:stripe_sdk/stripe_sdk_ui.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; +import '../../widgets/general/navigationbar.dart'; + + +class DesktopStripePayWeb extends StatefulWidget { + final Key key; + final Order order; + final PaymentPlatform paymentPlatform; + final StripePaymentMethod stripePaymentMethod; + const DesktopStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); + + @override + State createState() { + return DesktopStripePayWebState(); + } + +} + +class DesktopStripePayWebState extends State { + + GlobalKey _scaffoldKey = GlobalKey(); + final formKey = GlobalKey(); + final card = StripeCard(); + + CardForm form; + + bool isSubmitting; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget body = Center( + child: Icon( + Icons.credit_card, + size: 40.0, + color: Colors.black26, + ), + ); + if (widget.stripePaymentMethod == null) { + form = CardForm(card: card, formKey: formKey,); + body = ListView( + children: [ + form, + Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, right: 16.0), + alignment: Alignment.centerRight, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + primary: Theme.of(context).primaryColor, + ), + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + if (formKey.currentState.validate()) { + formKey.currentState.save(); + _paymentRequestWithCard(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + messageSnackBar( + context, S.of(context).this_credit_card_is_invalid + ) + ); + } + }, + ), + ), + ], + ); + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (widget.stripePaymentMethod != null) { + _paymentWithPaymentMethod(context); + } + }); + + return Scaffold( + key: _scaffoldKey, + appBar: NavigationBar( + title: S.of(context).blog, + back: true, + breadCrumbs: [ + BreadCrumb(S.of(context).add_credit_card, null), + ], + breadCrumbHeight: Constants.BREADCRUMB_HEIGHT, + ), + body: Row( + children: [ + Container(width: sideSpace,), + Expanded(child: body,), + Container(width: sideSpace,), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + isSubmitting = false; + StripeApi.init(widget.paymentPlatform.publishableKey); + } + + SnackBar messageSnackBar(BuildContext context, String message) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text( + message, + style: TextStyle( + color: Colors.white, + ), + )], + ); + return SnackBar( + content: Container( + height: 45.0, + child: column, + ), + action: SnackBarAction( + label: S.of(context).ok, + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ); + } + + _paymentWithPaymentMethod(BuildContext context) async { + Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, + widget.stripePaymentMethod.paymentMethodId, + widget.stripePaymentMethod.paymentMethodType, (response) async { + + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + await StripeApi.instance.confirmPaymentIntent( + response.data[Constants.STRIPE_CLIENT_SECRET], + data: { + 'payment_method': response.data['payment_method'], + }, + ).then((result2) { + if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + widget.stripePaymentMethod.paymentMethodId, + result2['id'], (response) { + if (isSubmitting) { + Navigator.of(context).pop(); + } + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog)); + + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + _paymentRequestWithCard(BuildContext context) async { + isSubmitting = true; + Utils.showSubmitDialog(context); + await StripeApi.instance.createPaymentMethodFromCard(card) + .then((result) { + Utils.stripePaymentIntent(widget.order, null, result['id'], result['type'], (response) async { + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + await StripeApi.instance.confirmPaymentIntent( + response.data[Constants.STRIPE_CLIENT_SECRET], + data: { + 'payment_method': response.data['payment_method'], + } + ).then((result2) { + if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + result['id'], // payment method id + result2['id'], (response) { // payment intent id + if (isSubmitting) { + Navigator.of(context).pop(); + } + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog), + cardBrand: result['card']['brand'], + cardCountry: result['card']['country'], + cardExpMonth: result['card']['exp_month'], + cardExpYear: result['card']['exp_year'], + cardFunding: result['card']['funding'], + cardLast4: result['card']['last4'], + ); + }).catchError(showErrorDialog); + } + + void showErrorDialog(dynamic error) { + if (isSubmitting) { + Navigator.of(context).pop(); + isSubmitting = false; + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_tutorials.dart b/lib/widgets/desktop/desktop_tutorials.dart new file mode 100644 index 0000000..3119421 --- /dev/null +++ b/lib/widgets/desktop/desktop_tutorials.dart @@ -0,0 +1,127 @@ + +import '../../constants.dart'; +import '../../utils/iframe_web.dart' if (dart.library.io) '../../utils/fake_iframe_web.dart'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +class DesktopTutorials extends StatefulWidget { + const DesktopTutorials({Key key}) : super(key: key); + + @override + State createState() { + return DesktopTutorialsState(); + } + +} + +class DesktopTutorialsState extends State { + double sideSpace = 0; + double mainSpace = 1200; + + InAppWebViewController webView; + String url = ""; + double progress = 0; + bool isLoadStop = false; + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (kIsWeb) { + return Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: IFrameWeb( + width: double.maxFinite.toString(), + height: double.maxFinite.toString(), + src: Constants.TUTORIAL_URL, + ), + ), + ], + ), + ), + Container( + width: sideSpace, + ), + ], + ); + } else { + print('progress: $progress'); + return Stack( + children: [ + Row( + children: [ + Container( + width: sideSpace, + ), + Expanded( + child: InAppWebView( + initialUrlRequest: URLRequest(url: Uri.parse(Constants.TUTORIAL_URL)), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + + ), + ), + onWebViewCreated: (InAppWebViewController controller) { + webView = controller; + }, + onLoadStart: (controller, url) { + if (mounted) { + setState(() { + this.url = url.toString(); + this.isLoadStop = false; + }); + } + }, + onLoadStop: (controller, url) async { + if (mounted) { + setState(() { + this.url = url.toString(); + this.isLoadStop = true; + }); + } + }, + onProgressChanged: (InAppWebViewController controller, int progress) { + if (mounted) { + setState(() { + if (this.progress < 1.0) { + this.isLoadStop = false; + } else { + this.isLoadStop = true; + } + this.progress = progress / 100; + }); + } + }, + ), + ), + Container( + width: sideSpace, + ), + ], + ), + isLoadStop ? Container() : + Center( + child: CircularProgressIndicator(), + ), + ], + ); + } + } + +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_user_profile.dart b/lib/widgets/desktop/desktop_user_profile.dart new file mode 100644 index 0000000..e0d9b88 --- /dev/null +++ b/lib/widgets/desktop/desktop_user_profile.dart @@ -0,0 +1,480 @@ + +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +class DesktopUserProfile extends StatefulWidget { + final Key key; + + const DesktopUserProfile({this.key}) + : super(key: key); + + @override + State createState() { + return DesktopUserProfileState(); + } +} + +class DesktopUserProfileState extends State { + User _user; + + bool _showProgress; + double _progress; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + Widget view = SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 20.0, top: 20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 10.0), + child: Text( + S.of(context).basic_info, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey + ), + ), + ), + Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: -85.0, + child: Visibility( + visible: _showProgress, + child: LinearPercentIndicator( + lineHeight: 10.0, + percent: _progress, + backgroundColor: Colors.transparent, + progressColor: Colors.blue, + linearStrokeCap: LinearStrokeCap.butt, + ), + ), + ), + Positioned( + child: GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).avatar, + style: TextStyle( + + ), + ), + ), + Container( + child: Util.showImage('https:${_user.avatarUrl}', + width: 60, + height: 60, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon( + Icons.account_circle, + size: 60.0, + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + _getAvatar(context); + }, + ), + ), + ], + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).nick_name, + style: TextStyle( + + ), + ), + ), + Container( + child: Text( + _user.nickname, + style: TextStyle( + color: Colors.grey + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + _changeNickname(context); + }, + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).my_addresses, + style: TextStyle( + + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 14.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/my-addresses/-1'); + }, + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 36.0, bottom: 10.0), + child: Text( + S.of(context).account_binding, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.phone_iphone, + size: 16.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).mobile_number, + style: TextStyle( + + ), + ), + ) + ], + ), + ), + Container( + child: Text( + _user.mobile != null && _user.mobile.isNotEmpty ? Utils.safePhoneNumber(_user.mobile) : S.of(context).not_binding, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/change-mobile-email/1'); + }, + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.mail_outline, + size: 16.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).email, + style: TextStyle( + + ), + ), + ) + ], + ), + ), + Container( + child: Text( + _user.email != null && _user.email.isNotEmpty ? Utils.safePhoneNumber(_user.email) : S.of(context).not_binding, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/change-mobile-email/0'); + }, + ), + ], + ), + ), + Container( + width: sideSpace, + ) + ], + ), + ); + return view; + } + + @override + void initState() { + super.initState(); + if (store.state.user == null) { + Routes.router.navigateTo(context, '/login', replace: true); + } else { + print(store.state.user.toString()); + setState(() { + _user = store.state.user; + _showProgress = false; + _progress = 0.0; + }); + } + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _showProgress = event.showProgress; + _progress = event.progress; + }); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + void _getAvatar(BuildContext mainContext) { + showDialog( + context: mainContext, + barrierDismissible: true, + builder: (BuildContext context) { + return Util().getPicture(mainContext, _user); + } + ); + } + + void _updateCurrentUser() { + store.dispatch(new UpdateCurrentUser(_user)); + eventBus.fire(new OnCurrentUserUpdated()); + } + + void _changeNickname(BuildContext context) { + final GlobalKey _formKey = GlobalKey(); + final nicknameController = TextEditingController(); + + nicknameController.text = _user.nickname; + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).change_nickname, + ), + content: Container( + child: Form( + key: _formKey, + child: TextFormField( + controller: nicknameController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).enter_new_nickname, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).nickname_is_required; + } + return null; + }, + ), + ), + ), + actions: [ + FlatButton( + child: Text( + S.of(context).cancel + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + S.of(context).submit_to_change, + ), + onPressed: () { + final FormState form = _formKey.currentState; + if (form.validate()) { + Utils.getBox().then((box) { + int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0); + HttpUtil.httpPut('v1/users/$userId', (response) { + Navigator.of(context).pop(); + if (mounted) { + setState(() { + _user = User.fromJson(response.data); + }); + _updateCurrentUser(); + } + }, + body: { + 'nickname': nicknameController.text + }, + ); + }).catchError((error){ + Navigator.of(context).pop(); + Routes.router.navigateTo(context, '/login'); + }); + } + }, + ) + ], + ); + } + ); + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_view_blog.dart b/lib/widgets/desktop/desktop_view_blog.dart new file mode 100644 index 0000000..3abc029 --- /dev/null +++ b/lib/widgets/desktop/desktop_view_blog.dart @@ -0,0 +1,194 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:photo_view/photo_view.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/blog.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/breadcrumbs.dart'; + +class DesktopViewBlog extends StatefulWidget { + final Key key; + final int bid; + + const DesktopViewBlog(this.bid, {this.key}) : super(key: key); + + @override + State createState() { + return DesktopViewBlogState(); + } +} + +class DesktopViewBlogState extends State { + Blog blog; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + _loadBlog(); + } + + void _loadBlog() { + HttpUtil.httpGet( + 'v1/blog/${widget.bid}', + businessId: Constants.BUSINESS_ID, + + ).then((value) { + if (mounted) { + setState(() { + blog = Blog.fromJson(value); + print('blog: $blog'); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (blog == null) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + BuildContext mainContext = context; + + Column blogCol = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Text( + '${blog.title}', + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ) + ], + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0), + child: Container( + child: Text( + '${blog.body}', + style: TextStyle( + color: Colors.black87, + fontSize: 17.0, + ), + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + ], + ); + + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: blogCol, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: (blog.imageUrl == null) ? + SizedBox.shrink() + : Container( + width: mainSpace / 2 - 100.0, + height: mainSpace / 2 - 100.0, + child: PhotoView( + imageProvider: NetworkImage( + 'https:${blog.imageUrl}', + ), + ), + ), + ), + ], + ), + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } +} \ No newline at end of file diff --git a/lib/widgets/desktop/desktop_view_ticket.dart b/lib/widgets/desktop/desktop_view_ticket.dart new file mode 100644 index 0000000..f2e8211 --- /dev/null +++ b/lib/widgets/desktop/desktop_view_ticket.dart @@ -0,0 +1,708 @@ + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:percent_indicator/percent_indicator.dart'; + +import '../../constants.dart'; +import '../../dialog/image_viewer.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/ticket.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/text_link.dart'; + +class DesktopViewTicket extends StatefulWidget { + final Key key; + final int ticketId; + + const DesktopViewTicket(this.ticketId, {this.key}) : super(key: key); + + @override + State createState() { + return DesktopViewTicketState(); + } +} + +class DesktopViewTicketState extends State { + final GlobalKey _formKey = GlobalKey(); + + Ticket ticket; + + final issueMsgController = TextEditingController(); + + bool onSubmitting = false; + double submitProgress = 0.0; + + final List> galleryImagesFlag = [ + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + ]; + + double sideSpace = 0; + double mainSpace = 1200; + + @override + void initState() { + super.initState(); + onSubmitting = false; + _loadTicket(); + } + + void _loadTicket() { + HttpUtil.httpGet( + 'v1/get-ticket/${widget.ticketId}', + businessId: Constants.BUSINESS_ID, + + ).then((value) { + if (mounted) { + setState(() { + ticket = Ticket.fromJson(value); + print('ticket: $ticket'); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (MediaQuery.of(context).size.width <= 1200) { + mainSpace = MediaQuery.of(context).size.width; + sideSpace = 0; + } else { + mainSpace = 1200; + sideSpace = (MediaQuery.of(context).size.width - 1200) / 2; + } + + if (ticket == null) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + BuildContext mainContext = context; + + Widget f = Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).your_reply, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: TextFormField( + controller: issueMsgController, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + decoration: new InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1.0, + color: Colors.blue, + ), + ), + labelText: S.of(context).your_reply, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: false, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + maxLines: 5, + maxLength: 1000, + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0), + child: Text( + S.of(context).attach_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).attach_pictures_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + height: 100.0, + child: galleryImages(mainContext), + ), + ), + ), + ], + ), + ); + + Widget s = Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).reply, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ); + + Column ticketCol = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + S.of(context).ticket_number_token(ticket.id), + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ) + ], + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0), + child: Text( + '${ticket.issue.msg}', + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0, right: 16.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: showGalleryImages(mainContext, ticket.issue.files), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black45, + ), + ), + ), + ), + Container( + width: double.maxFinite, + height: 10.0, + padding: EdgeInsets.only(bottom: 10.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 8, + color: Colors.black26, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.white70, + ), + ), + ), + ), + ], + ); + + if (ticket.followUps.length > 0) { + ticketCol.children.add( + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: Text( + S.of(context).follow_ups, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + for (int i = 0; i < ticket.followUps.length; i++) { + FollowUp followUp = ticket.followUps[i]; + ticketCol.children.add( + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${followUp.poster}', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: Colors.black38, + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString( + context, + followUp.createdAt + ), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Text( + '${followUp.msg}', + style: TextStyle( + color: Colors.black87, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 20.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: showGalleryImages( + mainContext, followUp.files, + ), + ), + ), + ], + ), + ), + ); + } + } + + Widget view = SingleChildScrollView( + child: Row( + children: [ + Container( + width: sideSpace, + ), + Container( + width: mainSpace, + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: ticketCol, + decoration: BoxDecoration( + border: Border( + right: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: mainSpace / 2, + margin: EdgeInsets.only(top: 16.0, bottom: 16.0), + padding: EdgeInsets.all(10.0), + child: (ticket.isClosed) ? + Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), + child: Text( + S.of(context).the_ticket_is_closed_desc, + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 0.0, bottom: 30.0), + child: TextLink( + S.of(context).new_ticket, + '/new-ticket/${ticket.store.id}', + ), + ) + ], + ) + : Column( + children: [ + f, + s, + ], + ), + ), + ], + ), + ), + ), + Container( + width: sideSpace, + ), + ], + ), + ); + return view; + } + + Widget showGalleryImages(BuildContext mainContext, List files) { + if (files.length <= 0) { + return SizedBox.shrink(); + } + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < files.length; i++) { + TicketFile image = files[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + 'https:${image.url}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${image.fileName}'), + content: ImageViewer( + NetworkImage( + 'https:${image.url}', + ), + ), + actions: [ + TextButton( + onPressed: () { + Routes.router.pop(context); + }, + child: Text(S.of(context).close) + ), + ], + ); + }, + ); + }, + ); + + row.children.add(imageWidget); + } + return Container( + height: 100.0, + child: row, + ); + } + + Widget galleryImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < 3; i++) { + Map image = galleryImagesFlag[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + '${image['path']}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Visibility( + visible: galleryImagesFlag[i]['show'], + child: Container( + padding: EdgeInsets.only(right: 20.0), + child: CircularPercentIndicator( + radius: 60.0, + lineWidth: 10.0, + percent: galleryImagesFlag[i]['progress'], + center: new Text( + '${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15.0, + color: Colors.white, + ), + ), + circularStrokeCap: CircularStrokeCap.round, + progressColor: Colors.orange, + ), + ), + ), + ], + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + return Util().getPicture2(mainContext, i, (int imageId, String path){ + setState(() { + galleryImagesFlag[imageId]['path'] = path; + }); + }); + return null; + } + ); + }, + ); + + row.children.add(Badge( + position: BadgePosition.topEnd(top: -10.0, end: 10.0), + badgeContent: Container( + child: GestureDetector( + child: Icon( + Icons.clear, + color: Colors.white, + size: 14.0, + ), + onTap: () { + _deleteImage(mainContext, i); + }, + ), + ), + showBadge: image['path'].isNotEmpty ? true : false, + child: imageWidget, + )); + } + return row; + } + + void _deleteImage(BuildContext mainContext, int imageId) { + showDialog(context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + RaisedButton( + child: Text(S.of(context).yes_i_am_sure), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + setState(() { + galleryImagesFlag[imageId]['path'] = ''; + }); + }, + ), + ], + ); + }, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + onSubmitting = event.showProgress; + submitProgress = event.progress; + }); + } + }); + } + + void _submit() { + final FormState form = _formKey.currentState; + if (form.validate()) { + setState(() { + onSubmitting = true; + }); + Util().createTicket(context, + issueMsgController.text.trim(), + galleryImagesFlag, + (response){ + showDialog(context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).ticket_created_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, + '/my-support/${ticket.store.id}', + replace: true, + ); + }, + ), + ], + ); + }); + }, (error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }, id: widget.ticketId); + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/add_remove_button.dart b/lib/widgets/general/add_remove_button.dart new file mode 100644 index 0000000..4d3f82b --- /dev/null +++ b/lib/widgets/general/add_remove_button.dart @@ -0,0 +1,395 @@ + + +import 'package:badges/badges.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/cart_info.dart'; +import '../../models/product.dart'; +import '../../pages/attribute_selection.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; + +class AddRemoveButton extends StatefulWidget { + final Product product; + final Business business; + final bool addOnly; + final int cartLineItemIndex; + final bool addToBasket; + + AddRemoveButton({ + this.product, + this.business, + this.addOnly = false, + this.cartLineItemIndex = -1, + this.addToBasket = false, + }); + + @override + State createState() { + return new AddRemoveButtonState(); + } +} + +class AddRemoveButtonState extends State { + int _qty; + var zeroColor = const Color(0xFFEFEFEF); + var qtyColor = const Color(0xFFFF6666); + var zeroFontColor = const Color(0xFF888888); + var qtyFontColor = const Color(0xFFFFFFFF); + + var d = 1; + + CartInfo cartInfo; + + GlobalKey startKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.product.leftNum == null) { + _qty = 0; + cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business); + if (cartInfo != null) { + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.id == widget.product.id + && cartInfo.productList[i].unitPrice == 0.0) { + _qty = cartInfo.productList[i].quantity.round(); + break; + } + } + } + return Container( + padding: EdgeInsets.only(top: 5.0, bottom: 5.0, left: 32.0, right: 32.0), + child: Text( + 'x$_qty' + ), + ); + } + if (widget.product.leftNum <= 0) { + return Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 8.0, right: 8.0), + child: Text( + S.of(context).out_of_stock, + style: TextStyle( + fontSize: 8.0, + color: Colors.white, + ), + ), + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(3.0), + topRight: Radius.circular(3.0), + bottomLeft: Radius.circular(3.0), + bottomRight: Radius.circular(3.0), + ), + ), + ); + } + if (widget.addToBasket) { + _qty = 0; + cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business); + if (cartInfo != null) { + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.id == widget.product.id) { + _qty = cartInfo.productList[i].quantity.round(); + break; + } + } + } + if (_qty > 0) { + return Badge( + badgeContent: Text( + '$_qty', + style: TextStyle( + color: Colors.white, + fontSize: 17.0, + ), + ), + padding: EdgeInsets.all(10), + position: BadgePosition.topEnd(top: -15, end: -10), + badgeColor: Colors.lightBlueAccent, + child: RaisedButton.icon( + padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20), + elevation: 2.0, + shape: new RoundedRectangleBorder( + borderRadius: new BorderRadius.circular(10.0), + ), + color: Colors.redAccent, + icon: Icon( + Icons.shopping_basket_outlined, + size: 32.0, + color: Colors.yellow, + ), + label: Text( + S.of(context).add_to_basket, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + onPressed: () { + _addToCart(context); + }, + ), + ); + } else { + return RaisedButton.icon( + padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20), + elevation: 2.0, + shape: new RoundedRectangleBorder( + borderRadius: new BorderRadius.circular(10.0), + ), + color: Colors.redAccent, + icon: Icon( + Icons.shopping_basket_outlined, + size: 32.0, + color: Colors.yellow, + ), + label: Text( + S.of(context).add_to_basket, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + onPressed: () { + _addToCart(context); + }, + ); + } + }else if (widget.addOnly) { + return Container( + key: startKey, + padding: EdgeInsets.all(0.0), + child: GestureDetector( + child: Icon( + Icons.add_circle, + size: 24.0, + ), + onTap: () { + _addToCart(context); + }, + ), + ); + } else { + _qty = 0; + cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business); + if (cartInfo != null) { + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.id == widget.product.id) { + _qty = cartInfo.productList[i].quantity.round(); + break; + } + } + } + if (widget.product.productAttributes != null && + widget.product.productAttributes.length > 0 && widget.cartLineItemIndex == -1) { + return new Row( + key: startKey, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + new GestureDetector( + child: new Container( + width: 60.0, + child: new Center( + child: new Text( + '$_qty', + style: new TextStyle( + fontSize: 13.0, + fontWeight: FontWeight.bold, + color: _qty > 0 ? qtyFontColor : zeroFontColor + ), + ), + ), + padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0), + decoration: BoxDecoration( + color: _qty > 0 ? qtyColor : zeroColor, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + ), + onTap: () { + _addToCart(context); + }, + ), + ], + ); + } else { + return new Row( + key: startKey, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + new GestureDetector( + child: new Container( + width: 30.0, + child: new Center( + child: new Text( + '$_qty', + style: new TextStyle( + fontSize: 13.0, + fontWeight: FontWeight.bold, + color: _qty > 0 ? qtyFontColor : zeroFontColor + ), + ), + ), + padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0), + decoration: BoxDecoration( + color: _qty > 0 ? qtyColor : zeroColor, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + ), + ), + ), + onTap: () { + _addToCart(context); + }, + ), + new GestureDetector( + child: new Container( + width: 30.0, + child: new Center( + child: new Text( + '-', + style: new TextStyle( + fontSize: 13.0, + fontWeight: FontWeight.bold, + ), + ), + ), + padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0), + decoration: BoxDecoration( + color: const Color(0xFFABABAB), + shape: BoxShape.rectangle, + borderRadius: BorderRadius.only( + topRight: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + ), + onTap: () { + if (_qty > 0) { + _removeFromCart(context); + } + }, + ), + ], + ); + } + } + } + + void _addToCart(BuildContext context) { + if (widget.cartLineItemIndex != -1) { + if (cartInfo.productList[widget.cartLineItemIndex].quantity + 1.0 > widget.product.leftNum) { + Fluttertoast.showToast( + msg: S.of(context).product_insufficient, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } else { + cartInfo.productList[widget.cartLineItemIndex].quantity += 1.0; + Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex]); + store.dispatch(UpdateCartInfo( + Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo))); + eventBus.fire(new OnCartInfoUpdated()); + } + } else { + if (widget.product.productAttributes.length > 0) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => + new AttributeSelection( + product: widget.product, business: widget.business, startKey: startKey,)), + ); + } else { + eventBus.fire(new OnProductWillAddToCart(widget.product, {}, + widget.product.price, widget.product.description, + widget.business, buttonKey: startKey)); + } + } + } + + void _removeFromCart(BuildContext context) { + if (widget.cartLineItemIndex != -1) { + if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_item), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + _removeCartLineItem(); + Navigator.of(context).pop(); + }, + ), + ], + ); + } + ); + } else { + _removeCartLineItem(); + } + } else { + eventBus.fire(new OnProductWillRemoveFromCart( + widget.product, -1, widget.business)); + } + } + + void _removeCartLineItem() { + if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) { + String uuid = cartInfo.productList[widget.cartLineItemIndex].uuid; + cartInfo.productList.removeAt(widget.cartLineItemIndex); + Utils.removeSubproduct(cartInfo, uuid); + } else { + cartInfo.productList[widget.cartLineItemIndex].quantity -= 1; + Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex], remove: true); + } + if (cartInfo.productList.length <= 0) { + store.dispatch(new UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(store.state.cartInfos, cartInfo))); + } else { + store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo))); + } + eventBus.fire(new OnCartInfoUpdated()); + } + + @override + void setState(VoidCallback fn) { + if(mounted) { + super.setState(fn); + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/animation_point_manager.dart b/lib/widgets/general/animation_point_manager.dart new file mode 100644 index 0000000..6b98a0b --- /dev/null +++ b/lib/widgets/general/animation_point_manager.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; + +import 'parabolic_animation_widget.dart'; +import 'popup_animation_widget.dart'; + +class AnimationPointManager { + List list = []; + static AnimationController controller1; + static AnimationController controller2; + + Future addParabolicAniamtion({ + @required TickerProvider vsync, + @required GlobalKey stackKey, + @required GlobalKey startKey, + @required GlobalKey endKey, + @required Duration duration, + @required AnimationStatusListener statusListener, + Color color = Colors.red, + double size = 20, + Offset startAdjustOffset = Offset.zero, + Offset endAdjustOffset = Offset.zero, + }) async { + controller1 = createController(vsync, duration); + Animation animation = createAnimation(controller1); + + AnimatedWidget animatedWidget = ParabolicAnimationWidget( + animation: animation, + stackKey: stackKey, + startKey: startKey, + endKey: endKey, + size: size, + color: color, + startAdjustOffset: startAdjustOffset, + endAdjustOffset: endAdjustOffset, + ); + list.add(animatedWidget); + statusListener(AnimationStatus.dismissed); + + try { + await controller1.forward().orCancel; + list.remove(animatedWidget); + controller1.dispose(); + print('Controller1 disposed'); + } on TickerCanceled { + print("Ticker Canceled"); + } catch (error) { + print('Error: $error'); + } + + statusListener(AnimationStatus.completed); + } + + Future addPopupAniamtion({ + @required TickerProvider vsync, + @required GlobalKey stackKey, + @required GlobalKey startKey, + @required Widget child, + Duration duration, + Offset popupOffset = Offset.zero, + AnimationStatusListener statusListener, + }) async { + controller2 = createController(vsync, duration); + + AnimatedWidget animatedWidget = PopupAnimationWidget( + animation: controller2.view, + stackKey: stackKey, + startKey: startKey, + child: child, + popupOffset: popupOffset, + ); + list.add(animatedWidget); + statusListener(AnimationStatus.dismissed); + + try { + await controller2.forward().orCancel; + await controller2.reverse().orCancel; + list.remove(animatedWidget); + controller2.dispose(); + print('Controller2 disposed'); + } on TickerCanceled { + print("Ticker Canceled"); + } catch (error) { + print('Error: $error'); + } + + statusListener(AnimationStatus.completed); + } + + static AnimationController createController( + TickerProvider vsync, Duration duration) { + AnimationController ani = AnimationController( + lowerBound: 0, + upperBound: 1, + duration: duration ?? Duration(milliseconds: 800), + vsync: vsync); + return ani; + } + + static CurvedAnimation createAnimation(controller) { + return CurvedAnimation(parent: controller, curve: Curves.linear); + } + + void dispose() { + try { + controller1?.dispose(); + controller2?.dispose(); + } catch (error) { + + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/attribute/check_options.dart b/lib/widgets/general/attribute/check_options.dart new file mode 100644 index 0000000..a14bd09 --- /dev/null +++ b/lib/widgets/general/attribute/check_options.dart @@ -0,0 +1,349 @@ + + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import '../../../events/eventbus.dart'; +import '../../../events/events.dart'; +import '../../../generated/l10n.dart'; +import '../../../models/product.dart'; +import '../../../models/product_option.dart'; +import '../../../utils/utils.dart'; +import 'rules.dart'; + +import 'options_base.dart'; + +class CheckOptions extends OptionsBase { + CheckOptions({@required Product product, @required int index, @required Map selections}) + : super(product: product, index: index, selections: selections); + + @override + State createState() { + return new CheckOptionsState(); + } +} + +class CheckOptionsState extends OptionsBaseState { + Map optionsState = new Map(); + int thisLimitQty = 0; + + @override + Widget build(BuildContext context) { + + Widget w = Center( + child: Text('Error'), + ); + try { + w = _getOptionWidget(); + } catch (error, stacktrace) { + print('Error: $error, Trace: $stacktrace'); + } + + return new ListView.builder( + itemCount: 2, + itemBuilder: (context, index) { + if (index == 0) { + return new Container( + padding: new EdgeInsets.all(10.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Text( + S.of(context).check_option_select_token(product.productAttributes[this.index].name), + style: new TextStyle( + fontSize: 12.5, + ), + overflow: TextOverflow.ellipsis, + ), + new Text( + product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional, + style: new TextStyle( + fontSize: 10.0, + color: new Color(0xFF999999) + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ); + } + return w; + } + ); + } + + @override + void initState() { + super.initState(); + setState(() { + product = widget.product; + selections = widget.selections; + index = widget.index; + }); + eventBus.on().listen((event) { + setState(() { + index = event.index; + }); + }); + } + + void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) { + Map opt = { + 'name': name, + 'quantity': quantity, + 'adjust_amount': adjustAmount + }; + var cloneSelections = json.decode(json.encode(selections)); + int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name); + if (idx != -1) { + (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx); + } else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) { + (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt); + } else { + cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt]; + } + + if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) { + cloneSelections.remove(product.productAttributes[index].name.toUpperCase()); + } + + setOptionsStateDisabled(product.productAttributes[index].name, false); + + setState(() { + selections = cloneSelections; + }); + eventBus.fire(new OnAttributeSelectionsChanged(selections)); + } + + Widget _getOptionWidget() { + var row = Wrap( + children: [], + ); + + List productOptions = product.productAttributes[index].productOptions; + + if (!optionsState.containsKey(product.productAttributes[index].name)) { + List> optionState = []; + for (var i = 0; i < productOptions.length; i++) { + optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false}); + } + optionsState[product.productAttributes[index].name] = optionState; + } + + if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) { + + Map attrExtraJson = Utils.stringToJson( + product.productAttributes[index].extra); + if (attrExtraJson != null) { + var selectLimitIfFieldEqualsTo = Rule.getRule( + attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO); + if (selectLimitIfFieldEqualsTo != null) { + if (selectLimitIfFieldEqualsTo is List) { + for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) { + Map selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b]; + if (selectLimitIfFieldEqualsTo1.containsKey( + Rule.RULE_KEY_FORCE_LIMITED)) { + int limitQty = selectLimitIfFieldEqualsTo1[Rule + .RULE_KEY_FORCE_LIMITED]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) { + if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) { + int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } + } + } + } else { + if (selectLimitIfFieldEqualsTo.containsKey( + Rule.RULE_KEY_FORCE_LIMITED)) { + int limitQty = selectLimitIfFieldEqualsTo[Rule + .RULE_KEY_FORCE_LIMITED]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) { + if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) { + int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } + } + } + } + } + + for (var i = 0; i < productOptions.length; i++) { + Map extraJson = Utils.stringToJson( + productOptions[i].extra); + if (extraJson != null) { + Map exclusiveRule = Rule.getRule( + extraJson, Rule.RULE_EXCLUSIVE_SELECTION); + Map multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS); + if (exclusiveRule != null) { + if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) { + optionsState[product.productAttributes[index].name][i]['disabled'] = true; + } else { + if (_checkOptionIsCheck(productOptions[i].name)) { + setOptionsStateDisabled( + product.productAttributes[index].name, true); + optionsState[product.productAttributes[index] + .name][i]['disabled'] = false; + break; + } + } + } + if (multiItemRule != null) { + if (_checkOptionIsCheck(productOptions[i].name)) { + if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) { + disableOptionIfNotSelected(); + } + } else { + if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) { + optionsState[product.productAttributes[index] + .name][i]['disabled'] = true; + } + } + } + } + } + } + + List> optionState = optionsState[product.productAttributes[index].name]; + for (var i = 0; i < optionState.length; i++) { + Widget optionWidget = _getOptionCheck( + product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i); + row.children.add(optionWidget); + } + return row; + } + + void disableOptionIfNotSelected() { + setOptionsStateDisabled(product.productAttributes[index].name, true); + for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) { + if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) { + optionsState[product.productAttributes[index].name][i]['disabled'] = false; + } + } + } + + bool _checkOptionIsCheck(String name) { + if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) { + return true; + } + return false; + } + + Widget _getOptionCheck(ProductOption productOption, bool optDisabled, int optIndex) { + bool disabled = optDisabled; + bool check = _checkOptionIsCheck(productOption.name); + + Map extraJson = Utils.stringToJson(productOption.extra); +// Utils.jsonPrettyPrint(extraJson); + + double extraAdjustAmount = 0.0; + + if (extraJson != null) { + var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE); + if (sizeBaseAdjustment != null) { + if (sizeBaseAdjustment is List) { + for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) { + Map sizeBaseAdjustment1 = sizeBaseAdjustment[i]; + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment1[attValues[0]]; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment[attValues[0]]; + } + } + } + + var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO); + if (disabledRule != null) { + if (disabledRule is List) { + for (var i = 0; i < (disabledRule as List).length; i++) { + Map disabledRule1 = disabledRule[i]; + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) { + disabled = true; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) { + disabled = true; + } + } + } + } + + return new GestureDetector( + child: new Container( + width: 100.0, + height: 70.0, + decoration: BoxDecoration( + color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor), + shape: BoxShape.rectangle, + border: new Border.all( + color: check ? selectedBorderColor : deselectBorderColor, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + padding: new EdgeInsets.all(4.0), + margin: new EdgeInsets.all(10.0).copyWith(right: 0.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text( + productOption.name, + style: new TextStyle( + fontSize: 14.0, + color: check ? selectedTextColor : deselectTextColor, + ), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + new Text( + (productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '', + style: new TextStyle( + fontSize: 11.0, + color: check ? selectedTextColor : new Color(0xFFABABAB), + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + onTap: () => disabled ? null : _onOptionTappedCallback( + productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption), + ); + } + + void setOptionsStateDisabled(String attrName, bool disabled) { + if (optionsState.containsKey(attrName)) { + List> optionState = optionsState[attrName]; + for (var i = 0; i < optionState.length; i++) { + optionState[i]['disabled'] = disabled; + } + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/attribute/options_base.dart b/lib/widgets/general/attribute/options_base.dart new file mode 100644 index 0000000..b9d5de9 --- /dev/null +++ b/lib/widgets/general/attribute/options_base.dart @@ -0,0 +1,39 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/models/product.dart'; + +typedef void OnOptionTapped(String name, int quantity, double adjustAmount); + +abstract class OptionsBase extends StatefulWidget { + final Product product; + final int index; + final Map selections; + OptionsBase({@required Product product, @required int index, @required Map selections}) + : product = product, + index = index, + selections = selections; +} + +abstract class OptionsBaseState extends State { + Product product; + Map selections; + int index; + + final Color disabledBackgroundColor = new Color(0xFFBCBCBC); + + final Color deselectBackgroundColor = new Color(0x00000000); + final Color deselectTextColor = new Color(0xFF333333); + final Color deselectBorderColor = new Color(0xFF888888); + + final Color selectedBackgroundColor = new Color(0xFFFF8908); + final Color selectedTextColor = new Color(0xFFFFFFFF); + final Color selectedBorderColor = new Color(0xFF444444); + + @override + void setState(VoidCallback fn) { + if(mounted) { + super.setState(fn); + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/attribute/qty_options.dart b/lib/widgets/general/attribute/qty_options.dart new file mode 100644 index 0000000..2367636 --- /dev/null +++ b/lib/widgets/general/attribute/qty_options.dart @@ -0,0 +1,422 @@ + + +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../../../events/eventbus.dart'; +import '../../../events/events.dart'; +import '../../../generated/l10n.dart'; +import '../../../models/product.dart'; +import '../../../models/product_option.dart'; +import '../../../utils/utils.dart'; +import 'options_base.dart'; +import 'rules.dart'; + +class QtyOptions extends OptionsBase { + QtyOptions({@required Product product, @required int index, @required Map selections}) + : super(product: product, index: index, selections: selections); + + @override + State createState() { + return new QtyOptionsState(); + } +} + +class QtyOptionsState extends OptionsBaseState { + Map optionsState = new Map(); + int thisLimitQty = 0; + + @override + Widget build(BuildContext context) { + + return new ListView.builder( + itemCount: 2, + itemBuilder: (context, index) { + if (index == 0) { + return new Container( + padding: new EdgeInsets.all(10.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Text( + S.of(context).check_option_select_token(product.productAttributes[this.index].name), + style: new TextStyle( + fontSize: 12.5, + ), + overflow: TextOverflow.ellipsis, + ), + new Text( + product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional, + style: new TextStyle( + fontSize: 10.0, + color: new Color(0xFF999999) + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ); + } + return _getOptionWidget(); + } + ); + } + + @override + void initState() { + super.initState(); + setState(() { + product = widget.product; + selections = widget.selections; + index = widget.index; + }); + eventBus.on().listen((event) { + setState(() { + index = event.index; + }); + }); + } + + void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) { + Map opt = { + 'name': name, + 'quantity': quantity, + 'adjust_amount': adjustAmount + }; + var cloneSelections = json.decode(json.encode(selections)); + int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name); + if (idx != -1) { + if (quantity == 1) { + cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] += 1; + optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity']; + } else { + if (cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] - 1 > 0) { + cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] -= 1; + optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity']; + } else { + (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx); + optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 0; + } + } + } else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) { + (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt); + optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1; + } else { + cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt]; + optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1; + } + + if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) { + cloneSelections.remove(product.productAttributes[index].name.toUpperCase()); + } + + setOptionsStateDisabled(product.productAttributes[index].name, false); + + setState(() { + selections = cloneSelections; + }); + eventBus.fire(new OnAttributeSelectionsChanged(selections)); + } + + Widget _getOptionWidget() { + var row = Wrap( + children: [], + ); + + List productOptions = product.productAttributes[index].productOptions; + + if (!optionsState.containsKey(product.productAttributes[index].name)) { + List> optionState = []; + for (var i = 0; i < productOptions.length; i++) { + int qty = 0; + int idx = Utils.selectionsContains(selections, product.productAttributes[index].name, productOptions[i].name); + if (idx != -1) { + qty = selections[product.productAttributes[index].name.toUpperCase()][idx]['quantity']; + } + optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'quantity': qty, 'check': false}); + } + optionsState[product.productAttributes[index].name] = optionState; + } + + if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) { + Map attrExtraJson = Utils.stringToJson( + product.productAttributes[index].extra); + if (attrExtraJson != null) { + var selectLimitIfFieldEqualsTo = Rule.getRule( + attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO); + if (selectLimitIfFieldEqualsTo != null) { + if (selectLimitIfFieldEqualsTo is List) { + for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) { + Map selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b]; + if (selectLimitIfFieldEqualsTo1.containsKey( + Rule.RULE_KEY_FORCE_LIMITED)) { + int limitQty = selectLimitIfFieldEqualsTo1[Rule + .RULE_KEY_FORCE_LIMITED]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) { + if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) { + int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } + } + } + } else { + if (selectLimitIfFieldEqualsTo.containsKey( + Rule.RULE_KEY_FORCE_LIMITED)) { + int limitQty = selectLimitIfFieldEqualsTo[Rule + .RULE_KEY_FORCE_LIMITED]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) { + if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) { + int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]]; + thisLimitQty = limitQty; + if ((selections[product.productAttributes[index].name + .toUpperCase()] as List).length >= limitQty) { + disableOptionIfNotSelected(); + } + } + } + } + } + } + + for (var i = 0; i < productOptions.length; i++) { + Map extraJson = Utils.stringToJson( + productOptions[i].extra); + if (extraJson != null) { + Map exclusiveRule = Rule.getRule( + extraJson, Rule.RULE_EXCLUSIVE_SELECTION); + Map multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS); + if (exclusiveRule != null) { + if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) { + optionsState[product.productAttributes[index].name][i]['disabled'] = true; + } else { + if (_checkOptionIsCheck(productOptions[i].name)) { + setOptionsStateDisabled( + product.productAttributes[index].name, true); + optionsState[product.productAttributes[index] + .name][i]['disabled'] = false; + break; + } + } + } + if (multiItemRule != null) { + if (_checkOptionIsCheck(productOptions[i].name)) { + if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) { + disableOptionIfNotSelected(); + } + } else { + if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) { + optionsState[product.productAttributes[index] + .name][i]['disabled'] = true; + } + } + } + } + } + } + + List> optionState = optionsState[product.productAttributes[index].name]; + for (var i = 0; i < optionState.length; i++) { + Widget optionWidget = _getOptionQty( + product.productAttributes[index].productOptions[i], optionState[i]['disabled'], optionState[i]['quantity'], i); + row.children.add(optionWidget); + } + return row; + } + + void disableOptionIfNotSelected() { + setOptionsStateDisabled(product.productAttributes[index].name, true); + for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) { + if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) { + optionsState[product.productAttributes[index].name][i]['disabled'] = false; + } + } + } + + bool _checkOptionIsCheck(String name) { + if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) { + return true; + } + return false; + } + + Widget _getOptionQty(ProductOption productOption, bool optDisabled, int quantity, int optIndex) { + bool disabled = optDisabled; + bool check = _checkOptionIsCheck(productOption.name); + + Map extraJson = Utils.stringToJson(productOption.extra); +// Utils.jsonPrettyPrint(extraJson); + + double extraAdjustAmount = 0.0; + + if (extraJson != null) { + var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE); + if (sizeBaseAdjustment != null) { + if (sizeBaseAdjustment is List) { + for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) { + Map sizeBaseAdjustment1 = sizeBaseAdjustment[i]; + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment1[attValues[0]]; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment[attValues[0]]; + } + } + } + + var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO); + if (disabledRule != null) { + if (disabledRule is List) { + for (var i = 0; i < (disabledRule as List).length; i++) { + Map disabledRule1 = disabledRule[i]; + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) { + disabled = true; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) { + disabled = true; + } + } + } + } + + return new Container( + width: 100.0, + height: 100.0, + decoration: BoxDecoration( + color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor), + shape: BoxShape.rectangle, + border: new Border.all( + color: check ? selectedBorderColor : deselectBorderColor, + width: 1.0, + ), +// borderRadius: BorderRadius.all(Radius.circular(10.0)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + ), + ), + + margin: new EdgeInsets.all(10.0).copyWith(right: 0.0), + child: new Column( + children: [ + new GestureDetector( + child: new Container( + width: 100.0, + height: 70.0, + padding: new EdgeInsets.all(4.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text( + productOption.name, + style: new TextStyle( + fontSize: 14.0, + color: check ? selectedTextColor : deselectTextColor, + ), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + new Text( + (productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '', + style: new TextStyle( + fontSize: 11.0, + color: check ? selectedTextColor : new Color(0xFFABABAB), + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + onTap: () => disabled ? null : _onOptionTappedCallback( + productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption), + ), + new Container( + width: 100.0, + height: 28.0, + decoration: BoxDecoration( + color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor), + shape: BoxShape.rectangle, + border: new Border( + top: new BorderSide( + color: check ? selectedBorderColor : deselectBorderColor, + width: 1.0, + ), + ), + ), + child: new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Expanded( + child: new GestureDetector( + child: new Container( + color: new Color(0x00000000), + width: 49.0, + height: 28.0, + child: new Center( + child: new Text( + '${quantity}', + style: new TextStyle( + color: check ? selectedTextColor : new Color(0xFFABABAB), + ), + ), + ), + ), + onTap: () => disabled ? null : _onOptionTappedCallback( + productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption), + ), + ), + new Expanded( + child: new GestureDetector( + child: new Container( + color: disabledBackgroundColor, + width: 49.0, + height: 28.0, + child: new Center( + child: new Text('-') + ), + ), + onTap: () => (disabled || quantity == 0) ? null : _onOptionTappedCallback( + productOption.name, -1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption), + ), + ), + ], + ), + ) + ], + ), + ); + } + + void setOptionsStateDisabled(String attrName, bool disabled) { + if (optionsState.containsKey(attrName)) { + List> optionState = optionsState[attrName]; + for (var i = 0; i < optionState.length; i++) { + optionState[i]['disabled'] = disabled; + } + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/attribute/radio_options.dart b/lib/widgets/general/attribute/radio_options.dart new file mode 100644 index 0000000..2f86d64 --- /dev/null +++ b/lib/widgets/general/attribute/radio_options.dart @@ -0,0 +1,261 @@ + + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import '../../../events/eventbus.dart'; +import '../../../events/events.dart'; +import '../../../generated/l10n.dart'; +import '../../../models/product.dart'; +import '../../../models/product_option.dart'; +import '../../../utils/utils.dart'; + +import 'options_base.dart'; +import 'rules.dart'; + +class RadioOptions extends OptionsBase { + RadioOptions({@required Product product, @required int index, @required Map selections}) + : super(product: product, index: index, selections: selections); + + @override + State createState() { + return new RadioOptionsState(); + } +} + +class RadioOptionsState extends OptionsBaseState { + Map optionsState = new Map(); + + @override + Widget build(BuildContext context) { + + return new ListView.builder( + itemCount: 2, + itemBuilder: (context, index) { + if (index == 0) { + return new Container( + padding: new EdgeInsets.all(10.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Text( + S.of(context).radio_option_select_token(product.productAttributes[this.index].name), + style: new TextStyle( + fontSize: 12.5, + ), + overflow: TextOverflow.ellipsis, + ), + new Text( + product.productAttributes[this.index].required ? S.of(context).radio_option_is_required : S.of(context).radio_option_is_optional, + style: new TextStyle( + fontSize: 10.0, + color: new Color(0xFF999999) + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ); + } + return _getOptionWidget(); + } + ); + } + + @override + void initState() { + super.initState(); + setState(() { + product = widget.product; + selections = widget.selections; + index = widget.index; + }); + eventBus.on().listen((event) { + setState(() { + index = event.index; + }); + }); + } + + void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) { + Map opt = { + 'name': name, + 'quantity': quantity, + 'adjust_amount': adjustAmount + }; + var cloneSelections = json.decode(json.encode(selections)); + if (product.productAttributes[index].required) { + cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt]; + } else { + if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase()) + && Utils.equalsIgnoreCase(cloneSelections[product.productAttributes[index].name.toUpperCase()][0]['name'], name)) { + cloneSelections.remove(product.productAttributes[index].name.toUpperCase()); + } else { + cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt]; + } + } + + Map extraJson = Utils.stringToJson(productOption.extra); + if (extraJson != null) { + Map exclusiveRule = Rule.getRule( + extraJson, Rule.RULE_EXCLUSIVE_SELECTION); + if (exclusiveRule != null) { + if (_checkOptionIsCheck(productOption.name)) { + setOptionsStateDisabled(product.productAttributes[index].name, false); + } + } + } + + setState(() { + selections = cloneSelections; + }); + eventBus.fire(new OnAttributeSelectionsChanged(selections)); + } + + Widget _getOptionWidget() { + var row = Wrap( + children: [], + ); + + List productOptions = product.productAttributes[index].productOptions; + + if (!optionsState.containsKey(product.productAttributes[index].name)) { + List> optionState = []; + for (var i = 0; i < productOptions.length; i++) { + optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false}); + } + optionsState[product.productAttributes[index].name] = optionState; + } + + for (var i = 0; i < productOptions.length; i++) { + Map extraJson = Utils.stringToJson(productOptions[i].extra); + if (extraJson != null) { + Map exclusiveRule = Rule.getRule( + extraJson, Rule.RULE_EXCLUSIVE_SELECTION); + if (exclusiveRule != null) { + if (_checkOptionIsCheck(productOptions[i].name)) { + setOptionsStateDisabled(product.productAttributes[index].name, true); + optionsState[product.productAttributes[index].name][i]['disabled'] = false; + break; + } + } + } + } + + List> optionState = optionsState[product.productAttributes[index].name]; + for (var i = 0; i < optionState.length; i++) { + Widget optionWidget = _getOptionRadio( + product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i); + row.children.add(optionWidget); + } + return row; + } + + bool _checkOptionIsCheck(String name) { + if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) { + return true; + } + return false; + } + + Widget _getOptionRadio(ProductOption productOption, bool optDisabled, int optIndex) { + bool disabled = optDisabled; + bool check = _checkOptionIsCheck(productOption.name); + + Map extraJson = Utils.stringToJson(productOption.extra); +// Utils.jsonPrettyPrint(extraJson); + + double extraAdjustAmount = 0.0; + + if (extraJson != null) { + var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE); + if (sizeBaseAdjustment != null) { + if (sizeBaseAdjustment is List) { + for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) { + Map sizeBaseAdjustment1 = sizeBaseAdjustment[i]; + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment1[attValues[0]]; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) { + extraAdjustAmount += sizeBaseAdjustment[attValues[0]]; + } + } + } + + var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO); + if (disabledRule != null) { + if (disabledRule is List) { + for (var i = 0; i < (disabledRule as List).length; i++) { + Map disabledRule1 = disabledRule[i]; + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) { + disabled = true; + } + } + } else { + List attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]); + if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) { + disabled = true; + } + } + } + } + + return new GestureDetector( + child: new Container( + width: 100.0, + height: 70.0, + decoration: BoxDecoration( + color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor), + shape: BoxShape.rectangle, + border: new Border.all( + color: check ? selectedBorderColor : deselectBorderColor, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + padding: new EdgeInsets.all(4.0), + margin: new EdgeInsets.all(10.0).copyWith(right: 0.0), + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Text( + productOption.name, + style: new TextStyle( + fontSize: 14.0, + color: check ? selectedTextColor : deselectTextColor, + ), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + new Text( + (productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '', + style: new TextStyle( + fontSize: 11.0, + color: check ? selectedTextColor : new Color(0xFFABABAB), + ), + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + onTap: () => disabled ? null : _onOptionTappedCallback( + productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption), + ); + } + + void setOptionsStateDisabled(String attrName, bool disabled) { + if (optionsState.containsKey(attrName)) { + Map optionState = optionsState[attrName]; + for (var i = 0; i < optionState.length; i++) { + optionState[i]['disabled'] = disabled; + } + } + } +} \ No newline at end of file diff --git a/lib/widgets/general/attribute/rules.dart b/lib/widgets/general/attribute/rules.dart new file mode 100644 index 0000000..d166843 --- /dev/null +++ b/lib/widgets/general/attribute/rules.dart @@ -0,0 +1,27 @@ + +class Rule { + static final String rulesName = 'rules'; + static final String RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE = "adjustment-based-on-selected-field-value"; + static final String RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO = "select-limit-if-field-equals-to"; + static final String RULE_DISABLED_IF_FIELD_EQUALS_TO = "disabled-if-field-equals-to"; + static final String RULE_EXCLUSIVE_SELECTION = "exclusive-selection"; + static final String RULE_MAX_QTY_LIMIT = "max-qty-limit"; + static final String RULE_ACTUAL_QTY_IS = "actual-quantity-is"; + + static final String RULE_KEY_FORCE_LIMITED = 'force_limited'; + static final String RULE_KEY_FIELD_KEY = 'field-key'; + + static dynamic getRule(Map json, String ruleName) { + if (json != null && json.containsKey(rulesName)) { + if ((json[rulesName] as Map).containsKey(ruleName)) { + if (json[rulesName][ruleName] is bool) { + return {ruleName: true}; + } else if (json[rulesName][ruleName] is int) { + return {ruleName: json[rulesName][ruleName]}; + } + return json[rulesName][ruleName]; + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/widgets/general/bottom_nav.dart b/lib/widgets/general/bottom_nav.dart index 25f97aa..4d4ce6d 100644 --- a/lib/widgets/general/bottom_nav.dart +++ b/lib/widgets/general/bottom_nav.dart @@ -17,8 +17,8 @@ class BottomNavState extends State { Widget build(BuildContext context) { return Container( width: MediaQuery.of(context).size.width, - height: 50.0, - padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + height: 70.0, + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0), decoration: BoxDecoration( color: Color(0xff232323), ), diff --git a/lib/widgets/general/breadcrumbs.dart b/lib/widgets/general/breadcrumbs.dart new file mode 100644 index 0000000..3ea5179 --- /dev/null +++ b/lib/widgets/general/breadcrumbs.dart @@ -0,0 +1,192 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; + +class BreadCrumbs extends StatelessWidget { + final List breadCrumbs; + final bool hasBack; + const BreadCrumbs(this.hasBack, {this.breadCrumbs, Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + List widgets = []; + if (this.hasBack) { + widgets.add(Container( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.arrow_back_ios, + size: 16.0, + color: Colors.black38, + ), + Container( + padding: EdgeInsets.only(left: 0.0, right: 4.0), + child: Text( + S.of(context).back, + style: TextStyle( + color: Colors.black38, + fontSize: 14.0, + ), + ), + ), + ], + ), + onTap: () { + Routes.router.pop(context); + }, + ), + ), + )); + } + if (breadCrumbs != null) { + for (int i = 0; i < breadCrumbs.length; i++) { + BreadCrumb breadCrumb = breadCrumbs[i]; + if (breadCrumb.text == null && breadCrumb.item != null) { + if (breadCrumb.onTap == null) { + widgets.add(breadCrumb.item); + } else { + widgets.add(MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: breadCrumb.item, + onTap: breadCrumb.onTap, + ), + )); + } + } else { + if (breadCrumb.route != null) { + widgets.add(Container( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 0.0), + child: Icon( + Icons.circle, + size: 6.0, + color: Colors.black38, + ), + ), + Container( + padding: EdgeInsets.only(left: 4.0, right: 4.0), + child: breadCrumb.icon != null ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + breadCrumb.icon, + size: 16.0, + color: Colors.blueAccent, + ), + Text( + breadCrumb.text, + style: TextStyle( + color: Colors.blueAccent, + fontSize: 14.0, + ), + ) + ], + ) : + Text( + breadCrumb.text, + style: TextStyle( + color: Colors.blueAccent, + fontSize: 14.0, + ), + ), + ), + ], + ), + onTap: () { + Routes.router.navigateTo(context, breadCrumb.route); + }, + ), + ), + )); + } else { + widgets.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 0.0), + child: Icon( + Icons.circle, + size: 6.0, + color: Colors.black38, + ), + ), + Container( + padding: EdgeInsets.only(left: 4.0, right: 4.0), + child: breadCrumb.icon != null ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + breadCrumb.icon, + size: 16.0, + color: Colors.lightBlueAccent, + ), + Text( + breadCrumb.text, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + fontWeight: FontWeight.bold, + ), + ) + ], + ) : + Text( + breadCrumb.text, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + )); + } + } + } + } + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8.0, top: 8.0), + height: 38, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: widgets, + ), + ); + } + +} + +class BreadCrumb { + final String text; + final String route; + final IconData icon; + final Widget item; + final Function onTap; + + BreadCrumb(this.text, this.route, {this.icon, this.item, this.onTap}); +} \ No newline at end of file diff --git a/lib/widgets/general/carousel.dart b/lib/widgets/general/carousel.dart new file mode 100644 index 0000000..e239e1e --- /dev/null +++ b/lib/widgets/general/carousel.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'style.dart'; + +class Carousel extends StatefulWidget { + Carousel({ + double height = 200.0, + List pages, + bool autoPlay, + Duration duration = const Duration(seconds: 2), + Duration animationDuration = const Duration(milliseconds: 1000), + }) + : height = height, + pages = pages, + autoPlay = autoPlay, + duration = duration, + animationDuration = animationDuration; + + final double height; + final List pages; + final bool autoPlay; + final Duration duration; + final Duration animationDuration; + + @override + createState() => new CarouselState(); +} + +class CarouselState extends State { + final _pageController = new PageController(); + + Timer _timer; + int _currentPage = 0; + bool reverse = false; + GlobalKey _indicatorStateKey = new GlobalKey(); + + @override + void initState() { + super.initState(); + if (widget.autoPlay) { + _timer = new Timer.periodic(widget.duration, (timer) { + _pageController.animateToPage(_currentPage, + duration: widget.animationDuration, curve: Curves.linear); + if (!reverse) { + _currentPage += 1; + if (_currentPage == widget.pages.length) { + _currentPage -= 1; + reverse = true; + } + } else { + _currentPage -= 1; + if (_currentPage < 0) { + _currentPage += 1; + reverse = false; + } + } + }); + } + } + + @override + void dispose() { + _pageController?.dispose(); + if (_timer != null) { + _timer.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Stack( + children: [ + new Container( + height: widget.height, + color: Style.backgroundColor, + child: new PageView( + controller: _pageController, + children: widget.pages, + onPageChanged: (index) { + _currentPage = index; + _indicatorStateKey.currentState.changeIndex(index); + }, + ), + ), + new Positioned( + left: 0.0, + right: 0.0, + bottom: 0.0, + child: new Align( + child: new Indicator( + key: _indicatorStateKey, + count: widget.pages.length, + ), + alignment: Alignment.center, + ), + ), + ], + ); + } +} + +class Indicator extends StatefulWidget { + Indicator({Key key, int count}) + : count = count, + super(key: key); + + final int count; + + @override + createState() => new IndicatorState(); +} + +class IndicatorState extends State { + int _index = 0; + + changeIndex(int index) { + setState(() { + _index = index; + }); + } + + @override + Widget build(BuildContext context) { + var indicators = []; + for (var i = 0; i < widget.count; ++i) { + indicators.add(new Container( + width: 5.0, + height: 5.0, + decoration: new BoxDecoration( + borderRadius: new BorderRadius.all(new Radius.circular(5.0)), + color: _index == i ? Style.primaryColor : Style.borderColor, + ), + )); + } + return new SizedBox( + width: widget.count * 15.0, + height: 30.0, + child: new Row( + children: indicators, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + ), + ); + } +} diff --git a/lib/widgets/general/double_back_to_close_app_wrapper.dart b/lib/widgets/general/double_back_to_close_app_wrapper.dart new file mode 100644 index 0000000..8d66405 --- /dev/null +++ b/lib/widgets/general/double_back_to_close_app_wrapper.dart @@ -0,0 +1,26 @@ + +import 'package:flutter/material.dart'; +import '../../generated/l10n.dart'; +import '../../utils/double_back_to_close_app.dart'; + +class DoubleBackToCloseAppWrapper extends StatelessWidget { + final Widget child; + + const DoubleBackToCloseAppWrapper({Key key, this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + return DoubleBackToCloseApp( + snackBar: SnackBar( + content: Container( + alignment: Alignment.center, + height: 24.0, + child: Text( + S.of(context).tap_back_again_to_exit, + ), + ), + ), + child: child, + ); + } +} \ No newline at end of file diff --git a/lib/widgets/general/download_apps.dart b/lib/widgets/general/download_apps.dart deleted file mode 100644 index 4204e18..0000000 --- a/lib/widgets/general/download_apps.dart +++ /dev/null @@ -1,47 +0,0 @@ - -import 'package:flutter/material.dart'; -import '../../utils/http_util.dart'; -import '../../widgets/desktop/desktop_download_apps.dart'; -import '../../widgets/mobile/mobile_download_apps.dart'; -import 'package:responsive_builder/responsive_builder.dart'; - -class DownloadApps extends StatefulWidget { - const DownloadApps({Key key}) : super(key: key); - - @override - State createState() { - return DownloadAppsState(); - } - -} - -class DownloadAppsState extends State { - Map data; - - @override - Widget build(BuildContext context) { - return ScreenTypeLayout( - mobile: MobileDownloadApps(data), - tablet: DesktopDownloadApps(data), - desktop: DesktopDownloadApps(data), - ); - } - - @override - void initState() { - super.initState(); - _loadData(); - } - - void _loadData() { - HttpUtil.httpGet('v1/get-wisetronic-download-page') - .then((value) { - print('$value'); - if (mounted) { - setState(() { - data = value; - }); - } - }); - } -} \ No newline at end of file diff --git a/lib/widgets/general/download_item.dart b/lib/widgets/general/download_item.dart index 9e74bc4..3000612 100644 --- a/lib/widgets/general/download_item.dart +++ b/lib/widgets/general/download_item.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import '../../widgets/general/text_link.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../generated/l10n.dart'; @@ -31,7 +30,7 @@ class DownloadItemState extends State { Widget build(BuildContext context) { return Container( width: widget.width ?? MediaQuery.of(context).size.width, - padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0, right: 10.0), + padding: EdgeInsets.only(top: 10.0, bottom: 20.0, left: 10.0, right: 10.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -45,16 +44,8 @@ class DownloadItemState extends State { child: Row( children: [ Container( - child: (kIsWeb) ? - Image.network( + child: Util.showImage( '${widget.desc['app_icon']}', - ) : - SvgPicture.network( - '${widget.desc['app_icon']}', - placeholderBuilder: (BuildContext context) => Container( - padding: const EdgeInsets.all(30.0), - child: const CircularProgressIndicator(), - ), ), width: iconWidth, height: iconWidth, @@ -210,7 +201,7 @@ class DownloadItemState extends State { break; } } - print('download url $downloadUrl'); + if (downloadUrl != null && downloadUrl == 'instore') { return Container( padding: EdgeInsets.all(8.0), diff --git a/lib/widgets/general/navigationbar.dart b/lib/widgets/general/navigationbar.dart index 1133839..9be5b98 100644 --- a/lib/widgets/general/navigationbar.dart +++ b/lib/widgets/general/navigationbar.dart @@ -1,21 +1,31 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/widgets/desktop/desktop_navigationbar.dart'; -import 'package:flutter_wisetronic/widgets/mobile/mobile_navigationbar.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../widgets/desktop/desktop_navigationbar.dart'; +import '../../widgets/mobile/mobile_navigationbar.dart'; +import 'breadcrumbs.dart'; + class NavigationBar extends StatefulWidget implements PreferredSizeWidget { final Key key; - final PreferredSizeWidget bottom; final String title; final bool back; + final bool toHome; + final bool showMe; + final List breadCrumbs; + final double breadCrumbHeight; + final Widget shoppingCart; - NavigationBar({Key key, PreferredSizeWidget bottom, String title, bool back}) + NavigationBar({Key key, PreferredSizeWidget bottom, String title, + bool back, bool toHome, bool showMe, this.breadCrumbs, + this.breadCrumbHeight, this.shoppingCart}) : key = key, - preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), - bottom = bottom, + preferredSize = breadCrumbHeight != null ? Size.fromHeight(kToolbarHeight + breadCrumbHeight) : + Size.fromHeight(kToolbarHeight), title = title ?? '', - back = back ?? false; + back = back ?? false, + toHome = toHome ?? false, + showMe = showMe ?? true; @override final Size preferredSize; @@ -32,10 +42,17 @@ class NavigationBarState extends State { @override Widget build(BuildContext context) { return ScreenTypeLayout( - mobile: MobileNavigationBar(title: widget.title, back: widget.back,), - tablet: DesktopNavigationBar(), - desktop: DesktopNavigationBar(), + mobile: MobileNavigationBar(title: widget.title, back: widget.back, + toHome: widget.toHome, showMe: widget.showMe,), + tablet: DesktopNavigationBar(hasBack: widget.back, + breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,), + desktop: DesktopNavigationBar(hasBack: widget.back, + breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,), ); } + @override + void initState() { + super.initState(); + } } \ No newline at end of file diff --git a/lib/widgets/general/parabolic_animation_widget.dart b/lib/widgets/general/parabolic_animation_widget.dart new file mode 100644 index 0000000..5aa0176 --- /dev/null +++ b/lib/widgets/general/parabolic_animation_widget.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +// ignore: must_be_immutable +class ParabolicAnimationWidget extends AnimatedWidget { + final GlobalKey stackKey; + final GlobalKey startKey; + final GlobalKey endKey; + final double size; + final Color color; + final Offset startAdjustOffset; + final Offset endAdjustOffset; + + ParabolicAnimationWidget({ + @required Animation animation, + @required this.stackKey, + @required this.startKey, + @required this.endKey, + this.size = 20.0, + this.color = Colors.red, + this.startAdjustOffset = Offset.zero, + this.endAdjustOffset = Offset.zero, + }) : super(listenable: animation); + + Offset _startOffset; + Offset _endOffset; + + @override + Widget build(BuildContext context) { + _calPoints(); + + final Animation animation = listenable; + final double time = animation.value; + + // 设time=1 已知两点坐标 和 初速度 可求出 加速度 a + // double x(double time) => _x + _v * time + 0.5 * _a * time * time; + final double initV = -400; //纵坐标初速度, 负值为向上抛 + final double acceleration = + (_endOffset.dy - _startOffset.dy - initV) / 0.5; //求纵坐标加速度 + + final GravitySimulation spy = GravitySimulation( + acceleration, _startOffset.dy, _endOffset.dy, initV); //y轴加速度运动 模拟 + + final GravitySimulation spx = GravitySimulation(0, _startOffset.dx, + _endOffset.dx, _endOffset.dx - _startOffset.dx); //x轴匀速模拟 加速度为0 + + final Animation opacity = Tween(begin: 1, end: 0).animate( + CurvedAnimation( + parent: animation, curve: Interval(0.9, 1, curve: Curves.ease))); + + return Positioned( + top: spy.x(time), + left: spx.x(time), + child: Opacity( + opacity: opacity.value, + child: Container( + height: size, + width: size, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(size / 2.0))), + ), + ), + ); + } + + void _calPoints() { + if (_startOffset == null) { + RenderBox stackBox = stackKey.currentContext.findRenderObject(); + Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero); + + EdgeInsets startMargin = _margin(startKey); + RenderBox startBox = startKey.currentContext.findRenderObject(); + _startOffset = startBox.localToGlobal(Offset( + startMargin.left + startAdjustOffset.dx, + stackBoxOffset.dy + startMargin.top + startAdjustOffset.dy)); + + EdgeInsets endMargin = _margin(endKey); + RenderBox endBox = endKey.currentContext.findRenderObject(); + _endOffset = endBox.localToGlobal(Offset( + endMargin.left + endAdjustOffset.dx, + stackBoxOffset.dy + endMargin.top + endAdjustOffset.dy)); + } + } + + EdgeInsets _margin(GlobalKey key) { + final Widget widget = key.currentContext.widget; + EdgeInsets margin = (widget is Container) ? widget.margin : EdgeInsets.zero; + return margin ?? EdgeInsets.zero; + } +} \ No newline at end of file diff --git a/lib/widgets/general/payment_verification_code_dialog.dart b/lib/widgets/general/payment_verification_code_dialog.dart new file mode 100644 index 0000000..1909e72 --- /dev/null +++ b/lib/widgets/general/payment_verification_code_dialog.dart @@ -0,0 +1,176 @@ + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:pinput/pin_put/pin_put.dart'; + +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +typedef OnCodeVerified(); +typedef OnCancel(); + +class PaymentVerificationCodeDialog extends StatefulWidget { + final Key key; + final User user; + final OnCodeVerified onCodeVerified; + final OnCancel onCancel; + + const PaymentVerificationCodeDialog(this.user, this.onCodeVerified, this.onCancel, {this.key}); + + @override + State createState() { + return PaymentVerificationCodeDialogState(); + } + +} + +class PaymentVerificationCodeDialogState extends State { + CountDown cd = CountDown(Duration(seconds: 90)); + var countDownListener; + bool enableGetCode = false; + String getCodeText = ''; + String paymentCodeEncrypt = ''; + + final TextEditingController _pinPutController = TextEditingController(); + final FocusNode _pinPutFocusNode = FocusNode(); + BoxDecoration get _pinPutDecoration { + return BoxDecoration( + border: Border.all(color: Colors.deepPurpleAccent), + borderRadius: BorderRadius.circular(15), + ); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(S.of(context).payment_verification), + content: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0), + child: Text( + S.of(context).payment_verification_sent(Utils.safePhoneNumber(widget.user.mobile)), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0), + child: FlatButton( + child: Text(getCodeText), + onPressed: enableGetCode ? () { + getPaymentCode(); + } : null, + ), + ), + Center( + child: Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0), + child: PinPut( + fieldsCount: 4, + focusNode: _pinPutFocusNode, + controller: _pinPutController, + onSubmit: (String pin) { + String codeString = generateSignature(pin, key: widget.user.id.toString()); + FocusScope.of(context).unfocus(); + if (paymentCodeEncrypt == codeString) { + Navigator.of(context).pop(); + if (widget.onCodeVerified != null) { + widget.onCodeVerified(); + } + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).error), + content: Text(S.of(context).wrong_payment_verification_code), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + _pinPutController.clear(); + _pinPutFocusNode.requestFocus(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + }, + submittedFieldDecoration: _pinPutDecoration.copyWith( + borderRadius: BorderRadius.circular(20)), + selectedFieldDecoration: _pinPutDecoration, + followingFieldDecoration: _pinPutDecoration.copyWith( + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: Colors.deepPurpleAccent.withOpacity(.5), + ), + ), + ), + ), + ), + ], + ), + actions: [ + FlatButton( + child: Text(S.of(context).close), + onPressed: () { + Navigator.of(context).pop(); + if (widget.onCancel != null) { + widget.onCancel(); + } + }, + ) + ], + ); + } + + @override + void initState() { + super.initState(); + getPaymentCode(); + } + + void getPaymentCode() { + HttpUtil.httpGet('v1/get-payment-verification-code', + queryParameters: { + 'key': widget.user.id, + 'method': 'mobile', // or 'mobile' + 'is_user': false, + }, + ).then((data) { + paymentCodeEncrypt = data['payment_code_encrypt']; + print('data: $data'); + startCountDown(); + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + + void startCountDown() { + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + }); + } +} \ No newline at end of file diff --git a/lib/widgets/general/popup_animation_widget.dart b/lib/widgets/general/popup_animation_widget.dart new file mode 100644 index 0000000..ade0ba6 --- /dev/null +++ b/lib/widgets/general/popup_animation_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +// ignore: must_be_immutable +class PopupAnimationWidget extends AnimatedWidget { + final GlobalKey stackKey; + final GlobalKey startKey; + final Color color; + final Widget child; + final Offset popupOffset; + final Animation animation; + + PopupAnimationWidget({ + @required this.animation, + @required this.stackKey, + @required this.startKey, + @required this.child, + this.color = Colors.yellow, + this.popupOffset = Offset.zero, + }) : super(listenable: animation); + + Offset _startOffset; + Offset _offset = Offset.zero; + + @override + Widget build(BuildContext context) { + _calAnimation(); + + final Animation opacityAnimation = Tween(begin: 0, end: 1) + .animate(CurvedAnimation( + parent: animation, curve: Interval(0, 0.4, curve: Curves.ease))); + _offset = + Offset(_startOffset.dx, _startOffset.dy - opacityAnimation.value * 80); + + return Positioned( + left: _offset.dx, + top: _offset.dy, + child: Opacity( + opacity: opacityAnimation.value, + child: ScaleTransition( + alignment: Alignment.bottomCenter, + scale: opacityAnimation, + child: Container( + child: child, + ), + ), + ), + ); + } + + void _calAnimation() { + if (_startOffset == null) { + final RenderBox stackBox = stackKey.currentContext.findRenderObject(); + final Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero); + + final EdgeInsets startMargin = _margin(startKey); + final RenderBox startBox = startKey.currentContext.findRenderObject(); + + _startOffset = startBox.localToGlobal(Offset( + startMargin.left + popupOffset.dx, + stackBoxOffset.dy + startMargin.top + popupOffset.dy)); + } + } + + EdgeInsets _margin(GlobalKey key) { + final Widget widget = key.currentContext.widget; + final EdgeInsets margin = + (widget is Container) ? widget.margin : EdgeInsets.zero; + return margin ?? EdgeInsets.zero; + } +} \ No newline at end of file diff --git a/lib/widgets/general/product_item.dart b/lib/widgets/general/product_item.dart new file mode 100644 index 0000000..5e2f50c --- /dev/null +++ b/lib/widgets/general/product_item.dart @@ -0,0 +1,27 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/models/business.dart'; +import 'package:flutter_wisetronic/models/product.dart'; +import 'package:flutter_wisetronic/widgets/desktop/desktop_product_item.dart'; +import 'package:flutter_wisetronic/widgets/mobile/mobile_product_item.dart'; +import 'package:responsive_builder/responsive_builder.dart'; + +class ProductItem extends StatelessWidget { + final Product product; + final Business business; + + const ProductItem({Key key, this.product, this.business}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveBuilder( + builder: (context, sizingInformation) => + ScreenTypeLayout( + mobile: MobileProductItem(product: product, business: business,), + tablet: DesktopProductItem(product: product, business: business,), + desktop: DesktopProductItem(product: product, business: business,), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/general/read_more_text.dart b/lib/widgets/general/read_more_text.dart new file mode 100644 index 0000000..8d5227f --- /dev/null +++ b/lib/widgets/general/read_more_text.dart @@ -0,0 +1,198 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +enum TrimMode { + Length, + Line, +} + +class ReadMoreText extends StatefulWidget { + const ReadMoreText( + this.data, { + Key key, + this.trimExpandedText = ' read less', + this.trimCollapsedText = ' ...read more', + this.colorClickableText, + this.trimLength = 240, + this.trimLines = 2, + this.trimMode = TrimMode.Length, + this.style, + this.textAlign, + this.textDirection, + this.locale, + this.textScaleFactor, + this.semanticsLabel, + }) : assert(data != null), + super(key: key); + + final String data; + final String trimExpandedText; + final String trimCollapsedText; + final Color colorClickableText; + final int trimLength; + final int trimLines; + final TrimMode trimMode; + final TextStyle style; + final TextAlign textAlign; + final TextDirection textDirection; + final Locale locale; + final double textScaleFactor; + final String semanticsLabel; + + @override + ReadMoreTextState createState() => ReadMoreTextState(); +} + +const String _kEllipsis = '\u2026'; + +const String _kLineSeparator = '\u2028'; + +class ReadMoreTextState extends State { + bool _readMore = true; + + void _onTapLink() { + setState(() => _readMore = !_readMore); + } + + @override + Widget build(BuildContext context) { + final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); + TextStyle effectiveTextStyle = widget.style; + if (widget.style == null || widget.style.inherit) { + effectiveTextStyle = defaultTextStyle.style.merge(widget.style); + } + + final textAlign = + widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start; + final textDirection = widget.textDirection ?? Directionality.of(context); + final textScaleFactor = + widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context); + final overflow = defaultTextStyle.overflow; + final locale = + widget.locale ?? Localizations.localeOf(context); + + final colorClickableText = + widget.colorClickableText ?? Theme.of(context).accentColor; + + TextSpan link = TextSpan( + text: _readMore ? widget.trimCollapsedText : widget.trimExpandedText, + style: effectiveTextStyle.copyWith( + color: colorClickableText, + ), + recognizer: TapGestureRecognizer()..onTap = _onTapLink, + ); + + Widget result = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + assert(constraints.hasBoundedWidth); + final double maxWidth = constraints.maxWidth; + + // Create a TextSpan with data + final text = TextSpan( + style: effectiveTextStyle, + text: widget.data, + ); + + // Layout and measure link + TextPainter textPainter = TextPainter( + text: link, + textAlign: textAlign, + textDirection: textDirection, + textScaleFactor: textScaleFactor, + maxLines: widget.trimLines, + ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, + locale: locale, + ); + textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth); + final linkSize = textPainter.size; + + // Layout and measure text + textPainter.text = text; + textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth); + final textSize = textPainter.size; + + print('linkSize $linkSize textSize $textSize'); + + // Get the endIndex of data + bool linkLongerThanLine = false; + int endIndex; + + if (linkSize.width < maxWidth) { + final pos = textPainter.getPositionForOffset(Offset( + textSize.width - linkSize.width, + textSize.height, + )); + endIndex = textPainter.getOffsetBefore(pos.offset); + } + else { + var pos = textPainter.getPositionForOffset( + textSize.bottomLeft(Offset.zero), + ); + endIndex = pos.offset; + linkLongerThanLine = true; + } + + var textSpan; + switch (widget.trimMode) { + case TrimMode.Length: + if (widget.trimLength < widget.data.length) { + textSpan = TextSpan( + style: effectiveTextStyle, + text: _readMore + ? widget.data.substring(0, widget.trimLength) + : widget.data, + children: [link], + ); + } else { + textSpan = TextSpan( + style: effectiveTextStyle, + text: widget.data, + ); + } + break; + case TrimMode.Line: + if (textPainter.didExceedMaxLines) { + textSpan = TextSpan( + style: effectiveTextStyle, + text: _readMore + ? widget.data.substring(0, endIndex) + + (linkLongerThanLine ? _kLineSeparator : '') + : widget.data, + children: [link], + ); + } else { + textSpan = TextSpan( + style: effectiveTextStyle, + text: widget.data, + ); + } + break; + default: + throw Exception( + 'TrimMode type: ${widget.trimMode} is not supported'); + } + + return RichText( + textAlign: textAlign, + textDirection: textDirection, + softWrap: true, + //softWrap, + overflow: TextOverflow.clip, + //overflow, + textScaleFactor: textScaleFactor, + text: textSpan, + ); + }, + ); + if (widget.semanticsLabel != null) { + result = Semantics( + textDirection: widget.textDirection, + label: widget.semanticsLabel, + child: ExcludeSemantics( + child: result, + ), + ); + } + return result; + } +} \ No newline at end of file diff --git a/lib/widgets/general/show_price.dart b/lib/widgets/general/show_price.dart new file mode 100644 index 0000000..3183639 --- /dev/null +++ b/lib/widgets/general/show_price.dart @@ -0,0 +1,86 @@ + +import 'package:flutter/material.dart'; + +class ShowPrice extends StatelessWidget { + final double price; + final double regularPrice; + final double largeFontSize; + final double smallFontSize; + final String currencySign; + final Color color; + final FontWeight fontWeight; + + ShowPrice(this.price, + { + this.regularPrice, + this.largeFontSize = 20, + this.smallFontSize = 12, + this.currencySign, + this.color = Colors.red, + this.fontWeight = FontWeight.normal, + }); + + @override + Widget build(BuildContext context) { + String priceString = price.toStringAsFixed(2); + var arr = priceString.split('.'); + String num1 = arr[0]; + String num2 = arr[1]; + + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + currencySign != null ? + Container( + padding: EdgeInsets.only(top: largeFontSize / 10.0), + child: Text( + currencySign, + style: TextStyle( + fontSize: smallFontSize, + color: color, + fontWeight: fontWeight, + ), + ), + ) : SizedBox.shrink(), + Container( + child: Text( + num1, + style: TextStyle( + color: color, + fontSize: largeFontSize, + fontWeight: fontWeight, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: largeFontSize / 10.0), + child: Text( + '.$num2', + style: TextStyle( + color: color, + fontSize: smallFontSize, + fontWeight: fontWeight, + ), + ), + ), + regularPrice == null || price.round() >= regularPrice.round() ? + SizedBox.shrink() : + Container( + padding: EdgeInsets.only(top: largeFontSize / 2), + child: Text( + regularPrice.toStringAsFixed(0), + style: TextStyle( + color: Colors.black38, + fontSize: smallFontSize, + decoration: TextDecoration.lineThrough, + ), + ), + ), + ], + ), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/general/sliding_up_panel.dart b/lib/widgets/general/sliding_up_panel.dart new file mode 100644 index 0000000..f8066f4 --- /dev/null +++ b/lib/widgets/general/sliding_up_panel.dart @@ -0,0 +1,711 @@ +/* +Name: Akshath Jain +Date: 3/18/2019 - 4/2/2020 +Purpose: Defines the sliding_up_panel widget +Copyright: © 2020, Akshath Jain. All rights reserved. +Licensing: More information can be found here: https://github.com/akshathjain/sliding_up_panel/blob/master/LICENSE +*/ + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'dart:math'; + +import 'package:flutter/physics.dart'; + +enum SlideDirection{ + UP, + DOWN, +} + +enum PanelState{ + OPEN, + CLOSED +} + +class SlidingUpPanel extends StatefulWidget { + + /// The Widget that slides into view. When the + /// panel is collapsed and if [collapsed] is null, + /// then top portion of this Widget will be displayed; + /// otherwise, [collapsed] will be displayed overtop + /// of this Widget. If [panel] and [panelBuilder] are both non-null, + /// [panel] will be used. + final Widget panel; + + /// WARNING: This feature is still in beta and is subject to change without + /// notice. Stability is not gauranteed. Provides a [ScrollController] and + /// [ScrollPhysics] to attach to a scrollable object in the panel that links + /// the panel position with the scroll position. Useful for implementing an + /// infinite scroll behavior. If [panel] and [panelBuilder] are both non-null, + /// [panel] will be used. + final Widget Function(ScrollController sc) panelBuilder; + + /// The Widget displayed overtop the [panel] when collapsed. + /// This fades out as the panel is opened. + final Widget collapsed; + + /// The Widget that lies underneath the sliding panel. + /// This Widget automatically sizes itself + /// to fill the screen. + final Widget body; + + /// Optional persistent widget that floats above the [panel] and attaches + /// to the top of the [panel]. Content at the top of the panel will be covered + /// by this widget. Add padding to the bottom of the `panel` to + /// avoid coverage. + final Widget header; + + /// Optional persistent widget that floats above the [panel] and + /// attaches to the bottom of the [panel]. Content at the bottom of the panel + /// will be covered by this widget. Add padding to the bottom of the `panel` + /// to avoid coverage. + final Widget footer; + + /// The height of the sliding panel when fully collapsed. + final double minHeight; + + /// The height of the sliding panel when fully open. + final double maxHeight; + + /// A point between [minHeight] and [maxHeight] that the panel snaps to + /// while animating. A fast swipe on the panel will disregard this point + /// and go directly to the open/close position. This value is represented as a + /// percentage of the total animation distance ([maxHeight] - [minHeight]), + /// so it must be between 0.0 and 1.0, exclusive. + final double snapPoint; + + /// A border to draw around the sliding panel sheet. + final Border border; + + /// If non-null, the corners of the sliding panel sheet are rounded by this [BorderRadiusGeometry]. + final BorderRadiusGeometry borderRadius; + + /// A list of shadows cast behind the sliding panel sheet. + final List boxShadow; + + /// The color to fill the background of the sliding panel sheet. + final Color color; + + /// The amount to inset the children of the sliding panel sheet. + final EdgeInsetsGeometry padding; + + /// Empty space surrounding the sliding panel sheet. + final EdgeInsetsGeometry margin; + + /// Set to false to not to render the sheet the [panel] sits upon. + /// This means that only the [body], [collapsed], and the [panel] + /// Widgets will be rendered. + /// Set this to false if you want to achieve a floating effect or + /// want more customization over how the sliding panel + /// looks like. + final bool renderPanelSheet; + + /// Set to false to disable the panel from snapping open or closed. + final bool panelSnapping; + + /// If non-null, this can be used to control the state of the panel. + final PanelController controller; + + /// If non-null, shows a darkening shadow over the [body] as the panel slides open. + final bool backdropEnabled; + + /// Shows a darkening shadow of this [Color] over the [body] as the panel slides open. + final Color backdropColor; + + /// The opacity of the backdrop when the panel is fully open. + /// This value can range from 0.0 to 1.0 where 0.0 is completely transparent + /// and 1.0 is completely opaque. + final double backdropOpacity; + + /// Flag that indicates whether or not tapping the + /// backdrop closes the panel. Defaults to true. + final bool backdropTapClosesPanel; + + /// If non-null, this callback + /// is called as the panel slides around with the + /// current position of the panel. The position is a double + /// between 0.0 and 1.0 where 0.0 is fully collapsed and 1.0 is fully open. + final void Function(double position) onPanelSlide; + + /// If non-null, this callback is called when the + /// panel is fully opened + final VoidCallback onPanelOpened; + + /// If non-null, this callback is called when the panel + /// is fully collapsed. + final VoidCallback onPanelClosed; + + /// If non-null and true, the SlidingUpPanel exhibits a + /// parallax effect as the panel slides up. Essentially, + /// the body slides up as the panel slides up. + final bool parallaxEnabled; + + /// Allows for specifying the extent of the parallax effect in terms + /// of the percentage the panel has slid up/down. Recommended values are + /// within 0.0 and 1.0 where 0.0 is no parallax and 1.0 mimics a + /// one-to-one scrolling effect. Defaults to a 10% parallax. + final double parallaxOffset; + + /// Allows toggling of the draggability of the SlidingUpPanel. + /// Set this to false to prevent the user from being able to drag + /// the panel up and down. Defaults to true. + final bool isDraggable; + + /// Either SlideDirection.UP or SlideDirection.DOWN. Indicates which way + /// the panel should slide. Defaults to UP. If set to DOWN, the panel attaches + /// itself to the top of the screen and is fully opened when the user swipes + /// down on the panel. + final SlideDirection slideDirection; + + /// The default state of the panel; either PanelState.OPEN or PanelState.CLOSED. + /// This value defaults to PanelState.CLOSED which indicates that the panel is + /// in the closed position and must be opened. PanelState.OPEN indicates that + /// by default the Panel is open and must be swiped closed by the user. + final PanelState defaultPanelState; + + SlidingUpPanel({ + Key key, + this.panel, + this.panelBuilder, + this.body, + this.collapsed, + this.minHeight = 100.0, + this.maxHeight = 500.0, + this.snapPoint, + this.border, + this.borderRadius, + this.boxShadow = const [ + BoxShadow( + blurRadius: 8.0, + color: Color.fromRGBO(0, 0, 0, 0.25), + ) + ], + this.color = Colors.white, + this.padding, + this.margin, + this.renderPanelSheet = true, + this.panelSnapping = true, + this.controller, + this.backdropEnabled = false, + this.backdropColor = Colors.black, + this.backdropOpacity = 0.5, + this.backdropTapClosesPanel = true, + this.onPanelSlide, + this.onPanelOpened, + this.onPanelClosed, + this.parallaxEnabled = false, + this.parallaxOffset = 0.1, + this.isDraggable = true, + this.slideDirection = SlideDirection.UP, + this.defaultPanelState = PanelState.CLOSED, + this.header, + this.footer + }) : assert(panel != null || panelBuilder != null), + assert(0 <= backdropOpacity && backdropOpacity <= 1.0), + assert (snapPoint == null || 0 < snapPoint && snapPoint < 1.0), + super(key: key); + + @override + _SlidingUpPanelState createState() => _SlidingUpPanelState(); +} + +class _SlidingUpPanelState extends State with SingleTickerProviderStateMixin{ + + AnimationController _ac; + + ScrollController _sc; + bool _scrollingEnabled = false; + VelocityTracker _vt = new VelocityTracker(); + + bool _isPanelVisible = true; + + @override + void initState(){ + super.initState(); + + _ac = new AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + value: widget.defaultPanelState == PanelState.CLOSED ? 0.0 : 1.0 //set the default panel state (i.e. set initial value of _ac) + )..addListener((){ + if(widget.onPanelSlide != null) widget.onPanelSlide(_ac.value); + + if(widget.onPanelOpened != null && _ac.value == 1.0) widget.onPanelOpened(); + + if(widget.onPanelClosed != null && _ac.value == 0.0) widget.onPanelClosed(); + }); + + // prevent the panel content from being scrolled only if the widget is + // draggable and panel scrolling is enabled + _sc = new ScrollController(); + _sc.addListener((){ + if(widget.isDraggable && !_scrollingEnabled) + _sc.jumpTo(0); + }); + + widget.controller?._addState(this); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: widget.slideDirection == SlideDirection.UP ? Alignment.bottomCenter : Alignment.topCenter, + children: [ + + //make the back widget take up the entire back side + widget.body != null ? AnimatedBuilder( + animation: _ac, + builder: (context, child){ + return Positioned( + top: widget.parallaxEnabled ? _getParallax() : 0.0, + child: child, + ); + }, + child: Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: widget.body, + ), + ) : Container(), + + + //the backdrop to overlay on the body + !widget.backdropEnabled ? Container() : GestureDetector( + onVerticalDragEnd: widget.backdropTapClosesPanel ? (DragEndDetails dets){ + // only trigger a close if the drag is towards panel close position + if((widget.slideDirection == SlideDirection.UP ? 1 : -1) * dets.velocity.pixelsPerSecond.dy > 0) + _close(); + } : null, + onTap: widget.backdropTapClosesPanel ? () => _close() : null, + child: AnimatedBuilder( + animation: _ac, + builder: (context, _) { + return Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + + //set color to null so that touch events pass through + //to the body when the panel is closed, otherwise, + //if a color exists, then touch events won't go through + color: _ac.value == 0.0 ? null : widget.backdropColor.withOpacity(widget.backdropOpacity * _ac.value), + ); + } + ), + ), + + //the actual sliding part + !_isPanelVisible ? Container() : _gestureHandler( + child: AnimatedBuilder( + animation: _ac, + builder: (context, child) { + return Container( + height: _ac.value * (widget.maxHeight - widget.minHeight) + widget.minHeight, + margin: widget.margin, + padding: widget.padding, + decoration: widget.renderPanelSheet ? BoxDecoration( + border: widget.border, + borderRadius: widget.borderRadius, + boxShadow: widget.boxShadow, + color: widget.color, + ) : null, + child: child, + ); + }, + child: Stack( + overflow: Overflow.visible, + children: [ + + //open panel + Positioned( + top: widget.slideDirection == SlideDirection.UP ? 0.0 : null, + bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null, + width: MediaQuery.of(context).size.width - + (widget.margin != null ? widget.margin.horizontal : 0) - + (widget.padding != null ? widget.padding.horizontal : 0), + child: Container( + height: widget.maxHeight, + child: widget.panel != null + ? widget.panel + : widget.panelBuilder(_sc), + ), + ), + + // header + widget.header != null ? Positioned( + top: widget.slideDirection == SlideDirection.UP ? 0.0 : null, + bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null, + child: widget.header, + ) : Container(), + + // footer + widget.footer != null ? Positioned( + top: widget.slideDirection == SlideDirection.UP ? null : 0.0, + bottom: widget.slideDirection == SlideDirection.DOWN ? null : 0.0, + child: widget.footer + ) : Container(), + + // collapsed panel + Positioned( + top: widget.slideDirection == SlideDirection.UP ? 0.0 : null, + bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null, + width: MediaQuery.of(context).size.width - + (widget.margin != null ? widget.margin.horizontal : 0) - + (widget.padding != null ? widget.padding.horizontal : 0), + child: Container( + height: widget.minHeight, + child: widget.collapsed == null ? Container() : FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0).animate(_ac), + + // if the panel is open ignore pointers (touch events) on the collapsed + // child so that way touch events go through to whatever is underneath + child: IgnorePointer( + ignoring: _isPanelOpen, + child: widget.collapsed + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + @override + void dispose(){ + _ac.dispose(); + super.dispose(); + } + + double _getParallax(){ + if(widget.slideDirection == SlideDirection.UP) + return -_ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset; + else + return _ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset; + } + + // returns a gesture detector if panel is used + // and a listener if panelBuilder is used. + // this is because the listener is designed only for use with linking the scrolling of + // panels and using it for panels that don't want to linked scrolling yields odd results + Widget _gestureHandler({Widget child}){ + if (!widget.isDraggable) return child; + + if (widget.panel != null){ + return GestureDetector( + onVerticalDragUpdate: (DragUpdateDetails dets) => _onGestureSlide(dets.delta.dy), + onVerticalDragEnd: (DragEndDetails dets) => _onGestureEnd(dets.velocity), + child: child, + ); + } + + return Listener( + onPointerDown: (PointerDownEvent p) => _vt.addPosition(p.timeStamp, p.position), + onPointerMove: (PointerMoveEvent p){ + _vt.addPosition(p.timeStamp, p.position); // add current position for velocity tracking + _onGestureSlide(p.delta.dy); + }, + onPointerUp: (PointerUpEvent p) => _onGestureEnd(_vt.getVelocity()), + child: child, + ); + } + + // handles the sliding gesture + void _onGestureSlide(double dy){ + + // only slide the panel if scrolling is not enabled + if(!_scrollingEnabled){ + if(widget.slideDirection == SlideDirection.UP) + _ac.value -= dy / (widget.maxHeight - widget.minHeight); + else + _ac.value += dy / (widget.maxHeight - widget.minHeight); + } + + // if the panel is open and the user hasn't scrolled, we need to determine + // whether to enable scrolling if the user swipes up, or disable closing and + // begin to close the panel if the user swipes down + if(_isPanelOpen && _sc.hasClients && _sc.offset <= 0){ + setState(() { + if(dy < 0){ + _scrollingEnabled = true; + }else{ + _scrollingEnabled = false; + } + }); + } + } + + // handles when user stops sliding + void _onGestureEnd(Velocity v){ + double minFlingVelocity = 365.0; + double kSnap = 8; + + //let the current animation finish before starting a new one + if(_ac.isAnimating) return; + + // if scrolling is allowed and the panel is open, we don't want to close + // the panel if they swipe up on the scrollable + if(_isPanelOpen && _scrollingEnabled) return; + + //check if the velocity is sufficient to constitute fling to end + double visualVelocity = -v.pixelsPerSecond.dy / (widget.maxHeight - widget.minHeight); + + // reverse visual velocity to account for slide direction + if(widget.slideDirection == SlideDirection.DOWN) + visualVelocity = -visualVelocity; + + + // get minimum distances to figure out where the panel is at + double d2Close = _ac.value; + double d2Open = 1 - _ac.value; + double d2Snap = ((widget.snapPoint ?? 3) -_ac.value).abs(); // large value if null results in not every being the min + double minDistance = min(d2Close, min(d2Snap, d2Open)); + + // check if velocity is sufficient for a fling + if(v.pixelsPerSecond.dy.abs() >= minFlingVelocity){ + + // snapPoint exists + if(widget.panelSnapping && widget.snapPoint != null){ + if(v.pixelsPerSecond.dy.abs() >= kSnap*minFlingVelocity || minDistance == d2Snap) + _ac.fling(velocity: visualVelocity); + else + _flingPanelToPosition(widget.snapPoint, visualVelocity); + + // no snap point exists + }else if(widget.panelSnapping){ + _ac.fling(velocity: visualVelocity); + + // panel snapping disabled + }else{ + _ac.animateTo( + _ac.value + visualVelocity * 0.16, + duration: Duration(milliseconds: 410), + curve: Curves.decelerate, + ); + } + + return; + } + + // check if the controller is already halfway there + if (widget.panelSnapping) { + + if(minDistance == d2Close){ + _close(); + }else if(minDistance == d2Snap){ + _flingPanelToPosition(widget.snapPoint, visualVelocity); + }else{ + _open(); + } + } + + } + + void _flingPanelToPosition(double targetPos, double velocity){ + final Simulation simulation = SpringSimulation( + SpringDescription.withDampingRatio( + mass: 1.0, + stiffness: 500.0, + ratio: 1.0, + ), + _ac.value, + targetPos, + velocity + ); + + _ac.animateWith(simulation); + } + + //--------------------------------- + //PanelController related functions + //--------------------------------- + + //close the panel + Future _close(){ + return _ac.fling(velocity: -1.0); + } + + //open the panel + Future _open(){ + return _ac.fling(velocity: 1.0); + } + + //hide the panel (completely offscreen) + Future _hide(){ + return _ac.fling(velocity: -1.0).then((x){ + setState(() { + _isPanelVisible = false; + }); + }); + } + + //show the panel (in collapsed mode) + Future _show(){ + return _ac.fling(velocity: -1.0).then((x){ + setState(() { + _isPanelVisible = true; + }); + }); + } + + //animate the panel position to value - must + //be between 0.0 and 1.0 + Future _animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){ + assert(0.0 <= value && value <= 1.0); + return _ac.animateTo(value, duration: duration, curve: curve); + } + + //animate the panel position to the snap point + //REQUIRES that widget.snapPoint != null + Future _animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){ + assert(widget.snapPoint != null); + return _ac.animateTo(widget.snapPoint, duration: duration, curve: curve); + } + + //set the panel position to value - must + //be between 0.0 and 1.0 + set _panelPosition(double value){ + assert(0.0 <= value && value <= 1.0); + _ac.value = value; + } + + //get the current panel position + //returns the % offset from collapsed state + //as a decimal between 0.0 and 1.0 + double get _panelPosition => _ac.value; + + //returns whether or not + //the panel is still animating + bool get _isPanelAnimating => _ac.isAnimating; + + //returns whether or not the + //panel is open + bool get _isPanelOpen => _ac.value == 1.0; + + //returns whether or not the + //panel is closed + bool get _isPanelClosed => _ac.value == 0.0; + + //returns whether or not the + //panel is shown/hidden + bool get _isPanelShown => _isPanelVisible; + +} + + + + + + + + +class PanelController{ + _SlidingUpPanelState _panelState; + + void _addState(_SlidingUpPanelState panelState){ + this._panelState = panelState; + } + + /// Determine if the panelController is attached to an instance + /// of the SlidingUpPanel (this property must return true before any other + /// functions can be used) + bool get isAttached => _panelState != null; + + /// Closes the sliding panel to its collapsed state (i.e. to the minHeight) + Future close(){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._close(); + } + + /// Opens the sliding panel fully + /// (i.e. to the maxHeight) + Future open(){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._open(); + } + + /// Hides the sliding panel (i.e. is invisible) + Future hide(){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._hide(); + } + + /// Shows the sliding panel in its collapsed state + /// (i.e. "un-hide" the sliding panel) + Future show(){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._show(); + } + + /// Animates the panel position to the value. + /// The value must between 0.0 and 1.0 + /// where 0.0 is fully collapsed and 1.0 is completely open. + /// (optional) duration specifies the time for the animation to complete + /// (optional) curve specifies the easing behavior of the animation. + Future animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + assert(0.0 <= value && value <= 1.0); + return _panelState._animatePanelToPosition(value, duration: duration, curve: curve); + } + + /// Animates the panel position to the snap point + /// Requires that the SlidingUpPanel snapPoint property is not null + /// (optional) duration specifies the time for the animation to complete + /// (optional) curve specifies the easing behavior of the animation. + Future animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + assert(_panelState.widget.snapPoint != null, "SlidingUpPanel snapPoint property must not be null"); + return _panelState._animatePanelToSnapPoint(duration: duration, curve: curve); + } + + /// Sets the panel position (without animation). + /// The value must between 0.0 and 1.0 + /// where 0.0 is fully collapsed and 1.0 is completely open. + set panelPosition(double value){ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + assert(0.0 <= value && value <= 1.0); + _panelState._panelPosition = value; + } + + /// Gets the current panel position. + /// Returns the % offset from collapsed state + /// to the open state + /// as a decimal between 0.0 and 1.0 + /// where 0.0 is fully collapsed and + /// 1.0 is full open. + double get panelPosition{ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._panelPosition; + } + + /// Returns whether or not the panel is + /// currently animating. + bool get isPanelAnimating{ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._isPanelAnimating; + } + + /// Returns whether or not the + /// panel is open. + bool get isPanelOpen{ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._isPanelOpen; + } + + /// Returns whether or not the + /// panel is closed. + bool get isPanelClosed{ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._isPanelClosed; + } + + /// Returns whether or not the + /// panel is shown/hidden. + bool get isPanelShown{ + assert(isAttached, "PanelController must be attached to a SlidingUpPanel"); + return _panelState._isPanelShown; + } + +} \ No newline at end of file diff --git a/lib/widgets/general/stripe_pay.dart b/lib/widgets/general/stripe_pay.dart new file mode 100644 index 0000000..bbc442c --- /dev/null +++ b/lib/widgets/general/stripe_pay.dart @@ -0,0 +1,158 @@ + + +import 'package:flutter/material.dart'; +import 'package:stripe_payment/stripe_payment.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; + + +class StripePay extends StatefulWidget { + final Key key; + final Order order; + final PaymentPlatform paymentPlatform; + final StripePaymentMethod stripePaymentMethod; + const StripePay(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); + + @override + State createState() { + return StripePayState(); + } + +} + +class StripePayState extends State { + + GlobalKey _scaffoldKey = GlobalKey(); + + bool isSubmitting; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (widget.stripePaymentMethod != null) { + _paymentWithPaymentMethod(context); + } else { + _paymentRequestWithCardForm(context); + } + }); + + return Scaffold( + key: _scaffoldKey, + body: Center( + child: Icon( + Icons.credit_card, + size: 40.0, + color: Colors.black26, + ), + ), + ); + } + + @override + void initState() { + super.initState(); + isSubmitting = false; + StripePayment.setOptions( + StripeOptions(publishableKey: widget.paymentPlatform.publishableKey, + merchantId: widget.paymentPlatform.merchantId, + androidPayMode: 'test') + ); + } + + _paymentWithPaymentMethod(BuildContext context) async { + Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, + widget.stripePaymentMethod.paymentMethodId, + widget.stripePaymentMethod.paymentMethodType, (response){ + + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + StripePayment.confirmPaymentIntent( + PaymentIntent( + clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], + paymentMethodId: response.data['payment_method'], + ), + ).then((paymentIntentResult) { + if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + widget.stripePaymentMethod.paymentMethodId, + paymentIntentResult.paymentIntentId, + (response) { + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog)); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + _paymentRequestWithCardForm(BuildContext context) async { + StripePayment.paymentRequestWithCardForm( + CardFormPaymentRequest() + ).then((paymentMethod) { + Utils.stripePaymentIntent(widget.order, null, paymentMethod.id, paymentMethod.type, (response){ + + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + StripePayment.confirmPaymentIntent( + PaymentIntent( + clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET], + paymentMethodId: response.data['payment_method'], + ), + ).then((paymentIntentResult) { + if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + paymentMethod.id, + paymentIntentResult.paymentIntentId, + (response) { + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog), + cardBrand: paymentMethod.card.brand, + cardCountry: paymentMethod.card.country, + cardExpMonth: paymentMethod.card.expMonth, + cardExpYear: paymentMethod.card.expYear, + cardFunding: paymentMethod.card.funding, + cardLast4: paymentMethod.card.last4, + ); + }).catchError(showErrorDialog); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + void showErrorDialog(dynamic error) { + if (isSubmitting) { + Navigator.of(context).pop(); + isSubmitting = false; + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/general/style.dart b/lib/widgets/general/style.dart new file mode 100644 index 0000000..e41c735 --- /dev/null +++ b/lib/widgets/general/style.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class Style { + static final fontSize = 15.0; + static final primaryColor = const Color(0xFF3190e8); + static final backgroundColor = const Color(0xFFFFFFFF); + static final emptyBackgroundColor = const Color(0xFFF5F5F5); + static final borderColor = const Color(0xFFE4E4E4); + static final gPadding = 16.0; + static final textStyle = new TextStyle( + color: const Color(0xFF333333), + fontSize: fontSize, + ); + + static BoxDecoration testDecoration(Color color) { + return new BoxDecoration( + border: new Border.all(color: color), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/general/text_link.dart b/lib/widgets/general/text_link.dart index b34e195..77b152b 100644 --- a/lib/widgets/general/text_link.dart +++ b/lib/widgets/general/text_link.dart @@ -2,6 +2,7 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../routes.dart'; @@ -21,6 +22,8 @@ class TextLink extends StatelessWidget { final bool rootNavigator; final TransitionType transition; final bool closeDrawer; + final bool isEmail; + final bool isPhone; TextLink(this.title, this.url, { this.color, this.paddingHorizontal, @@ -34,13 +37,17 @@ class TextLink extends StatelessWidget { bool rootNavigator, this.transition, bool closeDrawer, + bool isEmail, + bool isPhone, }) : isLink = isLink ?? false, replace = replace ?? false, clearStack = clearStack ?? false, maintainState = maintainState ?? true, rootNavigator = rootNavigator ?? false, - closeDrawer = closeDrawer ?? false; + closeDrawer = closeDrawer ?? false, + isEmail = isEmail ?? false, + isPhone = isPhone ?? false; @override Widget build(BuildContext context) { @@ -69,22 +76,28 @@ class TextLink extends StatelessWidget { ), ), onTap: () async { - if (!isLink) { - if (closeDrawer) { - Routes.router.pop(context); - } - Routes.router.navigateTo( - context, url, - replace: replace, - clearStack: clearStack, - maintainState: maintainState, - rootNavigator: rootNavigator, - ); - } else { - if (await canLaunch(url)) { - await launch(url); + if (selected == null || !selected) { + if (isEmail) { + Utils.openEmail(url); + } else if (isPhone) { + Utils.callPhone(url); + } else if (!isLink) { + if (closeDrawer) { + Routes.router.pop(context); + } + Routes.router.navigateTo( + context, url, + replace: replace, + clearStack: clearStack, + maintainState: maintainState, + rootNavigator: rootNavigator, + ); } else { - throw 'Could not launch $url'; + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } } } }, diff --git a/lib/widgets/mobile/MobileBottomNav.dart b/lib/widgets/mobile/MobileBottomNav.dart new file mode 100644 index 0000000..7c918ca --- /dev/null +++ b/lib/widgets/mobile/MobileBottomNav.dart @@ -0,0 +1,83 @@ + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; + +class MobileBottomNav extends StatelessWidget { + final int currentIndex; + + int count = 0; + + MobileBottomNav({int currentIndex = 0}) : currentIndex = currentIndex; + + @override + Widget build(BuildContext context) { + if (store.state.cartInfos != null && store.state.cartInfos.length > 0) { + count = 0; + for (var i = 0; i < store.state.cartInfos.length; i++) { + if (store.state.cartInfos[i].productList != null && store.state.cartInfos[i].productList.length > 0) { + count += 1; + } + } + } else { + count = 0; + } + return new BottomNavigationBar( + onTap: (index) => _go(context, index), + currentIndex: currentIndex, + fixedColor: Theme.of(context).primaryColor, + type: BottomNavigationBarType.fixed, + items: [ + new BottomNavigationBarItem( + icon: new Icon(Icons.store), + label: S.of(context).home + ), + new BottomNavigationBarItem( + icon: count > 0 ? Badge( + badgeContent: Text('${count}', style: TextStyle(fontSize: 11.0, color: Colors.white),), + child: new Icon(Icons.shopping_basket), + ) : Icon(Icons.shopping_basket), + label: S.of(context).shop + ), + new BottomNavigationBarItem( + icon: new Icon(Icons.support_agent), + label: S.of(context).support + ), + new BottomNavigationBarItem( + icon: new Icon(Icons.person), + label: S.of(context).me + ) + ] + ); + } + + _go(BuildContext context, int index) { + if (index == currentIndex) return; + String path = ''; + switch(index) { + case 0: + path = '/'; + break; + case 1: + path = '/shop'; + break; + case 2: + path = '/my-support/${Constants.BUSINESS_ID}'; + break; + case 3: + path = '/me'; + break; + } + if (path.length > 0) { + print('go to: $path'); + Routes.router.navigateTo( + context, + path, + replace: false, + ); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_attribute_selection.dart b/lib/widgets/mobile/mobile_attribute_selection.dart new file mode 100644 index 0000000..708d8d2 --- /dev/null +++ b/lib/widgets/mobile/mobile_attribute_selection.dart @@ -0,0 +1,287 @@ + + +import 'package:flutter/material.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../models/product_attribute.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/attribute/check_options.dart'; +import '../../widgets/general/attribute/qty_options.dart'; +import '../../widgets/general/attribute/radio_options.dart'; + +class MobileAttributeSelection extends StatefulWidget { + final Product product; + final Business business; + final Key key; + final GlobalKey startKey; + + const MobileAttributeSelection({@required this.product, @required this.business, this.key, this.startKey}) + : super(key: key); + + @override + State createState() { + return new MobileAttributeSelectionState(); + } + +} + +class MobileAttributeSelectionState extends State { + Product product; + int index; + FlatButton previousButton; + FlatButton nextButton; + bool previousButtonEnable; + bool nextButtonEnable; + + String productDesc; + double productPrice; + + String nextText; + String finishText; + + Map selections = new Map(); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).select_options), + backgroundColor: Theme.of(context).primaryColor, + actions: [], + ), + body: new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _getProductContent(), + Expanded( + child: _getOptionsView(), + ), + ], + ), + bottomNavigationBar: new Container( + padding: new EdgeInsets.all(10.0), + color: new Color(0xFFA8A8A8), + child: new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + previousButton, + nextButton + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + index = 0; + product = widget.product; + previousButtonEnable = false; + nextButtonEnable = false; + productDesc = product.description; + productPrice = product.price; + }); + + eventBus.on().listen((event) { + this.selections = event.selections; + List extendDescription = []; + double extendPrice = 0.0; + event.selections.forEach((key, value) { + List opt = []; + for (var i = 0; i < (value as List).length; i++) { + if (value[i]['quantity'] == 0) { + extendPrice += value[i]['adjust_amount']; + opt.add(value[i]['name']); + } else { + extendPrice += value[i]['adjust_amount'] * value[i]['quantity']; + opt.add(value[i]['name'] + '*' + value[i]['quantity'].toString()); + } + } + extendDescription.add(key + ': ' + opt.join(', ')); + }); + + ProductAttribute pa = product.productAttributes[index]; + if (pa.required && Utils.selectionsNotEmptyAt(selections, pa.name)) { + setState(() { + nextButtonEnable = true; + productDesc = product.description + ', ' + extendDescription.join('; '); + productPrice = product.price + extendPrice; + }); + } else if (!pa.required){ + setState(() { + nextButtonEnable = true; + productDesc = product.description + ', ' + extendDescription.join('; '); + productPrice = product.price + extendPrice; + }); + } else { + setState(() { + nextButtonEnable = false; + productDesc = product.description + ', ' + extendDescription.join('; '); + productPrice = product.price + extendPrice; + }); + } + }); + } + + bool _checkCanGoNext() { + ProductAttribute pa = product.productAttributes[index]; + if (pa.required && Utils.selectionsNotEmptyAt(selections, pa.name)) { + return true; + } else if (!pa.required){ + return true; + } + return false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + nextText = S.of(context).next; + finishText = S.of(context).finish; + } + + Widget _getProductContent() { + previousButton = new FlatButton( + onPressed: previousButtonEnable ? _goPrevious : null, + child: new Text(S.of(context).previous), + ); + + nextButton = new FlatButton( + onPressed: nextButtonEnable ? _goNext : null, + child: new Text( + product.productAttributes.length > index + 1 ? nextText : finishText + ), + ); + + var productContent = new SizedBox( + child: new Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + padding: new EdgeInsets.all(10.0), + width: 70.0, + height: 70.0, + child: Util.showImage(product.imagePath, + width: 70.0, + height: 70.0, + fit: BoxFit.fill, + ), + ), + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + padding: new EdgeInsets.all(10.0).copyWith(left: 0.0).copyWith(bottom: 0.0), + child: new Text( + product.name, + style: new TextStyle( + fontSize: 15.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + new Container( + padding: new EdgeInsets.only(right: 10.0), + child: new Text( + productDesc, + style: new TextStyle( + fontSize: 9.0, + color: new Color(0xFFCDCDCD) + ), + maxLines: 4, + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + new Container( + padding: new EdgeInsets.all(10.0).copyWith(left: 0.0), + width: 70.0, + child: + new Text( + productPrice.toStringAsFixed(2), + textAlign: TextAlign.right, + style: new TextStyle( + fontSize: 18.0, + ), + ), + ), + ], + ), + ); + return productContent; + } + + Widget _getOptionsView() { + Widget optionsView; + + ProductAttribute productAttribute = product.productAttributes[index]; + if (productAttribute.byQuantity) { + optionsView = new QtyOptions(product: product, index: index, selections: selections); + } else { + if (productAttribute.singleSelection) { + optionsView = new RadioOptions(product: product, index: index, selections: selections); + } else { + optionsView = new CheckOptions(product: product, index: index, selections: selections); + } + } + + return optionsView; + } + + void _goNext() { + if (index + 1 < product.productAttributes.length) { + setState(() { + index = index + 1; + previousButtonEnable = index >= 1; + nextButtonEnable = _checkCanGoNext(); + }); + } else if (product.productAttributes.length == index + 1) { + eventBus.fire(new OnProductWillAddToCart(product, selections, productPrice, productDesc, widget.business, buttonKey: widget.startKey)); + Navigator.pop(context); + } + eventBus.fire(new OnAttributePageChanged(index)); + } + + void _goPrevious() { + if (index - 1 >= 0) { + setState(() { + index = index - 1; + previousButtonEnable = index > 0; + nextButtonEnable = _checkCanGoNext(); + }); + eventBus.fire(new OnAttributePageChanged(index)); + } + } + + @override + void setState(VoidCallback fn) { + if(mounted) { + super.setState(fn); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_blog.dart b/lib/widgets/mobile/mobile_blog.dart new file mode 100644 index 0000000..8ebc2b5 --- /dev/null +++ b/lib/widgets/mobile/mobile_blog.dart @@ -0,0 +1,265 @@ + + +import 'package:flutter/material.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/blog.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/mobile/MobileBottomNav.dart'; + +class MobileBlog extends StatefulWidget { + final int businessId; + const MobileBlog({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return MobileBlogState(); + } + +} + +class MobileBlogState extends State { + List blogs; + + int _page = 1; + int _pageCount = 1; + + bool _isLoading = false; + bool _loadingFinish = false; + + RefreshController _refreshController = RefreshController(initialRefresh: true); + + void _onRefresh() { + _page = 1; + if (blogs != null) { + blogs.clear(); + } else { + blogs = []; + } + _refreshController.resetNoData(); + loadBlogs(true); + } + + void _onLoadMore() { + // if failed,use loadFailed(),if no data return,use LoadNodata() + if (_pageCount > _page) { + _page += 1; + loadBlogs(false); + } else { + _refreshController.loadNoData(); + } + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + BuildContext mainContext = context; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).blog), + backgroundColor: Theme.of(context).primaryColor, + actions: [], + ), + body: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode){ + Widget footer; + if(mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center(child: footer,), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoadMore, + child: _buildBody(), + ), + bottomNavigationBar: MobileBottomNav(currentIndex: 3,), + ); + } + + @override + void dispose() { + _refreshController?.dispose(); + super.dispose(); + } + + Widget _buildBody() { + if (blogs == null) { + return SizedBox.shrink(); + } + return ListView.builder( + itemCount: blogs.length <= 1 ? 1 : blogs.length, + itemBuilder: (BuildContext context, int i) { + if (blogs.length <= 0) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text(S.of(context).no_blog_yet), + ), + ); + } else { + Blog blog = blogs[i]; + + Widget w = Container( + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (blog.thumbUrl != null) ? + Container( + padding: EdgeInsets.only(right: 10.0), + child: Util.showImage( + 'https:${blog.thumbUrl}', + width: 80.0, + height: 80.0, + ), + ) : SizedBox.shrink(), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + blog.title, + style: TextStyle( + fontSize: 19.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt), + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + ], + ), + ), + ], + ), + ); + + return GestureDetector( + child: w, + onTap: () { + Routes.router.navigateTo(context, '/view-blog/${blog.id}'); + }, + ); + } + } + ); + } + + @override + void initState() { + super.initState(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + blogs = null; + }); + } + _refreshController.requestRefresh(); + }); + } + + void loadBlogs(bool isRefresh) { + _loadingFinish = false; + HttpUtil.httpGet( + 'v1/blogs', + businessId: widget.businessId, + queryParameters: { + 'page': _page.toString(), + 'size': Constants.BLOG_PER_PAGE_MOBILE.toString() + } + ).then((value) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshCompleted(); + } else { + _refreshController.loadComplete(); + } + + if (int.parse(value['currentPage'].toString()) >= int.parse(value['pageCount'].toString())) { + _loadingFinish = true; + } + if (_loadingFinish) { + _refreshController.loadNoData(); + } + _page = int.parse(value['currentPage'].toString()); + _pageCount = int.parse(value['pageCount'].toString()); + + setState(() { + if (blogs == null) { + blogs = []; + } + blogs.addAll((value['blogs'] as List).map((e) => Blog.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshFailed(); + } else { + _refreshController.loadFailed(); + } + _isLoading = false; + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_buy_service.dart b/lib/widgets/mobile/mobile_buy_service.dart new file mode 100644 index 0000000..4e0b587 --- /dev/null +++ b/lib/widgets/mobile/mobile_buy_service.dart @@ -0,0 +1,170 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../models/key_value.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileBuyService extends StatefulWidget { + final Map data; + + const MobileBuyService(this.data, {Key key}) + : super(key: key); + + @override + State createState() => MobileBuyServiceState(); +} + +class MobileBuyServiceState extends State { + List plans = []; + KeyValue selectedPlan; + double price = 0.0; + double tax = 0.0; + double paymentAmount = 0.0; + + @override + Widget build(BuildContext context) { + + Column col1 = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 16), + child: Text( + S.of(context).purchase_renew_service, + style: TextStyle( + fontSize: 28, + ), + ), + ), + ], + ); + + col1.children.add( + Utils.buildLine(S.of(context).service_descritpion, widget.data['service_selections']['description'], valueSize: 16), + ); + + col1.children.add( + Utils.buildLine(S.of(context).your_group, widget.data['group']['name'], valueSize: 16), + ); + + if (widget.data['exists_service'] == null) { + col1.children.add( + Utils.buildLine(S.of(context).current_plan, 'N/A', valueSize: 16), + ); + } else { + col1.children.add( + Utils.buildLine(S.of(context).current_plan, widget.data['exists_service']['description'], valueSize: 16), + ); + col1.children.add( + Utils.buildLine(S.of(context).expiration_date, + Utils.utcDatetimeStringToLocalDatetimeString(context, + widget.data['exists_service']['expiration_date']), + valueSize: 16 + ), + ); + } + + col1.children.add( + Utils.buildLine(S.of(context).select_a_plan, DropdownButton( + items: plans.map((e) { + return DropdownMenuItem( + value: e, + child: Text(e.name), + ); + }).toList(), + isExpanded: true, + hint: Text( + S.of(context).select_a_plan, + ), + onChanged: (newValue) { + print('newValue $newValue'); + setState(() { + selectedPlan = newValue; + price = selectedPlan.value['price']; + tax = selectedPlan.value['price'] * selectedPlan.value['tax']; + paymentAmount = price + tax; + }); + }, + value: selectedPlan, + ), valueSize: 16), + ); + col1.children.add( + Utils.buildLine(S.of(context).price, price.toStringAsFixed(2), valueSize: 16) + ); + col1.children.add( + Utils.buildLine(S.of(context).tax, tax.toStringAsFixed(2), valueSize: 16) + ); + col1.children.add( + Utils.buildLine(S.of(context).total, paymentAmount.toStringAsFixed(2), valueSize: 32) + ); + col1.children.add( + Container( + padding: EdgeInsets.only(left: 0, right: 0, top: 16), + alignment: Alignment.centerRight, + child: ElevatedButton( + child: Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), + child: Text( + S.of(context).pay_now, + style: TextStyle( + fontSize: 20, + ), + ), + ), + onPressed: (paymentAmount > 0) ? () => _submit() : null, + ), + ), + ); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).purchase_renew_service), + backgroundColor: Theme.of(context).primaryColor, + ), + body: SingleChildScrollView( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col1, + ), + ), + ); + } + + void _submit() { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/buy-service/${widget.data['group']['id']}/${widget.data['service_selections']['code']}'); + return; + } + Map newData = widget.data; + newData['selected_plan'] = selectedPlan.value; + HttpUtil.httpPost('v1/create-service-buy-renewal-invoice', + (response) { + Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true); + }, + body: newData, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } + + @override + void initState() { + super.initState(); + List o = (widget.data['service_selections']['options'] as List); + for (int i = 0; i < o.length; i++) { + Map o1 = o[i]; + plans.add(new KeyValue(o1['name'], o1)); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_change_mobile_or_email.dart b/lib/widgets/mobile/mobile_change_mobile_or_email.dart new file mode 100644 index 0000000..ef14b1f --- /dev/null +++ b/lib/widgets/mobile/mobile_change_mobile_or_email.dart @@ -0,0 +1,357 @@ + + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileChangeMobileOrEmail extends StatefulWidget { + final bool isMobile; + + const MobileChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key); + + @override + State createState() { + return MobileChangeMobileOrEmailState(); + } + +} + +class MobileChangeMobileOrEmailState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ListView( + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + widget.isMobile ? S.of(context).change_mobile : S.of(context).change_email, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 8.0), + child: Text( + widget.isMobile ? S.of(context).change_mobile_desc : S.of(context).change_email_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: widget.isMobile ? TextInputType.phone : TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: widget.isMobile ? S.of(context).mobile_number : S.of(context).email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + if (widget.isMobile) { + return S + .of(context) + .mobile_is_required; + } else { + return S + .of(context) + .email_is_required; + } + } + if (widget.isMobile && value.trim() == store.state.user.mobile) { + return S.of(context).the_mobile_number_is_same_as_current; + } + if (!widget.isMobile && value.trim() == store.state.user.email) { + return S.of(context).the_email_is_same_as_current; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 14.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit_to_change, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Utils.showMessageDialog(context, Exception(S.of(context).update_success), title: S.of(context).success, onOk: () { + Navigator.of(context).pop(); + Routes.router.navigateTo(context, '/me', replace: true, clearStack: true); + }); + }, + queryParameters: { + 'action': 'change-mobile-email', + }, + isFormData: true, + body: { + 'id': store.state.user.id, + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.isNotEmpty && + ((widget.isMobile && usernameController.text.trim() != store.state.user.mobile) || + (!widget.isMobile && usernameController.text.trim() != store.state.user.email))) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + }, + queryParameters: { + 'action': 'change_mobile_email_send_code' + }, + body: { + 'id': store.state.user.id, + 'mobile': usernameController.text, + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + String errorMsg = ''; + if (widget.isMobile && usernameController.text.trim().isEmpty) { + errorMsg = S.of(context).mobile_is_required; + } else if (!widget.isMobile && usernameController.text.trim().isEmpty) { + errorMsg = S.of(context).email_is_required; + } else if (widget.isMobile && usernameController.text.trim() == store.state.user.mobile) { + errorMsg = S.of(context).the_mobile_number_is_same_as_current; + } else if (!widget.isMobile && usernameController.text.trim() == store.state.user.email) { + errorMsg = S.of(context).the_email_is_same_as_current; + } + Fluttertoast.showToast( + msg: errorMsg, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_change_password.dart b/lib/widgets/mobile/mobile_change_password.dart new file mode 100644 index 0000000..5d10c0e --- /dev/null +++ b/lib/widgets/mobile/mobile_change_password.dart @@ -0,0 +1,286 @@ + +import 'package:flutter/material.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../generated/l10n.dart'; + +class MobileChangePassword extends StatefulWidget { + const MobileChangePassword({Key key}) : super(key: key); + + @override + State createState() { + return MobileChangePasswordState(); + } + +} + +class MobileChangePasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final oldPasswordController = TextEditingController(); + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).change_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: oldPasswordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).old_password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).current_password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 16.0, top: 16.0, bottom: 20.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ); + } + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).password_has_been_changed), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'change_password', + }, + isFormData: true, + body: { + 'id': store.state.user.id, + 'old_password': oldPasswordController.text.trim(), + 'password': passwordController.text.trim(), + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_checkout.dart b/lib/widgets/mobile/mobile_checkout.dart new file mode 100644 index 0000000..4596b64 --- /dev/null +++ b/lib/widgets/mobile/mobile_checkout.dart @@ -0,0 +1,2248 @@ + +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:intl/intl.dart'; +import 'package:toggle_switch/toggle_switch.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/address.dart'; +import '../../models/booking_date_time.dart'; +import '../../models/booking_time.dart'; +import '../../models/cart_info.dart'; +import '../../models/cart_line_item.dart'; +import '../../models/coupon.dart'; +import '../../models/error_message.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/shipping_rate.dart'; +import '../../models/text_value.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/sliding_up_panel.dart'; + +class MobileCheckout extends StatefulWidget { + final Key key; + final int businessId; + + const MobileCheckout(this.businessId, {this.key,}) : super(key: key); + + @override + State createState() { + return MobileCheckoutState(); + } + +} + +class MobileCheckoutState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { + CartInfo cartInfo; + Address shipAddress; + bool canSubmit; + List errorMessages; + List bookingTimeList; + List bookingDateTimeList; + List paymentPlatforms; + TextValue durationInTraffic; + int selectedCoupon; + double couponDiscountAmount = 0; + List coupons; + + int peopleCount = 2; + + final TextEditingController remarkController = new TextEditingController(); + String orderRemark = ''; + + int deliveryMethodIndex = 0; + String deliveryMethod; + List shippingRates = []; + ShippingRate selectedShippingRate; + + List shippingMethodLabels = []; + List shippingMethodIcons = []; + + GlobalKey _scaffoldKey = GlobalKey(); + + int bookingDateIndex; + int bookingTimeIndex; + int paymentPlatformIndex; + + GlobalKey slidingUpPanelKey = GlobalKey(); + SlidingUpPanel slidingUpPanel; + PanelController panelController = PanelController(); + Widget panel; + + double subtotal; + + TextEditingController newCouponController = TextEditingController(); + + @override + Widget build(BuildContext context) { + super.build(context); + + store.dispatch(UpdateContext(context)); + + if (cartInfo == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + if (cartInfo.businessInfo.deliveryPickup == false && cartInfo.businessInfo.deliveryCanadaPost == false && cartInfo.businessInfo.deliveryStoreDelivery == false) { + return Scaffold( + body: Container( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + S.of(context).no_delivery_method, + ), + RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).ok, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ), + ); + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if(errorMessages.length > 0) { + print('error: ${errorMessages.toString()}'); + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } + }); + + if (panel != null) { + slidingUpPanel = SlidingUpPanel( + key: slidingUpPanelKey, + minHeight: 0.0, + maxHeight: 400.0, + backdropEnabled: true, + backdropTapClosesPanel: false, + isDraggable: false, + controller: panelController, + panel: panel, + body: mainBody(), + ); + } + + Scaffold scaffold = Scaffold( + key: _scaffoldKey, + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).confirm_order), + backgroundColor: Theme.of(context).primaryColor, + centerTitle: true, + ), + body: WillPopScope( + child: slidingUpPanel, + onWillPop: () async { + if (panelController != null && panelController.isPanelOpen) { + panelController.close(); + return false; + } + return true; + }, + ), + bottomNavigationBar: Container( + padding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 0.0, right: 0.0), + height: 55.0, +// color: Colors.black87, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: Container( + height: 55.0, + color: Colors.black87, + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(top: 12.0, bottom: 12.0, left: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '\$${(cartInfo.totalPrice - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 20.0, + color: Colors.white, + ), + ), + cartInfo.businessInfo.isPublic ? SizedBox.shrink() : Container( + padding: EdgeInsets.only(top: 2.0, bottom: 2.0, left: 5.0, right: 5.0), + width: 100.0, + color: Colors.red, + child: Text( + S.of(context).under_renovation, + style: TextStyle( + color: Colors.white, + fontSize: 10.0, + ), + maxLines: 3, + softWrap: true, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + Expanded( + flex: 1, + child: GestureDetector( + child: Container( + height: 55.0, + color: canSubmit ? Colors.lightGreen : Colors.grey, + child: Center( + child: Text( + S.of(context).pay_now, + style: TextStyle( + color: Colors.white, + fontSize: 20.0, + ), + ), + ), + ), + onTap: () { + confirmOrder(); + }, + ), + ), + ], + ), + ), + ); + + return scaffold; + } + + String getShippingMethod(int index) { + if (shippingMethodLabels[index] == S.of(context).delivery) { + return 'store-delivery'; + } else if (shippingMethodLabels[index] == S.of(context).pickup) { + return 'pickup'; + } else if (shippingMethodLabels[index] == S.of(context).canada_post) { + return 'canada-post'; + } + return 'store-delivery'; + } + + Widget mainBody() { + return ListView.builder( + itemCount: 6, + addAutomaticKeepAlives: true, + itemBuilder: (BuildContext context, int position) { + var deliveryTimeInSeconds = cartInfo.businessInfo.shippingTime * 60 + (durationInTraffic != null ? durationInTraffic.value : 0); + print('aaa: $deliveryTimeInSeconds'); + DateTime now = DateTime.now(); + var formatter = DateFormat('H:mm'); + String deliveryAt = formatter.format(now.add(Duration(seconds: deliveryTimeInSeconds))); + + Widget peopleCountSelection = Container( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).table_token(store.state.tableNumber), + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(right: 10.0), + child: Text(S.of(context).number_of_people), + ), + DropdownButton( + value: peopleCount, + items: [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + ].map((value) { + return DropdownMenuItem( + value: value, + child: Text(value.toString()), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + peopleCount = newValue; + }); + } + }, + hint: Text(S.of(context).number_of_people), + isExpanded: false, + ) + ], + ), + ), + ], + ), + ); + + ToggleSwitch toggleSwitch = ToggleSwitch( + minWidth: 160.0, + cornerRadius: 20, + activeBgColor: [Colors.green], + activeFgColor: Colors.white, + inactiveBgColor: Colors.grey, + inactiveFgColor: Colors.white, + labels: shippingMethodLabels, + icons: shippingMethodIcons, + onToggle: (index) { + selectedShippingRate = null; + deliveryMethod = getShippingMethod(index); + check(); + }, + initialLabelIndex: deliveryMethodIndex, + ); + switch (position) { + case 0: + if (cartInfo.businessInfo.deliveryPickup) { + return Container( + padding: EdgeInsets.only( + top: 16.0, bottom: 16.0, left: 16.0, right: 16.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 10.0, + color: Colors.black12, + ), + ), + ), + child: Container( + alignment: Alignment.centerLeft, + child: store.state.deviceId != null && store.state.deviceId.isNotEmpty ? ( + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty ? + peopleCountSelection : + SizedBox.shrink() + ) : Center(child: toggleSwitch,), + ), + ); + } else { + return Container( + child: SizedBox(), + ); + } + break; + case 1: + if (store.state.deviceId != null && store.state.deviceId.isNotEmpty || + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty) { + return SizedBox.shrink(); + } + if (deliveryMethod == 'pickup') { + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).pickup_at, + style: TextStyle( + fontSize: 20.0, + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Text( + cartInfo.businessInfo.name, + style: TextStyle( + fontSize: 17.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 5.0), + child: Text( + cartInfo.businessInfo.address.addressLine1, + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ), + ), + Container( + child: cartInfo.businessInfo.address.addressLine2 != null + && cartInfo.businessInfo.address.addressLine2.length > 0 ? + Text(cartInfo.businessInfo.address.addressLine2, + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ) : SizedBox.shrink(), + ), + Container( + child: Text( + '${cartInfo.businessInfo.address.city}, ${cartInfo.businessInfo.address.state}, ${cartInfo.businessInfo.address.zip}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black45, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 5.0), + child: Text( + 'Tel: ${cartInfo.businessInfo.phone}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black54, + ) + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ) + ), + ), + ); + } + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Container( + padding: EdgeInsets.only(bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + shipAddress != null ? shipAddress.fullAddress : S.of(context).enter_delivery_address, + style: TextStyle( + fontSize: 16.0 + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: EdgeInsets.only(top: 6.0), + child: Text( + shipAddress != null ? shipAddress.contactName + ' ' + shipAddress.phone : '', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ) + ], + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/my-addresses/${cartInfo.businessInfo.id}', replace: true); + }, + ); + break; + case 2: + if (deliveryMethod == 'pickup' || store.state.deviceId != null && store.state.deviceId.isNotEmpty || + store.state.tableNumber != null && store.state.tableNumber.isNotEmpty) { + return SizedBox.shrink(); + } + if (deliveryMethod == 'canada-post') { + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + margin: EdgeInsets.only(right: 10.0), + child: Text( + S.of(context).canada_post_delivery, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + flex: 1, + child: Container( + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(right: 5.0), + alignment: Alignment.centerRight, + child: Text( + selectedShippingRate != null ? + '${selectedShippingRate.name} \$${selectedShippingRate.price.toStringAsFixed(2)}' : + S.of(context).please_select, + style: TextStyle( + fontSize: 15.0, + color: Colors.black38, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ), + ], + ), + ), + ), + ], + ), + ), + onTap: () { + onCanadaPostTapped(); + }, + ); + } + if (!cartInfo.businessInfo.instanceDelivery) { + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + child: Text(S.of(context).no_instance_delivery_desc), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ) + ); + } + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: Text( + bookingTimeList.length > 0 || bookingDateTimeList.length > 0 ? S.of(context).delivery_now : S.of(context).delivery_unavailable, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + bookingTimeList.length > 0 ? '${Utils.timestampToString(context, bookingTimeList[bookingTimeIndex].unixTime)}' + : ((bookingTimeList.length == 0 && bookingDateTimeList.length == 0) ? '' + : bookingDateTimeList[bookingDateIndex].viewDate + ' ' + (bookingDateTimeList[bookingDateIndex].bookTimes.length > 0 ? bookingDateTimeList[bookingDateIndex].bookTimes[bookingTimeIndex].viewTime : '')), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 18.0, + color: Colors.grey, + ), + ), + ], + ), + ), + ], + ), + ), + onTap: onDeliveryTap, + ); + case 3: + return SizedBox.shrink(); + // disable payment panel + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black12, + ), + ), + ), + child: GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).payment_method, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + paymentPlatforms.length == 0 ? S.of(context).pay_on_deliery : PaymentPlatform.getPaymentPlatformName(context, paymentPlatforms[paymentPlatformIndex].code), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 18.0, + color: Colors.grey, + ), + ) + ], + ), + ), + ], + ), + onTap: () { + onPaymentMethodTapped(); + }, + ), + ); + break; + case 4: + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + column.children.add(Container( + width: double.infinity, + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + cartInfo.businessInfo.name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.5, + ), + ), + ), + ),); + + subtotal = 0.0; + + for (var i = 0; i < cartInfo.productList.length; i++) { + subtotal += cartInfo.productList[i].totalPrice; + column.children.add(lineItem(cartInfo.productList[i])); + } + column.children.add(GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 8.0), + padding: EdgeInsets.only(top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + child: Text( + S.of(context).credit_coupon, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + getSelectedCouponName(), + style: TextStyle( + color: Colors.grey, + fontSize: 14.0, + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Colors.black38, + size: 18.0, + ), + ) + ], + ) + ], + ), + ), + onTap: () { + onCouponsTapped(); + }, + )); + + column.children.add(Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).subtotal, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${(subtotal - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + )); + + if (cartInfo.extraFeeList.length > 0) { + for (var i = 0; i < cartInfo.extraFeeList.length; i++) { + column.children.add(Container( + padding: EdgeInsets.only(bottom: 16.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.centerRight, + child: Text( + S.of(context).extra_fee_token(cartInfo.extraFeeList[i].name, cartInfo.extraFeeList[i].rate), + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${cartInfo.extraFeeList[i].price.toStringAsFixed(2)}' + ), + ), + ], + ), + )); + } + } + + column.children.add(Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).total, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + Container( + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${(cartInfo.totalPrice - couponDiscountAmount).toStringAsFixed(2)}', + style: TextStyle( + fontSize: 19.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 10.0, + ), + ), + ), + child: column, + ); + break; + case 5: + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 160.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).order_remark, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(right: 5.0), + child: Text( + orderRemark, + maxLines: 1, + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.grey, + size: 18.0, + ) + ], + ), + ), + ], + ), + ), + onTap: () { + onRemarkTapped(); + }, + ); + break; + default: + return SizedBox(); + } + }, + ); + } + + Widget lineItem(CartLineItem cartLineItem) { + return Container( + padding: EdgeInsets.only(top: 16.0, bottom: 0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(5.0), + child: Util.showImage('${cartLineItem.product.imagePath}', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 40.0, color: Colors.transparent,), + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + Utils.knownName(context, cartLineItem.name), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + cartLineItem.description, + style: TextStyle( + fontSize: 12.0, + color: Colors.black38, + ), + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + ) + ], + ), + ), + Container( + alignment: Alignment.centerRight, + margin: EdgeInsets.only(right: 10.0), + child: Text( + 'x${cartLineItem.quantity.round()}', + ), + ), + Container( + width: 60.0, + alignment: Alignment.centerRight, + child: Text( + '${cartLineItem.totalPrice.toStringAsFixed(2)}', + ), + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + canSubmit = false; + errorMessages = []; + bookingTimeList = []; + bookingDateTimeList = []; + paymentPlatforms = []; + bookingDateIndex = 0; + bookingTimeIndex = 0; + paymentPlatformIndex = 0; + deliveryMethodIndex = 0; + deliveryMethod = ''; + panel = Container( + child: SizedBox(), + ); + remarkController.addListener(remarkChangeListener); + check(); + } + + void check() { + if (cartInfo != null) { + Utils.showLoadingDialog(context); + } + CartInfo ci = Utils.getCartInfoByBusinessId(store.state.cartInfos, widget.businessId); + HttpUtil.httpPost('v1/orders/check', (response) { + if (cartInfo != null) { + Navigator.of(context).pop(); + } + Utils.jsonPrettyPrint(response.data); + if (mounted) { + shippingMethodLabels.clear(); + shippingMethodIcons.clear(); + setState(() { + cartInfo = CartInfo.fromJson(response.data['cart_info']); + shipAddress = response.data['last_address'] != null ? Address.fromJson(response.data['last_address']) : null; + canSubmit = response.data['can_submit']; + errorMessages = (response.data['error_messages'] as List).map((e) => ErrorMessage.fromJson(e)).toList(); + bookingTimeList = (response.data['booking_time_list'] as List).map((e) => BookingTime.fromJson(e)).toList(); + bookingDateTimeList = (response.data['booking_date_time_list'] as List).map((e) => BookingDateTime.fromJson(e)).toList(); + paymentPlatforms = (response.data['payment_platforms'] as List).map((e) => PaymentPlatform.fromJson(e)).toList(); + durationInTraffic = response.data['duration_in_traffic'] != null ? TextValue.fromJson(response.data['duration_in_traffic']) : null; + selectedCoupon = response.data['selected_coupon']; + coupons = (response.data['coupons'] as List).map((e) => Coupon.fromJson(e)).toList(); + deliveryMethod = response.data['delivery']; + shippingRates = (response.data['shipping_rates'] as List).map((e) => ShippingRate.fromJson(e)).toList(); + selectedShippingRate = (response.data['selected_shipping_rate'] as String).length > 0 ? ShippingRate.fromJson(json.decode(response.data['selected_shipping_rate'])) : null; + int i = 0; + if (cartInfo.businessInfo.deliveryStoreDelivery) { + shippingMethodLabels.add(S.of(context).delivery); + shippingMethodIcons.add(Icons.directions_car); + if (deliveryMethod == 'store-delivery') { + deliveryMethodIndex = i; + } + i++; + } + if (cartInfo.businessInfo.deliveryCanadaPost) { + shippingMethodLabels.add(S.of(context).canada_post); + shippingMethodIcons.add(Icons.local_shipping); + if (deliveryMethod == 'canada-post') { + deliveryMethodIndex = i; + } + i++; + } + if (cartInfo.businessInfo.deliveryPickup) { + shippingMethodLabels.add(S.of(context).pickup); + shippingMethodIcons.add(Icons.store); + if (deliveryMethod == 'pickup') { + deliveryMethodIndex = i; + } + i++; + } + }); + } + }, + businessId: widget.businessId, + body: { + 'product_list': ci.productList.toString(), + 'extra_data': ci.extraData, + 'business_id': widget.businessId, + 'pay_method': getPaymentMethod(), + 'delivery': deliveryMethod, + 'selected_coupon': selectedCoupon, + 'selected_shipping_rate': selectedShippingRate != null ? selectedShippingRate.toString() : '', + 'shipping_rates': shippingRates.toString(), + 'device_id': store.state.deviceId != null ? store.state.deviceId : '', + 'table_number': store.state.tableNumber != null ? store.state.tableNumber : '', + }, + isFormData: true, + ).catchError((error) { + if (cartInfo != null) { + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + + int getPaymentMethod() { + int method = 0; + if (paymentPlatforms.length > 0 && paymentPlatformIndex < paymentPlatforms.length) { + PaymentPlatform paymentPlatform = paymentPlatforms[paymentPlatformIndex]; + if (paymentPlatform.method == Constants.PAYMENT_METHOD_PAY_ON_DELIVERY) { + return 1; + } + } + return method; + } + + void afterBuild(Duration time) { + if(errorMessages.length > 0) { + print('error: ${errorMessages.toString()}'); + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } + } + + SnackBar errorSnackBar(List messages) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + for (var i = 0; i < messages.length; i++) { + column.children.add(Container( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 10.0, bottom: 0.0), + child: Text( + Utils.errorMsg2(context, messages[i].code, messages[i].string), + ), + )); + } + return SnackBar( + content: Container( + height: 60.0 * messages.length, + child: column, + ), + action: SnackBarAction( + label: S.of(context).ok, + onPressed: () { + // _scaffoldKey.currentState.hideCurrentSnackBar(); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ); + } + + Widget getBookingTimeWidget() { + Widget widget; + if (bookingTimeList.length > 0) { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: ListView.builder( + itemCount: bookingTimeList.length, + itemBuilder: (BuildContext context, int position) { + BookingTime bookingTime = bookingTimeList[position]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 10.0, top: 10.0), + child: Text( + '${Utils.timestampToString(context, bookingTime.unixTime)}', + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.3, + color: Colors.black12, + ) + ) + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingDateIndex = 0; + bookingTimeIndex = position; + }); + } + panelController.close(); + }, + ); + }, + ), + ) + ], + ); + } else if (bookingDateTimeList.length > 0) { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + width: 150.0, + color: new Color(0xFFF5F5F5), + child: SizedBox.expand( + child: ListView.builder( + itemCount: bookingDateTimeList.length, + itemBuilder: (BuildContext context, int position) { + BookingDateTime bookingDateTime = bookingDateTimeList[position]; + return GestureDetector( + child: Container( + color: bookingDateIndex == position ? Colors.white : Colors.transparent, + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + child: Center( + child: Text( + '${bookingDateTime.viewDate} (${Utils.getWeekDayName(context, bookingDateTime.unixTime, true)})', + ), + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingDateIndex = position; + panel = getBookingTimeWidget(); + }); + } + }, + ); + }, + ), + ), + ), + Expanded( + child: SizedBox.expand( + child: ListView.builder( + itemCount: bookingDateTimeList[bookingDateIndex].bookTimes.length, + itemBuilder: (BuildContext context, int position) { + BookingDateTime bookingDateTime = bookingDateTimeList[bookingDateIndex]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0, bottom: 12.0), + child: Text( + bookingDateTime.bookTimes[position].viewTime, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.3, + ), + ), + ), + ), + onTap: () { + if (mounted) { + setState(() { + bookingTimeIndex = position; + }); + } + panelController.close(); + }, + ); + }, + ), + ), + ), + ], + ), + ) + ], + ); + } else { + widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_delivery_time, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).close), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + Expanded( + child: Container( + padding: EdgeInsets.all(20.0), + child: Center( + child: Text( + S.of(context).shipping_time_will_schedule, + ), + ), + ), + ) + ], + ); + } + return widget; + } + + Widget getPaymentMethods() { + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).select_a_payment_method, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + + if (paymentPlatforms.length == 0) { + widget.children.add(Center( + child: Container( + padding: EdgeInsets.all(16.0), + child: Text( + S.of(context).payment_method_not_set, + ), + ), + )); + } else { + widget.children.add( + Expanded( + child:ListView.builder( + itemCount: paymentPlatforms.length, + itemBuilder: (BuildContext context, int position) { + PaymentPlatform paymentPlatform = paymentPlatforms[position]; + if (paymentPlatform.code == Constants.PAYMENT_METHOD_CODE_SQUARE && + (paymentPlatform.squareAppId == null || paymentPlatform.squareAppId.isEmpty) && + (paymentPlatform.squareAccessToken == null || paymentPlatform.squareAccessToken.isEmpty) && + (paymentPlatform.squareLocationId == null || paymentPlatform.squareLocationId.isEmpty) + ) { + return SizedBox.shrink(); + } + if (paymentPlatform.code == Constants.PAYMENT_METHOD_CODE_CHASE && + (paymentPlatform.xLogin == null || paymentPlatform.xLogin.isEmpty) && + (paymentPlatform.transactionKey == null || paymentPlatform.transactionKey.isEmpty) && + (paymentPlatform.responseKey == null || paymentPlatform.responseKey.isEmpty) + ) { + return SizedBox.shrink(); + } + Widget paymentWidget = GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: paymentPlatformIndex == position ? BoxDecoration( + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.3, + color: Colors.black12, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Util.showImage(paymentPlatform.icon, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 50.0, color: Colors.transparent,), + fit: BoxFit.cover, + width: 50.0, + height: 50.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 10.0), + child: Text( + PaymentPlatform.getPaymentPlatformName(context, paymentPlatform.code), + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + Container( + child: Icon( + Icons.check, + size: 32.0, + color: paymentPlatformIndex == position ? Colors.blue : Colors.grey, + ), + ), + ], + ), + ), + onTap: () { + setState(() { + paymentPlatformIndex = position; + }); + panelController.close(); + }, + ); + return paymentWidget; + } + ), + ), + ); + } + return widget; + } + + String getSelectedCouponName() { + if (selectedCoupon == null) { + if (coupons.length > 0) { + return S + .of(context) + .please_select; + } else { + return S + .of(context) + .no_coupon_available; + } + } else if (selectedCoupon == 0) { + return S.of(context).dont_use; + } else { + for (var i = 0; i < coupons.length; i++) { + if (selectedCoupon == coupons[i].id) { + if (coupons[i].isPercentage) { + return S.of(context).percentage_discount_token2(couponDiscountAmount.toStringAsFixed(2), coupons[i].valueAmount); + } else { + return S.of(context).discount_amount_token(couponDiscountAmount.toStringAsFixed(2)); + } + } + } + return 'N/A'; + } + } + + Widget getCouponList() { + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).pick_a_coupon, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + + widget.children.add( + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 5.0, + color: Colors.black26, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: TextFormField( + controller: newCouponController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).enter_coupon_code, + ), + validator: (String value) { + return null; + }, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: RaisedButton( + child: Text( + S.of(context).get_coupon, + ), + onPressed: () { + if (newCouponController.text.trim().isEmpty) { + Fluttertoast.showToast( + msg: S.of(context).please_enter_coupon_code, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white, + ); + } else { + _checkCouponCode(newCouponController.text.trim()); + } + }, + ), + ), + ], + ), + ), + ); + + if (paymentPlatforms.length == 0) { + widget.children.add(Center( + child: Container( + padding: EdgeInsets.all(16.0), + child: Text( + S.of(context).no_coupon_available, + ), + ), + )); + } else { + widget.children.add(Expanded( + child: ListView.builder( + itemCount: coupons.length + 1, + itemBuilder: (BuildContext context, int position) { + if (position == 0) { + return GestureDetector( + child: Container( + decoration: selectedCoupon == 0 ? BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Text( + S.of(context).dont_use, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedCoupon == 0 ? Colors.lightBlue : Colors.black26, + ), + ), + ], + ), + ), + onTap: () { + if (mounted) { + setState(() { + selectedCoupon = 0; + couponDiscountAmount = 0; + }); + panelController.close(); + } + }, + ); + } else { + Coupon coupon = coupons[position - 1]; + return GestureDetector( + child: Container( + decoration: selectedCoupon == coupon.id ? BoxDecoration( + color: subtotal > cartInfo.businessInfo.minPrice ? Colors + .transparent : Colors.black38, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: subtotal > coupon.minAmount ? Colors + .transparent : Colors.black26, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: coupon.isPercentage ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 30.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S + .of(context) + .percent_discount, + style: TextStyle( + fontSize: 12.0, + color: Colors.redAccent, + ), + ), + ) + ], + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Text( + '\$', + style: TextStyle( + fontSize: 12.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 30.0, + color: Colors.redAccent, + ), + ), + ) + ], + ), + ), + Expanded( + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + child: Text( + '${coupon.description}', + style: TextStyle( + fontSize: 11.0, + color: Colors.black45, + ), + ), + ), + Container( + child: Text( + coupon.expirationDate != null ? + S.of(context).expiration_date_token( + coupon.expirationDate) : + S.of(context) + .no_expiration, + style: TextStyle( + fontSize: 11.0, + color: Colors.black26, + ), + ), + ), + Container( + child: Text( + coupon.minAmount > 0 ? + S.of(context).min_order_amount_token( + coupon.minAmount) : + S.of(context) + .no_restriction, + style: TextStyle( + fontSize: 11.0, + color: Colors.black26, + ), + ), + ) + ], + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedCoupon == coupon.id ? Colors.lightBlue : Colors.black26, + ), + ) + ], + ), + ), + onTap: () { + if (subtotal > coupon.minAmount && + mounted) { + setState(() { + selectedCoupon = coupon.id; + if (coupon.isPercentage) { + couponDiscountAmount = subtotal * coupon.valueAmount / 100.0; + } else { + couponDiscountAmount = coupon.valueAmount; + } + }); + panelController.close(); + } + }, + ); + } + }, + ), + ),); + } + return widget; + } + + remarkChangeListener() { + orderRemark = remarkController.text; + } + + Widget remarkPanel() { + Column quickInputs = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + if (cartInfo.businessInfo.quickInputs.length > 0) { + Wrap w = Wrap( + children: [], + ); + quickInputs.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).quick_input, + style: TextStyle( + fontSize: 18.0, + color: Colors.black54, + ), + ), + )); + for (int i = 0; i < cartInfo.businessInfo.quickInputs.length; i++) { + String qi = cartInfo.businessInfo.quickInputs[i].value; + w.children.add(FlatButton( + child: Text( + qi, + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + onPressed: () { + orderRemark += ' ' + qi; + remarkController.text = orderRemark; + }, + )); + } + quickInputs.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0), + child: w, + )); + } + + Column widget = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).order_remark, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).save), + onPressed: () { + if (mounted) { + setState(() { + orderRemark = remarkController.text; + }); + } + panelController.close(); + }, + ), + ), + ], + ), + Expanded( + child: ListView( + children: [ + Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 16.0, right: 16.0), + child: TextField( + controller: remarkController, + keyboardType: TextInputType.multiline, + maxLines: 5, + maxLength: 100, + decoration: new InputDecoration( + border: new OutlineInputBorder( + borderRadius: const BorderRadius.all( + const Radius.circular(12.0), + ), + ), + filled: true, + hintStyle: new TextStyle(color: Colors.grey[800]), + hintText: S.of(context).type_your_order_remark, + fillColor: Colors.white70, + ), + ), + ), + quickInputs, + ], + ), + ), + ], + ); + return widget; + } + + Widget canadaPostPanel() { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0), + child: Text( + S.of(context).choose_a_shipping_rate, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), + child: FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + panelController.close(); + }, + ), + ) + ], + ), + ], + ); + column.children.add(Expanded( + child: ListView.builder( + itemCount: shippingRates.length, + itemBuilder: (BuildContext context, int position) { + ShippingRate shippingRate = shippingRates[position]; + return GestureDetector( + child: Container( + decoration: selectedShippingRate == shippingRate ? BoxDecoration( + color: Colors.transparent, + border: Border( + top: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + bottom: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + left: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + right: BorderSide( + width: 3.0, + color: Colors.lightBlue, + ), + ), + ) : BoxDecoration( + color: Colors.transparent, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Container( + child: Text( + shippingRate.name, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.centerRight, + child: Text( + '${shippingRate.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + color: Colors.black38, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, top: 16.0, right: 16.0, bottom: 16.0), + child: Icon( + Icons.check, + size: 30.0, + color: selectedShippingRate == shippingRate ? Colors.lightBlue : Colors.black26, + ), + ) + ], + ), + ), + onTap: () { + selectedShippingRate = shippingRate; + panelController.close(); + check(); + }, + ); + } + ), + ),); + + return column; + } + + void onDeliveryTap() { + panelController.open(); + setState(() { + panel = getBookingTimeWidget(); + }); + } + + void onPaymentMethodTapped() { + panelController.open(); + setState(() { + panel = getPaymentMethods(); + }); + } + + void onCouponsTapped() { + panelController.open(); + setState(() { + panel = getCouponList(); + }); + } + + void onRemarkTapped() { + panelController.open(); + setState(() { + panel = remarkPanel(); + }); + } + + void onCanadaPostTapped() { + panelController.open(); + setState(() { + panel = canadaPostPanel(); + }); + } + + confirmOrder() { + if (!canSubmit) { + // _scaffoldKey.currentState.showSnackBar(errorSnackBar(errorMessages)); + ScaffoldMessenger.of(context).showSnackBar(errorSnackBar(errorMessages)); + } else { + Utils.showSubmitDialog(context); + HttpUtil.httpPost('v1/orders', (response) { + Navigator.of(context).pop(); + store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(store.state.cartInfos, cartInfo))); + eventBus.fire(OnCartInfoUpdated()); + Order order = Order.fromJson(response.data); + PaymentPlatform paymentPlatform = paymentPlatforms[paymentPlatformIndex]; + Routes.router.navigateTo(context, '/paynow/${order.id}', replace: true); + }, + businessId: widget.businessId, + body: { + 'cart_id': cartInfo.id, + 'remark': orderRemark, + 'booked_at': bookingTimeList.length > 0 + ? bookingTimeList[bookingTimeIndex].unixTime + : ( + (bookingTimeList.length == 0 && bookingDateTimeList.length == 0) ? + 0 : + bookingDateTimeList[bookingDateIndex].bookTimes[bookingTimeIndex] + .unixTime + ), + 'delivery': deliveryMethod, + 'selected_coupon': selectedCoupon, + 'delivery_method': deliveryMethod, + 'coupon_discount_amount': couponDiscountAmount, + 'device_id': store.state.deviceId != null ? store.state.deviceId : '', + 'table_number': store.state.tableNumber != null ? store.state.tableNumber : '', + 'people_count': peopleCount, + }, + isFormData: true, + ).catchError((error) { + Navigator.of(context).pop(); + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + } + + @override + bool get wantKeepAlive => true; + + _checkCouponCode(String code) { + Utils.showSubmitDialog(context); + HttpUtil.httpGet('v1/check-coupon-code', + queryParameters: { + 'coupon_code': code, + 'store_id': widget.businessId, + }).then((data) { + Navigator.of(context).pop(); + if (mounted) { + setState(() { + newCouponController.text = ''; + coupons = (data as List).map((e) => Coupon.fromJson(e)).toList(); + panel = getCouponList(); + }); + } + }).catchError((error) { + Navigator.of(context).pop(); + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_contact_us.dart b/lib/widgets/mobile/mobile_contact_us.dart new file mode 100644 index 0000000..690e48e --- /dev/null +++ b/lib/widgets/mobile/mobile_contact_us.dart @@ -0,0 +1,333 @@ + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:universal_io/io.dart'; + +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../widgets/general/text_link.dart'; + +class MobileContactUs extends StatefulWidget { + final Business business; + + const MobileContactUs(this.business, {Key key}) : super(key: key); + + @override + State createState() { + return MobileContactUsState(); + } + +} + +class MobileContactUsState extends State { + + String mapUrl = 'https://goo.gl/maps/M365MF5AW35n9ij67'; + + Completer _controller = Completer(); + LatLng _lastMapPosition; + final Set _markers = {}; + final Set _polyLine = {}; + + void _onMapCreated(GoogleMapController controller) { + _controller.complete(controller); + } + + void _onCameraMove(CameraPosition position) { + _lastMapPosition = position.target; + } + + @override + Widget build(BuildContext context) { + Column col = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + col.children.add( + Container( + padding: EdgeInsets.only(top: 20.0, left: 16, right: 16, bottom: 20), + child: Center( + child: Text( + S.of(context).contact_us, + style: TextStyle( + fontSize: 36, + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0, bottom: 20), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 20), + child: Util.showImage( + '${widget.business.picUrl}', + width: 100, + height: 100, + ), + ), + Container( + child: Text( + '${widget.business.name}', + style: TextStyle( + fontSize: 20, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black38, + ), + ), + ), + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.mail_outline, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).by_email, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + 'support@wisetronic.com', + 'support@wisetronic.com', + isEmail: true, + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.phone, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).by_phone, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + '905-604-8861', + '905-604-8861', + isPhone: true, + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: TextLink( + '905-604-6681', + '905-604-6681', + isPhone: true, + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Row( + children: [ + Container( + child: Text( + S.of(context).toll_free, + ), + ), + Container( + child: TextLink( + '1-855-278-8026', + '1-855-278-8026', + isPhone: true, + ), + ), + ], + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8), + child: Container( + padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.location_on, size: 18, color: Colors.black38,), + ), + Text( + S.of(context).address, + style: TextStyle( + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Colors.black12, + ), + ), + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.addressLine1}', + ), + ) + ); + if (widget.business.address.addressLine2 != null && widget.business.address.addressLine2.isNotEmpty) { + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.addressLine2}', + ), + ) + ); + } + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.city}, ${widget.business.address.state}', + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), + child: Text( + '${widget.business.address.country}, ${widget.business.address.zip}', + ), + ) + ); + if (Platform.isAndroid || Platform.isIOS) { + _markers.clear(); + _markers.add(Marker( + markerId: MarkerId('shop_position'), + position: LatLng(double.parse(widget.business.address.lat), + double.parse(widget.business.address.lng)), + infoWindow: InfoWindow( + title: S + .of(context) + .store, + snippet: '', + ), + )); + col.children.add( + Container( + height: 200, + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), + child: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: LatLng( + double.parse(widget.business.address.lat), + double.parse(widget.business.address.lng)), + zoom: 14.0, + ), + onCameraMove: _onCameraMove, + markers: _markers, + gestureRecognizers: >[ + new Factory(() => new EagerGestureRecognizer(),) + ].toSet(), + ), + ), + ); + } else { + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), + child: TextLink( + S.of(context).view_on_google_map, + mapUrl, + isLink: true, + ), + ) + ); + } + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).contact_us), + backgroundColor: Theme.of(context).primaryColor, + ), + body: SingleChildScrollView( + child: col, + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_coupons.dart b/lib/widgets/mobile/mobile_coupons.dart new file mode 100644 index 0000000..47bb473 --- /dev/null +++ b/lib/widgets/mobile/mobile_coupons.dart @@ -0,0 +1,307 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../generated/l10n.dart'; +import '../../models/coupon.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + +class MobileCoupons extends StatefulWidget { + final int contactId; + final Key key; + const MobileCoupons(this.contactId, {this.key}); + + @override + State createState() { + return MobileCouponsState(); + } +} + +class MobileCouponsState extends State { + List coupons; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (coupons == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + } + ), + title: Text( + S.of(context).coupons, + ), + ), + body: ListView.builder( + itemCount: coupons.length > 0 ? coupons.length : 1, + itemBuilder: (BuildContext context, int position) { + if (coupons.length > 0) { + Coupon coupon = coupons[position]; + return Container( + color: Colors.black12, + child: couponWidget(coupon), + ); + } else { + return Center( + child: Container( + padding: EdgeInsets.all(20.0), + child: Text( + S.of(context).no_coupon_available, + ), + ), + ); + } + } + ), + ); + } + + Widget couponWidget(Coupon coupon) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: coupon.store != null ? + Util.showImage('${coupon.store.picUrl}', + fit: BoxFit.fill, + width: 40.0, + ) : + Image.asset( + 'assets/images/ic_launcher.png', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + coupon.store != null ? coupon.store.name : S.of(context).general_coupon, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + coupon.description, + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ) + ], + ), + ), + ), + ], + ); + + Column valueColumn = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: !coupon.isPercentage ? + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '\$', + style: TextStyle( + fontSize: 15.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 28.0, + color: Colors.redAccent, + ), + ), + ), + ], + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '${Utils.smartRound(coupon.valueAmount, 2)}', + style: TextStyle( + fontSize: 28.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).percent_discount, + style: TextStyle( + fontSize: 15.0, + color: Colors.redAccent, + ), + ), + ), + ], + ), + ), + Container( + child: Text( + coupon.minAmount > 0 ? + S.of(context).available_for_order_over_token(coupon.minAmount) : + S.of(context).no_restriction, + style: TextStyle( + fontSize: 10.0, + color: Colors.black26, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + + row.children.add(valueColumn); + column.children.add(row); + column.children.add(Container( + width: double.infinity, + margin: EdgeInsets.only(top: 10.0, bottom: 0.0), + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 0.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12 + ) + ) + ), + child: SizedBox.shrink(), + )); + + column.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + coupon.expirationDate == null || coupon.expirationDate.length == 0 ? + S.of(context).no_expiration : + S.of(context).expiration_date_token(coupon.expirationDate), + style: TextStyle( + color: Colors.black26, + fontSize: 15.0, + ), + ), + ), + Container( + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).redeem_coupon, + style: TextStyle( + color: Colors.white, + ), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + onPressed: () { + if (coupon.store != null) { + Routes.router.navigateTo(context, '/shop/${coupon.store.id}/na/na/na'); + } else { + Routes.router.navigateTo(context, '/businesses'); + } + }, + ), + ), + ], + ), + ); + + return Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0, bottom: 5.0), + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 10.0, right: 10.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + ), + ), + child: column, + ); + } + + @override + void initState() { + super.initState(); + coupons = null; + HttpUtil.httpGet( + 'v1/coupons' + ).then((data) { + if (mounted) { + setState(() { + coupons = + (data as List).map((e) => Coupon.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_download_apps.dart b/lib/widgets/mobile/mobile_download_apps.dart index e341feb..ef1b1a2 100644 --- a/lib/widgets/mobile/mobile_download_apps.dart +++ b/lib/widgets/mobile/mobile_download_apps.dart @@ -35,7 +35,9 @@ class MobileDownloadAppsState extends State { for (int i = 0; i < apps.length; i++) { col.children.add(apps[i]); } - return col; + return SingleChildScrollView( + child: col, + ); } List _getApps() { diff --git a/lib/widgets/mobile/mobile_edit_address.dart b/lib/widgets/mobile/mobile_edit_address.dart new file mode 100644 index 0000000..83b3313 --- /dev/null +++ b/lib/widgets/mobile/mobile_edit_address.dart @@ -0,0 +1,565 @@ + + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/constants.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/address.dart'; +import 'package:flutter_wisetronic/store/actions.dart'; +import 'package:flutter_wisetronic/store/store.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:gender_selection/gender_selection.dart'; + +import '../../routes.dart'; + +class MobileEditAddress extends StatefulWidget { + final Key key; + final Address address; + final int businessId; + const MobileEditAddress(this.address, {this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + State createState() { + return MobileEditAddressState(); + } + +} + +class MobileEditAddressState extends State { + final GlobalKey _formKey = new GlobalKey(); + + final contactNameController = TextEditingController(); + final phoneController = TextEditingController(); + final streetLine1Controller = TextEditingController(); + final streetLine2Controller = TextEditingController(); + final cityController = TextEditingController(); + final postalCodeController = TextEditingController(); + final emailController = TextEditingController(); + final faxController = TextEditingController(); + + String country; + Gender _selectedGender; + + String _selectedProvince; + + bool showLoading; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (showLoading) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S + .of(context) + .edit_address), + backgroundColor: Theme + .of(context) + .primaryColor, + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + padding: EdgeInsets.only( + left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: contactNameController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .contact_name, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .contact_name_is_required; + } + return null; + }, + ), + ), + GenderSelection( + maleText: S + .of(context) + .mr, + femaleText: S + .of(context) + .ms, + selectedGenderIconBackgroundColor: Colors.indigo, + checkIconAlignment: Alignment.bottomRight, + selectedGenderCheckIcon: Icons.check, + onChanged: (Gender gender) { + _selectedGender = gender; + }, + equallyAligned: true, + animationDuration: Duration(milliseconds: 400), + isCircular: true, + isSelectedGenderIconCircular: true, + opacityOfGradient: 0.6, + padding: const EdgeInsets.all(3.0), + size: 50, + selectedGender: _selectedGender, + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + keyboardType: TextInputType.phone, + controller: phoneController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .mobile_phone_number, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .mobile_phone_number_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine1Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .street_line_1, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .street_line_1_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine2Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .street_line_2, + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: cityController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .city, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .city_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: DropdownButton( + value: _selectedProvince, + items: [ + 'Ontario', + 'Quebec', + 'British Columbia', + 'Alberta', + 'Manitoba', + 'Saskatchewan', + 'Nova Scotia', + 'New Brunswich', + 'Newfoundland and Labrador', + 'Prince Edward Island', + 'Northwest Territories', + 'Nunavut', + 'Yukon', + ].map((value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + _selectedProvince = newValue; + }); + } + }, + hint: Text(S + .of(context) + .province), + isExpanded: true, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: postalCodeController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .postal_code, + ), + validator: (String value) { + if (value + .trim() + .isEmpty) { + return S + .of(context) + .postal_code_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 32.0, bottom: 0.0), + child: Text( + S + .of(context) + .optional_information, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 0.0, bottom: 0.0), + child: TextFormField( + controller: emailController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .email, + ), + validator: (String value) { + if (value.isNotEmpty && !EmailValidator.validate(value)) { + return S + .of(context) + .email_is_not_valid; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: TextFormField( + controller: faxController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S + .of(context) + .fax, + ), + ), + ), + ], + ), + ), + ), + bottomNavigationBar: Container( + padding: EdgeInsets.all(8.0), + color: Theme + .of(context) + .buttonColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlatButton( + child: Text( + S + .of(context) + .delete, + ), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S + .of(context) + .warning), + content: Text(S + .of(context) + .are_you_sure_to_delete_the_address), + actions: [ + FlatButton( + child: Text(S + .of(context) + .cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S + .of(context) + .yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _deleteAddress(); + }, + ) + ], + ); + } + ); + }, + ), + FlatButton( + child: Text( + S + .of(context) + .save + ), + onPressed: () { + _saveEditAddress(); + }, + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + showLoading = false; + _selectedProvince = widget.address.state; + cityController.text = widget.address.city; + postalCodeController.text = widget.address.zip; + streetLine1Controller.text = widget.address.addressLine1; + streetLine2Controller.text = widget.address.addressLine2; + _selectedGender = widget.address.gender == 1 ? Gender.Male : Gender.Female; + country = widget.address.country; + contactNameController.text = widget.address.contactName; + phoneController.text = widget.address.phone; + emailController.text = widget.address.email; + faxController.text = widget.address.fax; + }); + } + + void _saveEditAddress() { + final FormState form = _formKey.currentState; + if (form.validate()) { + if (mounted) { + setState(() { + showLoading = true; + }); + } + HttpUtil.httpPatch('v1/addresses/${widget.address.id}', (response) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + eventBus.fire(OnAddressesUpdated()); + if (widget.businessId > 0) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + } else { + Navigator.of(context).pop(); + } + }, + body: { + 'id': widget.address.id, + 'name': contactNameController.text.trim(), + 'address_line1': streetLine1Controller.text.trim(), + 'address_line2': streetLine2Controller.text.trim(), + 'city': cityController.text.trim(), + 'state': _selectedProvince, + 'zip': postalCodeController.text.trim(), + 'phone': phoneController.text.trim(), + 'gender': _selectedGender == Gender.Male ? true : false, + 'email': emailController.text, + 'fax': faxController.text, + 'country': country, + } + ).catchError((error) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } + } + + void _deleteAddress() { + if (mounted) { + setState(() { + showLoading = true; + }); + } + HttpUtil.httpDelete('v1/addresses/${widget.address.id}', (response) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Fluttertoast.showToast( + msg: S.of(context).the_address_has_been_deleted, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + eventBus.fire(OnAddressesUpdated()); + Navigator.of(context).pop(); + }).catchError((error) { + if (mounted) { + setState(() { + showLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_forgot_password.dart b/lib/widgets/mobile/mobile_forgot_password.dart new file mode 100644 index 0000000..495680f --- /dev/null +++ b/lib/widgets/mobile/mobile_forgot_password.dart @@ -0,0 +1,321 @@ + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileForgotPassword extends StatefulWidget { + const MobileForgotPassword({Key key}) : super(key: key); + + @override + State createState() { + return MobileForgotPasswordState(); + } + +} + +class MobileForgotPasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ListView( + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).forgot_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 8.0), + child: Text( + S.of(context).forgot_password_description, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38 + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).mobile_or_email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_or_email_is_required; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 12.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).verify, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Routes.router.navigateTo(context, '/reset-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true); + }, + queryParameters: { + 'action': 'forgot_password_verify_code' + }, + isFormData: true, + body: { + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.trim().isNotEmpty) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + }, + queryParameters: {'action': 'forgot_password'}, + body: { + 'mobile': usernameController.text.trim(), + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).enter_mobile_or_email, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_igoshow_learn_more.dart b/lib/widgets/mobile/mobile_igoshow_learn_more.dart new file mode 100644 index 0000000..3aaee3b --- /dev/null +++ b/lib/widgets/mobile/mobile_igoshow_learn_more.dart @@ -0,0 +1,125 @@ + +import 'package:flutter/material.dart'; +import 'package:youtube_player_iframe/youtube_player_iframe.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class MobileiGoShowLearnMore extends StatefulWidget { + final Map data; + const MobileiGoShowLearnMore(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return MobileiGoShowLearnMoreState(); + } + +} + +class MobileiGoShowLearnMoreState extends State { + + @override + Widget build(BuildContext context) { + Column col = Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 20.0), + child: Util.showImage( + 'https:${widget.data['image-top']['image']}', + fit: BoxFit.contain, + ), + ), + + ], + ); + List w = _getContent(); + for (int i = 0; i < w.length; i++) { + col.children.add(w[i]); + } + return SingleChildScrollView( + child: col, + ); + } + + @override + void initState() { + super.initState(); + } + + List _getContent() { + List widgets = []; + for (int i = 0; i < (widget.data['sections'] as List).length; i++) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + col.children.add(Container( + margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0), + child: Text( + '${(widget.data['sections'] as List)[i]['title']}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + color: Colors.black87, + ), + ), + )); + if ((widget.data['sections'] as List)[i]['image'] != null) { + col.children.add( + Container( + padding: EdgeInsets.only( + top: 10.0, left: 8.0, right: 8.0, bottom: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Util.showImage( + 'https:${(widget + .data['sections'] as List)[i]['image']['image']}', + fit: BoxFit.contain, + ), + ), + ), + ); + } else if (((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 0) { + YoutubePlayerController _controller = YoutubePlayerController( + initialVideoId: '${((widget.data['sections'] as List)[i]['youtube_videos'] as List)[0] as String}', + params: YoutubePlayerParams( + playlist: ((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 1 ? + ((widget.data['sections'] as List)[i]['youtube_videos'] as List).map((e) { + return '$e'; + }).toList() : [], // Defining custom playlist + startAt: Duration(seconds: 0), + showControls: true, + showFullscreenButton: false, + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only( + top: 10.0, left: 8.0, right: 8.0, bottom: 8.0, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: YoutubePlayerIFrame( + controller: _controller, + aspectRatio: 2 / 1, + ), + ), + ), + ); + } + + col.children.add(Container( + margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0), + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + '${(widget.data['sections'] as List)[i]['description']}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + )); + widgets.add(col); + } + return widgets; + } +} diff --git a/lib/widgets/mobile/mobile_index_main_content_1.dart b/lib/widgets/mobile/mobile_index_main_content_1.dart index 0f11fb7..375d927 100644 --- a/lib/widgets/mobile/mobile_index_main_content_1.dart +++ b/lib/widgets/mobile/mobile_index_main_content_1.dart @@ -31,7 +31,7 @@ class MobileIndexMainContent1State extends State { ), ), decoration: BoxDecoration( - color: Colors.lightGreen, + color: Color(0xFF4FB0C6), borderRadius: BorderRadius.circular(10), ), ); diff --git a/lib/widgets/mobile/mobile_index_main_content_2.dart b/lib/widgets/mobile/mobile_index_main_content_2.dart index 53041bb..57f3e3c 100644 --- a/lib/widgets/mobile/mobile_index_main_content_2.dart +++ b/lib/widgets/mobile/mobile_index_main_content_2.dart @@ -58,7 +58,7 @@ class MobileIndexMainContent2State extends State { Container( padding: EdgeInsets.symmetric(horizontal: 10.0), child: Util.showImage( - 'http:${widget.content['minipos_image']['image']}', + 'https:${widget.content['minipos_image']['image']}', fit: BoxFit.fitWidth ), ), @@ -103,6 +103,7 @@ class MobileIndexMainContent2State extends State { child: TextLink( S.of(context).learn_more, '/minipos-learn-more', + fontWeight: FontWeight.bold, ), ), ); diff --git a/lib/widgets/mobile/mobile_index_main_content_3.dart b/lib/widgets/mobile/mobile_index_main_content_3.dart index 291feea..2283512 100644 --- a/lib/widgets/mobile/mobile_index_main_content_3.dart +++ b/lib/widgets/mobile/mobile_index_main_content_3.dart @@ -58,7 +58,7 @@ class MobileIndexMainContent3State extends State { Container( padding: EdgeInsets.symmetric(horizontal: 10.0), child: Util.showImage( - 'http:${widget.content['igoshow_image']['image']}', + 'https:${widget.content['igoshow_image']['image']}', fit: BoxFit.fitWidth ), ), @@ -106,6 +106,7 @@ class MobileIndexMainContent3State extends State { child: TextLink( S.of(context).learn_more, '/igoshow-learn-more', + fontWeight: FontWeight.bold, ), ), ); diff --git a/lib/widgets/mobile/mobile_login.dart b/lib/widgets/mobile/mobile_login.dart new file mode 100644 index 0000000..1cd5318 --- /dev/null +++ b/lib/widgets/mobile/mobile_login.dart @@ -0,0 +1,275 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import '../../models/user.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; + +class MobileLogin extends StatefulWidget { + final Key key; + + const MobileLogin({this.key}) : super(key: key); + + @override + State createState() { + return MobileLoginState(); + } +} + +class MobileLoginState extends State { + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + final passwordController = TextEditingController(); + bool passwordVisible; + + bool onSubmitting; + + @override + void initState() { + super.initState(); + passwordVisible = true; + onSubmitting = false; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (onSubmitting) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + Widget view = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Text( + S.of(context).please_login, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0), + child: Text( + S.of(context).login_instruction, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + Form( + key: _formKey, + child: Column( + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 0.0), + child: TextFormField( + controller: usernameController, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).mobile_email_username, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 5.0), + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + obscureText: passwordVisible, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlatButton( + child: Text( + S.of(context).new_user_question, + style: TextStyle( + fontSize: 14.0, + ), + ), + onPressed: () { + newUser(); + }, + ), + Expanded( + child: Text(''), + ), + Container( + child: FlatButton( + child: Text( + S.of(context).forgot_password_question, + style: TextStyle( + fontSize: 14.0, + ), + ), + onPressed: () { + forgotPassword(); + }, + ), + ), + ], + ), + ), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).login, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + signIn(); + }, + ), + ), + ), + ], + ); + return SingleChildScrollView( + child: view, + ); + } + + void newUser() { + Routes.router.navigateTo(context, '/new-user'); + } + + void forgotPassword() { + Routes.router.navigateTo(context, '/forgot-password'); + } + + void signIn() { + final FormState form = _formKey.currentState; + if (form.validate()) { + if (mounted) { + setState(() { + onSubmitting = true; + }); + } + + HttpUtil.httpPost( + 'v1/oauth-wisetronic/access_token', (response) { + print('response $response'); + User user = User.fromJson(response.data['user']); + store.dispatch(UpdateCurrentUser(user)); + Utils.getBox().then((box) { + box.put(Constants.KEY_USER_ID, response.data['user_id']); + box.put(Constants.KEY_ACCESS_TOKEN, response.data['access_token']); + if (store.state.redirectRoute != null) { + Routes.router.navigateTo(context, store.state.redirectRoute, replace: true); + store.dispatch(UpdateRedirectRoute(null)); + } else { + Routes.router.navigateTo( + context, '/me', replace: true, clearStack: false); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + }, + queryParameters: { + 'client_id': '${Utils.getPlatformName()}', + 'grant_type': 'password' + }, + body: { + 'username': usernameController.text.trim(), + 'password': passwordController.text.trim(), + 'fcm_token': store.state.fcmToken != null ? store.state.fcmToken : '' + }, + isFormData: true, + businessId: Constants.BUSINESS_ID, + ).catchError((error) { + if (mounted) { + setState(() { + onSubmitting = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_me.dart b/lib/widgets/mobile/mobile_me.dart new file mode 100644 index 0000000..a8c8861 --- /dev/null +++ b/lib/widgets/mobile/mobile_me.dart @@ -0,0 +1,842 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../constants.dart'; +import '../../dialog/logout_dialog.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/shop_scroll_controller.dart'; +import '../../utils/shop_scroll_coordinator.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + +MediaQueryData mediaQuery; +double statusBarHeight; +double screenHeight; + +class MobileMe extends StatefulWidget { + final Key key; + + const MobileMe({this.key}) : super(key: key); + + @override + State createState() { + return MobileMeState(); + } +} + +class MobileMeState extends State { + int userId; + String accessToken; + + bool isLoading; + User _user; + + ShopScrollCoordinator _shopCoordinator; + ShopScrollController _pageScrollController; + final double _sliverAppBarInitHeight = 165.0; + final double _appBarHeight = 85.0; + + ShopScrollController _listScrollController1; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (isLoading) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + mediaQuery ??= MediaQuery.of(context); + screenHeight ??= mediaQuery.size.height; + statusBarHeight ??= mediaQuery.padding.top; + + _pageScrollController ??= + _shopCoordinator.pageScrollController(_sliverAppBarInitHeight * -1.0); + _shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () { + return statusBarHeight + kToolbarHeight + _appBarHeight; + }; + + _listScrollController1 = _shopCoordinator.newChildScrollController(); + + Widget userInfo = GestureDetector( + child: Container( + padding: + EdgeInsets.only(left: 16.0, right: 16.0, top: 5.0, bottom: 5.0), + margin: EdgeInsets.only(top: kToolbarHeight + 40.0), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 5.0), + child: _user != null && _user.avatarUrl.isNotEmpty + ? Util.showImage( + 'https:${_user.avatarUrl}', + width: 60, + height: 60, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon( + Icons.account_circle, + size: 60.0, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ) + : Icon( + Icons.person_outline, + size: 60.0, + color: Colors.white38, + ), + ), + Expanded( + child: Container( + height: 60.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + _user != null + ? _user.nickname + : S.of(context).please_login, + style: TextStyle( + color: Theme.of(context).scaffoldBackgroundColor, + fontSize: 28.0, + ), + ), + ), + Visibility( + visible: _user != null, + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.phone_iphone, + color: + Theme.of(context).scaffoldBackgroundColor, + size: 20.0, + ), + ), + Container( + child: Text( + _user != null + ? Utils.safePhoneNumber(_user.mobile) + : '', + style: TextStyle( + color: + Theme.of(context).scaffoldBackgroundColor, + fontSize: 16.0, + ), + ), + ) + ], + ), + ), + ), + ], + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + color: Theme.of(context).scaffoldBackgroundColor, + size: 20.0, + ), + color: Colors.transparent, + ), + ], + ), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo(context, '/user-profile'); + } else { + Routes.router.navigateTo(context, '/login'); + } + }, + ); + + Listener listener = Listener( + onPointerUp: _shopCoordinator.onPointerUp, + child: CustomScrollView( + controller: _pageScrollController, + physics: ClampingScrollPhysics(), + slivers: [ + SliverAppBar( + pinned: true, + floating: true, + snap: true, + title: Text(S.of(context).me), + centerTitle: true, + backgroundColor: Theme.of(context).primaryColor, + expandedHeight: _sliverAppBarInitHeight, + actions: [], + flexibleSpace: FlexibleSpaceBar( + background: userInfo, + collapseMode: CollapseMode.none, + ), + ), + SliverPersistentHeader( + pinned: true, + floating: false, + delegate: _SliverAppBarDelegate( + minHeight: _appBarHeight, + maxHeight: _appBarHeight, + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null + ? '${_user.wallet.toStringAsFixed(2)}' + : '0.00', + style: TextStyle( + fontSize: 24.0, + color: Colors.redAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).wallet, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + right: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null ? '${_user.coupon}' : '0', + style: TextStyle( + fontSize: 24.0, + color: Colors.orangeAccent, + ), + ), + ), + Container( + child: Text( + S.of(context).red_coupon, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + right: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + if (_user != null) { + Routes.router + .navigateTo(context, '/coupons/${_user.id}'); + } else { + _pleaseLoginToast(); + } + }, + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + _user != null ? '${_user.points}' : '0', + style: TextStyle( + fontSize: 24.0, + color: Colors.lightGreen, + ), + ), + ), + Container( + child: Text( + S.of(context).point, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 6.0, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + SliverFillRemaining( + child: ListView.builder( + controller: _listScrollController1, + itemCount: 7, + itemBuilder: (BuildContext context, int i) { + Widget item; + switch (i) { + case 0: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.settings_overscan, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).ocr_scan, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + if (kIsWeb) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).error), + content: Text( + S.of(context).feature_not_available_web, + ), + actions: [ + FlatButton( + onPressed: () { + Routes.router.pop(context); + }, + child: Text(S.of(context).ok)), + ], + ); + }); + } else { + Routes.router.navigateTo( + context, + '/ocr-scan', + ); + } + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 1: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.lock, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).change_password, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo( + context, + '/change-password', + ); + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 2: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.sticky_note_2_outlined, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).my_orders, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo(context, '/orders'); + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 3: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.location_on, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).my_addresses, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo(context, '/my-addresses/-1'); + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 4: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.credit_card, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).my_cards, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + Routes.router.navigateTo(context, '/my-cards'); + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 5: + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.headset_mic, + size: 20.0, + ), + ), + Container( + child: Text( + S.of(context).my_support, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Expanded( + child: SizedBox(), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 15.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + if (_user != null) { + if (_user.email == null || _user.email.isEmpty) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).email_needed), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + RaisedButton( + color: Theme.of(context).primaryColor, + textColor: Colors.white, + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + Routes.router.navigateTo(context, + '/change-mobile-email/2'); + }, + ), + ], + ); + }); + } else { + Routes.router.navigateTo(context, + '/my-support/${Constants.BUSINESS_ID}'); + } + } else { + _pleaseLoginToast(); + } + }, + ); + break; + case 6: + if (_user == null) { + item = SizedBox.shrink(); + } else { + item = GestureDetector( + child: Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).logout, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Colors.red, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 10.0), + child: Icon( + Icons.logout, + color: Colors.redAccent, + ), + ), + ], + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, width: 1.0))), + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return logoutDialog(context); + }); + }, + ); + } + break; + } + return item; + }, + ), + ), + ], + ), + ); + + return listener; + } + + @override + void initState() { + super.initState(); + setState(() { + isLoading = true; + _user = null; + }); + _shopCoordinator = ShopScrollCoordinator(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + isLoading = false; + _user = store.state.user; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + isLoading = false; + _user = null; + }); + } + }); + Utils.getCurrentUser(); + } + + void _toLogin() { + Routes.router + .navigateTo(context, '/login', replace: true, clearStack: true); + } + + _pleaseLoginToast() { + Fluttertoast.showToast( + msg: S.of(context).please_login, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white); + } +} + +class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { + _SliverAppBarDelegate({ + @required this.minHeight, + @required this.maxHeight, + @required this.child, + }); + + final double minHeight; + final double maxHeight; + final Widget child; + + @override + double get minExtent => this.minHeight; + + @override + double get maxExtent => max(maxHeight, minHeight); + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return SizedBox.expand(child: child); + } + + @override + bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { + return maxHeight != oldDelegate.maxHeight || + minHeight != oldDelegate.minHeight || + child != oldDelegate.child; + } +} diff --git a/lib/widgets/mobile/mobile_minipos_learn_more.dart b/lib/widgets/mobile/mobile_minipos_learn_more.dart new file mode 100644 index 0000000..337796c --- /dev/null +++ b/lib/widgets/mobile/mobile_minipos_learn_more.dart @@ -0,0 +1,92 @@ + +import 'package:flutter/material.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class MobileMiniPosLearnMore extends StatefulWidget { + final Map data; + const MobileMiniPosLearnMore(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return MobileMiniPosLearnMoreState(); + } + +} + +class MobileMiniPosLearnMoreState extends State { + + @override + Widget build(BuildContext context) { + Column col = Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 20.0), + child: Util.showImage( + 'https:${widget.data['image-top']['image']}', + fit: BoxFit.contain, + ), + ), + + ], + ); + List w = _getContent(); + for (int i = 0; i < w.length; i++) { + col.children.add(w[i]); + } + return SingleChildScrollView( + child: col, + ); + } + + @override + void initState() { + super.initState(); + } + + List _getContent() { + List widgets = []; + for (int i = 0; i < (widget.data['sections'] as List).length; i++) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + col.children.add(Container( + margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0), + child: Text( + '${(widget.data['sections'] as List)[i]['title']}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0, + color: Colors.black87, + ), + ), + )); + col.children.add( + Container( + padding: EdgeInsets.only(top: 10.0, left: 8.0, right: 8.0, bottom: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Util.showImage( + 'https:${(widget.data['sections'] as List)[i]['image']['image']}', + fit: BoxFit.contain, + ), + ), + ), + ); + col.children.add(Container( + margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0), + padding: EdgeInsets.only(bottom: 10.0), + child: Text( + '${(widget.data['sections'] as List)[i]['description']}', + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + )); + widgets.add(col); + } + return widgets; + } +} diff --git a/lib/widgets/mobile/mobile_my_addresses.dart b/lib/widgets/mobile/mobile_my_addresses.dart new file mode 100644 index 0000000..fc26f30 --- /dev/null +++ b/lib/widgets/mobile/mobile_my_addresses.dart @@ -0,0 +1,227 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/address.dart'; +import 'package:flutter_wisetronic/pages/edit_address.dart'; +import 'package:flutter_wisetronic/store/actions.dart'; +import 'package:flutter_wisetronic/store/store.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:flutter_wisetronic/widgets/mobile/MobileBottomNav.dart'; + +import '../../routes.dart'; + +class MobileMyAddresses extends StatefulWidget { + final int businessId; + const MobileMyAddresses({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return MobileMyAddressesState(); + } + +} + +class MobileMyAddressesState extends State { + List
addresses; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (addresses == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + BuildContext mainContext = context; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).my_addresses), + backgroundColor: Theme.of(context).primaryColor, + actions: [ + IconButton( + padding: EdgeInsets.only(right: 16.0), + icon: Icon( + Icons.add, + size: 32.0, + ), + onPressed: () { + Routes.router.navigateTo(context, '/search-place/${widget.businessId}'); + } + ), + ], + ), + body: ListView.builder( + itemCount: addresses.length <= 1 ? 1 : addresses.length, + itemBuilder: (BuildContext context, int i) { + if (addresses.length <= 0) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text(S.of(context).no_address_yet), + ), + ); + } else { + Address address = addresses[i]; + + return Container( + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + address.addressLine1, + style: TextStyle( + fontSize: 19.0, + ), + ), + ), + Container( + child: Text( + address.addressLine2, + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Text( + '${address.contactName} ${Utils.safePhoneNumber(address.phone)}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + ), + ) + ], + ), + ), + Container( + child: widget.businessId == 0 ? + IconButton( + icon: Icon( + Icons.edit, + color: Colors.grey, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => new EditAddress(address, businessId: 0,), + )); + }, + ) : + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 5.0), + child: IconButton( + icon: Icon( + Icons.edit, + color: Colors.grey, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => new EditAddress(address, businessId: widget.businessId,), + )); + }, + ), + ), + Container( + child: widget.businessId > 0 ? IconButton( + icon: Icon( + Icons.check, + color: Colors.grey, + ), + onPressed: () { + HttpUtil.httpPut('v1/select-address/${address.id}', (response) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + }).catchError((error) { + Utils.showMessageDialog(mainContext, error, onOk: () { + Navigator.of(mainContext).pop(); + Navigator.of(mainContext).pop(); + }); + }); + }, + ) : SizedBox.shrink(), + ) + ], + ), + ), + ], + ), + ); + } + } + ), + bottomNavigationBar: MobileBottomNav(currentIndex: 3,), + ); + } + + @override + void initState() { + super.initState(); + loadAddresses(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + addresses = null; + }); + } + loadAddresses(); + }); + } + + void loadAddresses() { + HttpUtil.httpGet( + 'v1/addresses', + ).then((value) { + if (mounted) { + setState(() { + addresses = (value as List).map((e) => Address.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_my_cards.dart b/lib/widgets/mobile/mobile_my_cards.dart new file mode 100644 index 0000000..77a8571 --- /dev/null +++ b/lib/widgets/mobile/mobile_my_cards.dart @@ -0,0 +1,182 @@ + + +import 'package:flutter/material.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../models/user.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileMyCards extends StatefulWidget { + final Key key; + const MobileMyCards({this.key}); + + @override + State createState() { + return MyCardsState(); + } + +} + +class MyCardsState extends State { + User _user; + + bool isSubmitting; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).my_cards), + backgroundColor: Theme.of(context).primaryColor, + ), + body: ListView.builder( + itemCount: _user.stripePaymentMethods.length, + itemBuilder: (BuildContext context, int position) { + StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position]; + return cardWidget(paymentMethod); + } + ), + ); + } + + Widget cardWidget(StripePaymentMethod paymentMethod) { + return Container( + padding: EdgeInsets.only( + top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + paymentMethod.cardBrand == 'visa' ? + Image.asset( + 'assets/images/visa.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : (paymentMethod.cardBrand == 'mastercard' ? + Image.asset( + 'assets/images/master.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : Icon( + Icons.credit_card, size: 50.0, color: Colors.black38,)), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + '**** ${paymentMethod.cardLast4}', + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).expire_token(paymentMethod.cardExpMonth, paymentMethod.cardExpYear), + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + IconButton( + icon: Icon(Icons.clear, color: Colors.black26,), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text( + S.of(context).are_you_sure_to_remove_the_card, + ), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _removeCard(paymentMethod); + }, + ) + ], + ); + } + ); + }, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ); + } + + _removeCard(StripePaymentMethod paymentMethod) { + HttpUtil.httpDelete('v1/stripe-card/${paymentMethod.id}', (response) { + _user = User.fromJson(response.data); + store.dispatch(UpdateCurrentUser(_user)); + eventBus.fire(OnCurrentUserUpdated()); + if (mounted) { + Navigator.of(context).pop(); + setState(() { + isSubmitting = false; + }); + } + }).catchError((error) { + if (isSubmitting) { + Navigator.of(context).pop(); + isSubmitting = false; + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + @override + void initState() { + super.initState(); + setState(() { + isSubmitting = false; + _user = store.state.user; + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_my_support.dart b/lib/widgets/mobile/mobile_my_support.dart new file mode 100644 index 0000000..e864c8d --- /dev/null +++ b/lib/widgets/mobile/mobile_my_support.dart @@ -0,0 +1,295 @@ + + +import 'package:flutter/material.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/ticket.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/double_back_to_close_app_wrapper.dart'; +import '../../widgets/mobile/MobileBottomNav.dart'; + +class MobileMySupport extends StatefulWidget { + final int businessId; + const MobileMySupport({Key key, this.businessId}) : super(key: key); + + @override + State createState() { + return MobileMySupportState(); + } + +} + +class MobileMySupportState extends State { + List tickets; + + int _page = 1; + int _pageCount = 1; + + bool _isLoading = false; + bool _loadingFinish = false; + + RefreshController _refreshController = RefreshController(initialRefresh: true); + + void _onRefresh() { + _page = 1; + if (tickets != null) { + tickets.clear(); + } else { + tickets = []; + } + _refreshController.resetNoData(); + loadTicketes(true); + } + + void _onLoadMore() { + // if failed,use loadFailed(),if no data return,use LoadNodata() + if (_pageCount > _page) { + _page += 1; + loadTicketes(false); + } else { + _refreshController.loadNoData(); + } + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/my-support/${widget.businessId}'); + return; + } + }); + + BuildContext mainContext = context; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).my_support), + backgroundColor: Theme.of(context).primaryColor, + actions: [ + IconButton( + padding: EdgeInsets.only(right: 16.0), + icon: Icon( + Icons.add, + size: 32.0, + ), + onPressed: () { + Routes.router.navigateTo(context, '/new-ticket/${widget.businessId}'); + } + ), + ], + ), + body: DoubleBackToCloseAppWrapper( + child: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode){ + Widget footer; + if(mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center(child: footer,), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoadMore, + child: _buildBody(), + ), + ), + bottomNavigationBar: MobileBottomNav(currentIndex: 2,), + ); + } + + @override + void dispose() { + _refreshController?.dispose(); + super.dispose(); + } + + Widget _buildBody() { + if (tickets == null) { + return SizedBox.shrink(); + } + return ListView.builder( + itemCount: tickets.length <= 1 ? 1 : tickets.length, + itemBuilder: (BuildContext context, int i) { + if (tickets.length <= 0) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text(S.of(context).no_ticket_yet), + ), + ); + } else { + Ticket ticket = tickets[i]; + + return GestureDetector( + onTap: () { + Routes.router.navigateTo(context, '/view-ticket/${ticket.id}'); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 0.6, + ) + ) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + ticket.issue.msg, + style: TextStyle( + fontSize: 19.0, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt, withTime: true), + style: TextStyle( + fontSize: 13.0, + color: Colors.grey, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ticket.isClosed ? + Container( + padding: EdgeInsets.only(right: 10.0), + child: Icon(Icons.lock, color: Colors.green, size: 16.0,), + ) : + SizedBox.shrink(), + Text( + S.of(context).followups_token(ticket.followUps.length), + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + ), + ], + ), + ) + ], + ), + ), + ], + ), + ), + ); + } + } + ); + } + + @override + void initState() { + super.initState(); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + tickets = null; + }); + _refreshController.requestRefresh(); + } + }); + } + + void loadTicketes(bool isRefresh) { + _loadingFinish = false; + HttpUtil.httpGet( + 'v1/mysupport', + businessId: widget.businessId, + queryParameters: { + 'page': _page.toString(), + 'size': Constants.TICKET_PER_PAGE_MOBILE.toString() + } + ).then((value) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshCompleted(); + } else { + _refreshController.loadComplete(); + } + + if (int.parse(value['_meta']['currentPage'].toString()) >= int.parse(value['_meta']['pageCount'].toString())) { + _loadingFinish = true; + } + if (_loadingFinish) { + _refreshController.loadNoData(); + } + _page = int.parse(value['_meta']['currentPage'].toString()); + _pageCount = int.parse(value['_meta']['pageCount'].toString()); + + setState(() { + if (tickets == null) { + tickets = []; + } + tickets.addAll((value['tickets'] as List).map((e) => Ticket.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if (mounted) { + if (isRefresh) { + _refreshController.refreshFailed(); + } else { + _refreshController.loadFailed(); + } + _isLoading = false; + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } + }); + } + +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_navigation_drawer.dart b/lib/widgets/mobile/mobile_navigation_drawer.dart index 6e4c8a0..d8a33fd 100644 --- a/lib/widgets/mobile/mobile_navigation_drawer.dart +++ b/lib/widgets/mobile/mobile_navigation_drawer.dart @@ -1,8 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/generated/l10n.dart'; -import 'package:flutter_wisetronic/widgets/general/text_link.dart'; -import 'package:flutter_wisetronic/widgets/mobile/mobile_navigation_drawer_header.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/text_link.dart'; +import '../../widgets/mobile/mobile_navigation_drawer_header.dart'; import '../../constants.dart'; @@ -17,6 +23,8 @@ class MobileNavigationDrawer extends StatefulWidget { } class MobileNavigationDrawerState extends State { + User _user; + @override Widget build(BuildContext context) { String currentRoute = ModalRoute.of(context).settings.name; @@ -53,18 +61,19 @@ class MobileNavigationDrawerState extends State { ), TextLink( S.of(context).tutorials, - '/tutorials', + Constants.TUTORIAL_URL, paddingVertical: 10.0, paddingHorizontal: 15.0, selected: currentRoute == '/tutorials', closeDrawer: true, + isLink: true, ), TextLink( S.of(context).support, - '/support', + '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 10.0, paddingHorizontal: 15.0, - selected: currentRoute == '/support', + selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}', closeDrawer: true, ), TextLink( @@ -77,12 +86,41 @@ class MobileNavigationDrawerState extends State { ), TextLink( S.of(context).blog, - '/blog', + '/blog/${Constants.BUSINESS_ID}', paddingVertical: 10.0, paddingHorizontal: 15.0, - selected: currentRoute == '/blog', + selected: currentRoute == '/blog/${Constants.BUSINESS_ID}', closeDrawer: true, ), + _user != null ? + (currentRoute == '/me') ? + GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).logout, + ), + ), + Icon( + Icons.logout, + ) + ], + ), + onTap: () { + + }, + ) : IconButton( + icon: Icon( + Icons.account_circle, + color: Colors.lightBlueAccent, + ), + onPressed: () { + Routes.router.navigateTo(context, '/me'); + }, + ) : TextLink( S.of(context).login, '/login', @@ -109,28 +147,28 @@ class MobileNavigationDrawerState extends State { ), TextLink( S.of(context).service_policy, - '/service_policy', + '/service-policy', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true, ), TextLink( S.of(context).return_policy, - '/return_policy', + '/return-policy', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true, ), TextLink( S.of(context).privacy_policy, - '/privacy_policy', + '/privacy-policy', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true, ), TextLink( S.of(context).license_agreement, - '/license_agreement', + '/end-user-license-agreement', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true, @@ -147,10 +185,10 @@ class MobileNavigationDrawerState extends State { ), ), TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), - TextLink(S.of(context).support_ticket, '/support_ticket', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), - TextLink(S.of(context).contact_us, '/contact_us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), - TextLink(S.of(context).about_us, '/about_us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), - TextLink(S.of(context).renew_license, '/renew_license', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), + TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), + TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), + TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), + TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,), Container( margin: EdgeInsets.only(top: 20.0, bottom: 10.0), child: Text( @@ -238,10 +276,66 @@ class MobileNavigationDrawerState extends State { ), ], ), + Container( + padding: EdgeInsets.only(left: 6.0, right: 6.0, top: 12.0, bottom: 18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + child: Text( + '© 2007-${DateTime.now().year} wisetronic.com. All Rights Reserved.', + style: TextStyle( + fontSize: 10.0, + color: Colors.black38, + ), + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(top: 6.0), + child: Text( + 'All logos shown are registered trademark, copyrighted and belong to their respective owners.', + style: TextStyle( + fontSize: 8.0, + color: Colors.black38, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), ], ), ), ); } + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = null; + }); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + } } \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_navigationbar.dart b/lib/widgets/mobile/mobile_navigationbar.dart index 4b6e02e..e49a51d 100644 --- a/lib/widgets/mobile/mobile_navigationbar.dart +++ b/lib/widgets/mobile/mobile_navigationbar.dart @@ -1,15 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:flutter_wisetronic/events/eventbus.dart'; -import 'package:flutter_wisetronic/events/events.dart'; -import 'package:flutter_wisetronic/widgets/general/navigationbar_logo.dart'; +import '../../models/user.dart'; +import '../../utils/utils.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../store/store.dart'; +import '../../widgets/general/navigationbar_logo.dart'; import '../../routes.dart'; class MobileNavigationBar extends StatefulWidget { final String title; final bool back; - const MobileNavigationBar({Key key, this.title, this.back}) : super(key: key); + final bool toHome; + final bool showMe; + const MobileNavigationBar({Key key, this.title, this.back, this.toHome, this.showMe}) : super(key: key); @override State createState() { @@ -19,6 +24,7 @@ class MobileNavigationBar extends StatefulWidget { } class MobileNavigationBarState extends State { + User _user; @override Widget build(BuildContext context) { @@ -30,7 +36,11 @@ class MobileNavigationBarState extends State { color: Colors.white, ), onPressed: () { - Routes.router.pop(context); + if (widget.toHome) { + Routes.router.navigateTo(context, '/', clearStack: true); + } else { + Routes.router.pop(context); + } }, ) : IconButton( @@ -58,7 +68,53 @@ class MobileNavigationBarState extends State { padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), ), centerTitle: true, + actions: actions(), ); } + List actions() { + List ws = []; + // if (store.state.user != null && widget.showMe) { + // if (ModalRoute.of(context).settings.name != '/me') { + // ws.add(IconButton( + // icon: Icon( + // Icons.account_circle, + // color: Colors.white, + // ), + // onPressed: () { + // Routes.router.navigateTo(context, '/me'); + // } + // )); + // } + // } + return ws; + } + + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = null; + }); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + } } \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_new_address.dart b/lib/widgets/mobile/mobile_new_address.dart new file mode 100644 index 0000000..efc8d8c --- /dev/null +++ b/lib/widgets/mobile/mobile_new_address.dart @@ -0,0 +1,395 @@ + +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import 'package:gender_selection/gender_selection.dart'; + +import '../../generated/l10n.dart'; +import '../../models/located_address.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; + +class MobileNewAddress extends StatefulWidget { + final Key key; + final LocatedAddress locatedAddress; + final int businessId; + const MobileNewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key); + + @override + State createState() { + return MobileNewAddressState(); + } + +} + +class MobileNewAddressState extends State { + final GlobalKey _formKey = new GlobalKey(); + + final contactNameController = TextEditingController(); + final phoneController = TextEditingController(); + final streetLine1Controller = TextEditingController(); + final streetLine2Controller = TextEditingController(); + final cityController = TextEditingController(); + final postalCodeController = TextEditingController(); + final emailController = TextEditingController(); + final faxController = TextEditingController(); + + String country = 'CA'; + Gender _selectedGender; + + String _selectedProvince; + + List provinces = [ + 'Ontario', + 'Quebec', + 'British Columbia', + 'Alberta', + 'Manitoba', + 'Saskatchewan', + 'Nova Scotia', + 'New Brunswich', + 'Newfoundland and Labrador', + 'Prince Edward Island', + 'Northwest Territories', + 'Nunavut', + 'Yukon', + ]; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).new_address), + backgroundColor: Theme.of(context).primaryColor, + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: contactNameController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).contact_name, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).contact_name_is_required; + } + return null; + }, + ), + ), + GenderSelection( + maleText: S.of(context).mr, + femaleText: S.of(context).ms, + selectedGenderIconBackgroundColor: Colors.indigo, + checkIconAlignment: Alignment.bottomRight, + selectedGenderCheckIcon: Icons.check, + onChanged: (Gender gender) { + _selectedGender = gender; + }, + equallyAligned: true, + animationDuration: Duration(milliseconds: 400), + isCircular: true, + isSelectedGenderIconCircular: true, + opacityOfGradient: 0.6, + padding: const EdgeInsets.all(3.0), + size: 50, + selectedGender: _selectedGender, + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + keyboardType: TextInputType.phone, + controller: phoneController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).mobile_phone_number, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_phone_number_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine1Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).street_line_1, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).street_line_1_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: streetLine2Controller, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).street_line_2, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: cityController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).city, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).city_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: DropdownButton( + value: _selectedProvince, + items: provinces.map((value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + if (mounted) { + setState(() { + _selectedProvince = newValue; + }); + } + }, + hint: Text(S.of(context).province), + isExpanded: true, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0), + child: TextFormField( + controller: postalCodeController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).postal_code, + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).postal_code_is_required; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 32.0, bottom: 0.0), + child: Text( + S.of(context).optional_information, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 0.0), + child: TextFormField( + controller: emailController, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).email, + ), + validator: (String value) { + if (value.isNotEmpty && !EmailValidator.validate(value)) { + return S.of(context).email_is_not_valid; + } + return null; + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: TextFormField( + controller: faxController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ) + ), + labelText: S.of(context).fax, + ), + ), + ), + ], + ), + ), + ), + bottomNavigationBar: Container( + padding: EdgeInsets.all(8.0), + color: Theme.of(context).buttonColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + FlatButton( + child: Text( + S.of(context).save + ), + onPressed: () { + _saveNewAddress(); + }, + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + setState(() { + if (widget.locatedAddress != null) { + if (provinces.contains(widget.locatedAddress.province)) { + _selectedProvince = widget.locatedAddress.province; + } + cityController.text = widget.locatedAddress.city; + postalCodeController.text = widget.locatedAddress.postalCode; + streetLine1Controller.text = (widget.locatedAddress.streetNumber != null + && widget.locatedAddress.streetNumber.isNotEmpty + ? widget.locatedAddress.streetNumber + ' ' : '') + + widget.locatedAddress.streetName; + } else { + _selectedProvince = 'Ontario'; + } + streetLine2Controller.text = ''; + _selectedGender = Gender.Male; + }); + } + + void _saveNewAddress() { + final FormState form = _formKey.currentState; + if (form.validate()) { + + HttpUtil.httpPost('v1/addresses', (response) { + if (widget.businessId > 0) { + Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true); + } else { + Routes.router.navigateTo(context, '/my-addresses/${widget.businessId}', replace: true); + } + }, + body: { + 'name': contactNameController.text.trim(), + 'address_line1': streetLine1Controller.text.trim(), + 'address_line2': streetLine2Controller.text.trim(), + 'city': cityController.text.trim(), + 'state': _selectedProvince, + 'zip': postalCodeController.text.trim(), + 'phone': phoneController.text.trim(), + 'gender': _selectedGender == Gender.Male ? 1 : 0, + 'email': emailController.text, + 'fax': faxController.text, + 'country': country, + } + ); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_new_comment.dart b/lib/widgets/mobile/mobile_new_comment.dart new file mode 100644 index 0000000..8d58445 --- /dev/null +++ b/lib/widgets/mobile/mobile_new_comment.dart @@ -0,0 +1,372 @@ + + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:smooth_star_rating/smooth_star_rating.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/comment.dart'; +import '../../models/product_image.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + + +class MobileNewComment extends StatefulWidget { + final Key key; + final int orderId; + const MobileNewComment(this.orderId, {this.key}); + + @override + State createState() { + return MobileNewCommentState(); + } + +} + +class MobileNewCommentState extends State { + Comment comment; + + bool _showProgress; + + double _progress; + + double rating; + + bool isSubmitting = false; + + final TextEditingController commentController = new TextEditingController(); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).comment), + backgroundColor: Theme.of(context).primaryColor, + actions: [ + IconButton( + icon: Icon(Icons.check), + onPressed: () { + _submit(); + }, + ), + ], + ), + body: _buildBody(context), + ); + } + + @override + void initState() { + super.initState(); + _showProgress = false; + _progress = 0.0; + setState(() { + rating = 5.0; + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _showProgress = event.showProgress; + _progress = event.progress; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + comment = event.comment; + }); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + Widget _buildBody(BuildContext context) { + + return ListView.builder( + itemCount: 3, + itemBuilder: (BuildContext context, int position) { + + switch(position) { + case 0: + return Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 16.0, bottom: 16.0), + child: SmoothStarRating( + allowHalfRating: false, + onRated: (v) { + setState(() { + rating = v; + }); + }, + starCount: 5, + rating: rating, + size: 40.0, + filledIconData: Icons.star, + color: Colors.green, + borderColor: Colors.green, + spacing: 0.0, + ), + ); + break; + case 1: + return Container( + padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 16.0, right: 16.0), + child: TextField( + controller: commentController, + keyboardType: TextInputType.multiline, + maxLines: 10, + maxLength: 500, + decoration: new InputDecoration( + border: new OutlineInputBorder( + borderRadius: const BorderRadius.all( + const Radius.circular(12.0), + ), + ), + filled: true, + hintStyle: new TextStyle(color: Colors.grey[800]), + hintText: S.of(context).input_your_comment, + fillColor: Colors.white70, + ), + ), + ); + break; + case 2: + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: commentImages(context), + ); + break; + } + return null; + } + ); + } + + Widget commentImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + + if (comment != null && comment.images.length > 0) { + for (ProductImage image in comment.images) { + row.children.add( + Container( + padding: EdgeInsets.only(left: 10.0), + child: Badge( + position: BadgePosition.topEnd(top: -10.0, end: -6.0), + badgeContent: Container( + child: Text( + '-', + style: TextStyle( + fontSize: 14.0, + color: Colors.white, + ), + ), + ), + child: GestureDetector( + child: Container( + child: Util.showImage('https:${image.image}', + width: 60.0, + height: 60.0, + fit: BoxFit.fill, + errorWidget: (mainContext, url, error) => Icon( + Icons.image, + size: 60.0, + color: Colors.grey, + ), + ), + ), + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + color: Theme.of(context).primaryColor, + ), + FlatButton( + child: Text(S.of(context).yes_i_am_sure), + onPressed: () { + Navigator.of(context).pop(); + _deleteImage(image.id); + }, + ), + ], + ); + } + ); + }, + ), + ), + ), + ); + } + } + + row.children.add( + GestureDetector( + child: Container( + margin: EdgeInsets.only(left: 10,), + child: Icon( + Icons.add, + size: 60.0, + color: comment == null || comment.images.length < 3 ? Colors.lightBlue : Colors.black12, + ), + decoration: BoxDecoration( + color: Colors.white70, + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + if (comment == null || comment.images.length < 3) { + showDialog( + context: mainContext, + barrierDismissible: true, + builder: (BuildContext context) { + return Util().getPicture(mainContext, store.state.user, + commentId: comment != null ? comment.id : 0, + orderId: widget.orderId); + } + ); + } + }, + ), + ); + + return Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: -100.0, + child: Visibility( + visible: _showProgress, + child: LinearPercentIndicator( + lineHeight: 10.0, + percent: _progress, + backgroundColor: Colors.transparent, + progressColor: Colors.blue, + linearStrokeCap: LinearStrokeCap.butt, + ), + ), + ), + Positioned( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 16.0), + child: Text( + S.of(context).add_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black38, + ), + ), + ), + row, + ], + ), + ), + ], + ); + } + + void _deleteImage(int imageId) { + HttpUtil.httpDelete('v1/comment-image/${imageId}', (response) { + if (mounted) { + setState(() { + comment = Comment.fromJson(response.data); + }); + } + }, + queryParameters: { + 'comment_id': comment != null ? comment.id : 0, + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + + void _submit() { + if (commentController.text.isEmpty) { + Utils.showMessageDialog(context, Exception(S.of(context).comment_empty)); + } else { + HttpUtil.httpPost('v1/add-comment', (response) { + if (isSubmitting) { + isSubmitting = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, + Exception(S.of(context).thank_you_for_your_comment), + title: S.of(context).success, + onOk: () { + Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true); + } + ); + }, + isFormData: true, + body: { + 'order_id': widget.orderId, + 'comment_id': comment != null ? comment.id : 0, + 'content': commentController.text, + 'rating': rating.round(), + }, + ).catchError((error) { + if (isSubmitting) { + isSubmitting = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + }); + isSubmitting = true; + Utils.showSubmitDialog(context); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_new_ticket.dart b/lib/widgets/mobile/mobile_new_ticket.dart new file mode 100644 index 0000000..eac2d5d --- /dev/null +++ b/lib/widgets/mobile/mobile_new_ticket.dart @@ -0,0 +1,400 @@ + +import 'package:badges/badges.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:percent_indicator/circular_percent_indicator.dart'; +import '../../models/user.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class MobileNewTicket extends StatefulWidget { + final Key key; + final int businessId; + + const MobileNewTicket({this.key, int businessId}) : + businessId = businessId ?? Constants.BUSINESS_ID; + + @override + State createState() { + return MobileNewTicketState(); + } +} + +class MobileNewTicketState extends State { + final GlobalKey _formKey = GlobalKey(); + + final issueMsgController = TextEditingController(); + + bool onSubmitting = false; + double submitProgress = 0.0; + + final List> galleryImagesFlag = [ + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + ]; + + @override + void initState() { + super.initState(); + onSubmitting = false; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + BuildContext mainContext = context; + + Widget view = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Text( + S.of(context).add_new_ticket, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0), + child: Text( + S.of(context).add_new_ticket_desc, + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + ), + Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: TextFormField( + controller: issueMsgController, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + decoration: new InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1.0, + color: Colors.blue, + ), + ), + labelText: S.of(context).your_question_issue, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + maxLines: 5, + maxLength: 1000, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0), + child: Text( + S.of(context).attach_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).attach_pictures_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0), + child: galleryImages(mainContext), + ), + ], + ), + ), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ), + ], + ); + return SingleChildScrollView( + child: Stack( + children: [ + Visibility( + visible: onSubmitting, + child: Container( + height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.bottom - MediaQuery.of(context).padding.top, + color: Colors.grey.withOpacity(0.6), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 10, + backgroundColor: Colors.green, + valueColor: new AlwaysStoppedAnimation(Colors.red), + value: submitProgress, + ), + Container( + margin: EdgeInsets.only(top: 16.0), + child: Text( + S.of(context).submitting_please_wait, + style: TextStyle( + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ), + Visibility( + visible: !onSubmitting, + child: view, + ), + ], + ), + ); + } + + void _submit() { + final FormState form = _formKey.currentState; + if (form.validate()) { + setState(() { + onSubmitting = true; + }); + Util().createTicket(context, issueMsgController.text.trim(), + galleryImagesFlag, (response){ + showDialog(context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).ticket_created_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/my-support/${widget.businessId}', replace: true); + }, + ), + ], + ); + }); + }, (error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }, id: null); + } + } + + Widget galleryImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < 3; i++) { + Map image = galleryImagesFlag[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + '${image['path']}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Visibility( + visible: galleryImagesFlag[i]['show'], + child: Container( + padding: EdgeInsets.only(right: 20.0), + child: CircularPercentIndicator( + radius: 60.0, + lineWidth: 10.0, + percent: galleryImagesFlag[i]['progress'], + center: new Text( + '${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15.0, + color: Colors.white, + ), + ), + circularStrokeCap: CircularStrokeCap.round, + progressColor: Colors.orange, + ), + ), + ), + ], + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + return Util().getPicture2(mainContext, i, (int imageId, String path){ + setState(() { + galleryImagesFlag[imageId]['path'] = path; + }); + }); + return null; + } + ); + }, + ); + + row.children.add(Badge( + position: BadgePosition.topEnd(top: -10.0, end: 10.0), + badgeContent: Container( + child: GestureDetector( + child: Icon( + Icons.clear, + color: Colors.white, + size: 14.0, + ), + onTap: () { + _deleteImage(mainContext, i); + }, + ), + ), + showBadge: image['path'].isNotEmpty ? true : false, + child: imageWidget, + )); + } + return row; + } + + void _deleteImage(BuildContext mainContext, int imageId) { + showDialog(context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + RaisedButton( + child: Text(S.of(context).yes_i_am_sure), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + setState(() { + galleryImagesFlag[imageId]['path'] = ''; + }); + }, + ), + ], + ); + }, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + onSubmitting = event.showProgress; + submitProgress = event.progress; + }); + } + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_new_user.dart b/lib/widgets/mobile/mobile_new_user.dart new file mode 100644 index 0000000..3d5e987 --- /dev/null +++ b/lib/widgets/mobile/mobile_new_user.dart @@ -0,0 +1,319 @@ + +import 'package:countdown/countdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileNewUser extends StatefulWidget { + const MobileNewUser({Key key}) : super(key: key); + + @override + State createState() { + return MobileNewUserState(); + } + +} + +class MobileNewUserState extends State { + + final GlobalKey _formKey = GlobalKey(); + + final usernameController = TextEditingController(); + bool usernameEnable = true; + final codeController = TextEditingController(); + + bool enableGetCode; + String getCodeText; + bool canRegister; + + var countDownListener; + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).user_registration, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: Text( + S.of(context).user_registration_desc, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: TextFormField( + controller: usernameController, + enabled: usernameEnable, + keyboardType: TextInputType.emailAddress, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).mobile_or_email, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).mobile_or_email_is_required; + } + return null; + }, + onChanged: (string) { + if (string.isEmpty) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } + if (string.isNotEmpty && !enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = true; + }); + } + } else if (string.isEmpty && enableGetCode) { + if (mounted) { + setState(() { + enableGetCode = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: codeController, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).verification_code, + suffixIcon: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 6.0), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + border: new Border.all( + color: enableGetCode ? Colors.black87 : Colors.black26, + width: 1.0, + ), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + getCodeText, + style: TextStyle( + color: enableGetCode ? Colors.black87 : Colors.black26, + fontSize: 14.0 + ), + ), + ), + ), + onTap: enableGetCode ? getCodeTapped : null, + ), + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).verification_code_is_required; + } + return null; + }, + onChanged: (string) { + if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) { + if (mounted) { + setState(() { + canRegister = true; + }); + } + } else { + if (mounted) { + setState(() { + canRegister = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 0.0, right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).register, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canRegister ? register : null, + ), + ), + ), + ], + ); + } + + @override + void initState() { + super.initState(); + setState(() { + enableGetCode = false; + canRegister = false; + usernameEnable = true; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + getCodeText = S.of(context).get_code; + } + + void register() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + Routes.router.navigateTo(context, '/set-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true); + }, + queryParameters: { + 'action': 'verify_code' + }, + isFormData: true, + body: { + 'mobile': usernameController.text.trim(), + 'code': codeController.text.trim(), + 'store_id': Constants.BUSINESS_ID + }, + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } + + void getCodeTapped() { + if (usernameController.text.isNotEmpty) { + HttpUtil.httpPost('v1/users', (response) { + Fluttertoast.showToast( + msg: S.of(context).verification_code_sent, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + startCountDown(); + if (mounted) { + setState(() { + usernameEnable = false; + }); + } + }, + queryParameters: {'action': 'send_code'}, + body: { + 'mobile': usernameController.text, + }, + isFormData: true, + ).catchError((error) { + if (mounted) { + setState(() { + canRegister = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } else { + Fluttertoast.showToast( + msg: S.of(context).enter_mobile_or_email, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.red, + textColor: Colors.white + ); + } + } + + void startCountDown() { + countDownListener.onData((Duration d) { + if (mounted) { + setState(() { + enableGetCode = false; + getCodeText = S.of(context).get_code_token(d.inSeconds); + }); + } + }); + + countDownListener.onDone(() { + if (mounted) { + setState(() { + enableGetCode = true; + getCodeText = S.of(context).get_code_again; + }); + } + countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_order_detail.dart b/lib/widgets/mobile/mobile_order_detail.dart new file mode 100644 index 0000000..910c153 --- /dev/null +++ b/lib/widgets/mobile/mobile_order_detail.dart @@ -0,0 +1,1187 @@ + +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/cart_line_item.dart'; +import 'package:flutter_wisetronic/models/extra_fee.dart'; +import 'package:flutter_wisetronic/models/fulfillment.dart'; +import 'package:flutter_wisetronic/models/order.dart'; +import 'package:flutter_wisetronic/store/actions.dart'; +import 'package:flutter_wisetronic/store/store.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +import '../../constants.dart'; +import '../../routes.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class MobileOrderDetail extends StatefulWidget { + final Key key; + final int orderId; + final bool fromOrders; + const MobileOrderDetail(this.orderId, {this.key, this.fromOrders}); + + @override + State createState() { + return MobileOrderDetailState(); + } + +} + +class MobileOrderDetailState extends State { + Order order; + + LatLng _lastMapPosition; + LatLng customerLatLng; + LatLng deliveryLatLng; + LatLng storeLatLng; + final Set _markers = {}; + final Set _polyLine = {}; + + BitmapDescriptor homeIcon; + BitmapDescriptor deliveryIcon; + BitmapDescriptor shopIcon; + + Completer _controller = Completer(); + + void _onMapCreated(GoogleMapController controller) { + _controller.complete(controller); + } + + void _onCameraMove(CameraPosition position) { + _lastMapPosition = position.target; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (order == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + if (widget.fromOrders != null && widget.fromOrders) { + Navigator.of(context).pop(); + } else { + Routes.router.navigateTo( + context, '/orders', replace: true, clearStack: true); + } + }, + ), + title: Text(S.of(context).order_detail), + backgroundColor: Theme.of(context).primaryColor, + ), + body: WillPopScope( + onWillPop: () async { + if (widget.fromOrders != null && widget.fromOrders) { + return true; + } else { + Routes.router.navigateTo( + context, '/orders', replace: true, clearStack: true); + } + return false; + }, + child: ListView.builder( + itemCount: 4, + itemBuilder: (BuildContext context, int position) { + + switch (position) { + case 0: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: GestureDetector( + child: Container( + width: double.infinity, + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + child: Text( + order.cartInfo.businessInfo.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/shop/${order.businessId}/na/na/na'); + }, + ), + ), + Container( + padding: EdgeInsets.only(left: 5.0), + margin: EdgeInsets.only(left: 5.0), + child: GestureDetector( + child: Icon( + Icons.phone, + ), + onTap: () { + Utils.launchURL('tel:${order.businessInfo.phone}'); + }, + ), + ), + ], + ) + ); + + if (!kIsWeb) { + if (order.shippingMethod == 'store-delivery' && order.status != Constants.STATUS_COMPLETE && order.status != Constants.STATUS_CANCELLED) { + col.children.add(Container( + height: 200.0, + child: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: LatLng( + double.parse(order.shippingAddress.lat), + double.parse(order.shippingAddress.lng)), + zoom: 11.0, + ), + onCameraMove: _onCameraMove, + markers: _markers, + polylines: _polyLine, + gestureRecognizers: >[ + new Factory(() => new EagerGestureRecognizer(),) + ].toSet(), + ), + )); + if (order.deliveryDistance != null && order.deliveryDistance.distance != null) { + col.children.add(Container( + padding: EdgeInsets.only(top: 6.0, bottom: 6.0), + margin: EdgeInsets.only(bottom: 6.0), + child: Text( + S.of(context).delivery_distance_token( + order.deliveryDistance.distance.text, + order.deliveryDistance.duration.text + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + } + } + + for (CartLineItem lineItem in order.cartInfo.productList) { + + col.children.add(Container( + padding: EdgeInsets.only(top: 16.0, bottom: 0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Util.showImage('${lineItem.product.imagePath}', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 40.0, color: Colors.transparent,), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(left: 5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${lineItem.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.0, + ), + ), + Text( + '${lineItem.description}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.0, + color: Colors.black26, + ), + ) + ], + ), + ), + ), + Container( + width: 30.0, + alignment: Alignment.centerRight, + child: Text( + 'x${lineItem.quantity.round()}', + style: TextStyle( + fontSize: 14.0, + ), + ), + ), + Container( + width: 60.0, + alignment: Alignment.centerRight, + child: Text( + '${lineItem.getTotalPrice().toStringAsFixed(2)}', + style: TextStyle( + fontSize: 14.0, + ), + ), + ), + ], + ), + )); + } + col.children.add(Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + S.of(context).subtotal, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${order.getSubtotal().toStringAsFixed(2)}', + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ], + ), + ); + for (var i = 0; i < order.cartInfo.extraFeeList.length; i++) { + ExtraFee extraFee = order.cartInfo.extraFeeList[i]; + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + '${extraFee.name}(${extraFee.rate}%)', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${extraFee.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ], + ), + ); + } + col.children.add( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 12.0), + alignment: Alignment.centerRight, + child: Text( + S.of(context).total, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.0, bottom: 12.0), + width: 100.0, + alignment: Alignment.centerRight, + child: Text( + '${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 1: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + col.children.add(Container( + width: double.infinity, + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + child: Text( + S.of(context).delivery_info, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ), + ); + if (order.shippingMethod != 'pickup') { + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).delivery_address, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + child: Text( + '${order.address}, ${order.consignee}, ${order.phone}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } else { + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).pickup_address, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + '${order.cartInfo.businessInfo.fullAddress}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).schedule_delivery, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + '${Utils.timestampToString(context, order.bookedAt, withTime: true)}', + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).delivery_method, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + flex: 1, + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + order.shippingMethod == 'pickup' ? S.of(context).pickup : S.of(context).store_delivery, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 2: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + col.children.add(Container( + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).order_info, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + Text(''), + ], + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ),); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).order_number, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + '${order.orderNum}', + style: TextStyle( + color: Colors.black38, + ), + ), + Container( + margin: EdgeInsets.only(left: 10.0, right: 10.0), + child: Text(''), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + width: 0.5, + color: Colors.black12 + ), + ), + ), + ), + GestureDetector( + child: Text( + S.of(context).copy, + ), + onTap: () { + Clipboard.setData(ClipboardData(text: '${order.orderNum}')); + Fluttertoast.showToast( + msg: S.of(context).order_number_copied_to_clipboard, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + backgroundColor: Colors.black54, + textColor: Colors.white + ); + }, + ), + ], + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).payment_method, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + order.payMethod == 0 ? S.of(context).online_payment : S.of(context).pay_on_deliery, + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).payment_status, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + order.paymentStatus == Constants.PAYMENT_STATUS_PAID ? S.of(context).paid : S.of(context).unpaid, + style: TextStyle( + color: Colors.black38, + ), + ), + Container( + margin: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? EdgeInsets.only(left: 10.0, right: 10.0) : EdgeInsets.only(left: 0.0, right: 0.0), + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? Text('') : SizedBox.shrink(), + decoration: order.paymentStatus != Constants.PAYMENT_STATUS_PAID && order.status != Constants.STATUS_CANCELLED ? BoxDecoration( + border: Border( + left: BorderSide( + width: 0.5, + color: Colors.black12 + ), + ), + ) : null, + ), + GestureDetector( + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE ? Text( + S.of(context).pay_now, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ) : SizedBox.shrink(), + onTap: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + ), + ], + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + col.children.add(Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 110.0, + margin: EdgeInsets.only(top: 10.0, bottom: 10.0, right: 10.0), + child: Text( + S.of(context).order_datetime, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + alignment: Alignment.centerRight, + child: Text( + Utils.timestampToString(context, order.createdAt, withTime: true), + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + case 3: + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + + col.children.add(Container( + padding: EdgeInsets.only(top: 0.0, bottom: 16.0), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).order_fulfillment, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + ), + ), + Text( + Utils.getOrderStatus(context, order.status), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 17.0, + fontWeight: FontWeight.bold, + color: Colors.orange, + ), + ), + ], + ), + decoration: BoxDecoration( + color: Colors.transparent, + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black26, + ) + ), + ), + ),); + + for (Fulfillment fulfillment in order.fulfillments) { + col.children.add(Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: fulfillment.shippingMethod.isNotEmpty && fulfillment.trackingNumber != null && fulfillment.trackingNumber.isNotEmpty ? + Text( + '${fulfillment.shippingMethod} ${fulfillment.trackingNumber}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ) : (fulfillment.shippingMethod.isNotEmpty ? Text( + '${fulfillment.shippingMethod}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ) : SizedBox.shrink()), + ), + Container( + child: fulfillment.note != null && fulfillment.note.isNotEmpty ? + Text( + '${fulfillment.note}', + style: TextStyle( + fontSize: 15.0, + color: Colors.black38, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ) : SizedBox.shrink(), + ), + Container( + child: Text( + '${Utils.utcDatetimeStringToLocalDatetimeString(context, fulfillment.createdAt, withTime: true)}', + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + )); + } + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: col, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black12, + ) + ), + ), + ); + break; + default: + return SizedBox(); + } + }, + ), + ), + bottomNavigationBar: Container( + padding: new EdgeInsets.only(left: 16.0, right: 16.0, top: 5.0, bottom: 5.0), + color: new Color(0xFFA8A8FF), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 10.0), + child: order.status == Constants.STATUS_PENDING && order.paymentStatus == Constants.PAYMENT_STATUS_UNPAID ? + FlatButton( + child: Text( + S.of(context).cancel_order, + ), + onPressed: () { + _cancelOrder(context); + }, + ) : SizedBox.shrink(), + ), + Container( + child: order.status == Constants.STATUS_COMPLETE && !order.hasComment ? RaisedButton( + child: Text( + S.of(context).comment, + ), + onPressed: () { + Routes.router.navigateTo(context, '/new-comment/${order.id}'); + }, + ) : SizedBox.shrink(), + ), + ], + ), + Container( + child: order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE ? + FlatButton( + child: Text( + S.of(context).pay_now, + ), + onPressed: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + ) : FlatButton( + child: Text( + S.of(context).order_again, + ), + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + ), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + if (!kIsWeb) { + Util.getBytesFromAsset('assets/images/home.png', 100).then((value) { + homeIcon = BitmapDescriptor.fromBytes(value); + }); + Util.getBytesFromAsset('assets/images/delivery.png', 80).then((value) { + deliveryIcon = BitmapDescriptor.fromBytes(value); + }); + Util.getBytesFromAsset('assets/images/shop.png', 100).then((value) { + shopIcon = BitmapDescriptor.fromBytes(value); + }); + } + _loadOrder(); + } + + _loadOrder() { + HttpUtil.httpGet('v1/orders/${widget.orderId}', + queryParameters: { + 'expand': 'cart_info,business_info', + }, + ).then((data) { + if (mounted) { + setState(() { + order = Order.fromJson(data); + + if (!kIsWeb) { + if (order.shippingMethod == 'store-delivery' && order.status != Constants.STATUS_COMPLETE && order.status != Constants.STATUS_CANCELLED) { + storeLatLng = LatLng(double.parse(order.businessInfo.address.lat), + double.parse(order.businessInfo.address.lng)); + customerLatLng = LatLng(double.parse(order.shippingAddress.lat), + double.parse(order.shippingAddress.lng)); + deliveryLatLng = + LatLng(order.shipperPosition.lat, order.shipperPosition.lng); + + _polyLine.clear(); + _polyLine.add( + Polyline( + polylineId: PolylineId('shipper-customer'), + color: Colors.green, + patterns: [ + PatternItem.dash(20.0), + PatternItem.gap(10), + ], + width: 3, + points: [ + order.shipperPosition.lat != 0.0 ? deliveryLatLng : storeLatLng, + customerLatLng, + ] + ) + ); + + _markers.clear(); + _markers.add(Marker( + markerId: MarkerId('shop_position'), + position: storeLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .store, + snippet: '', + ), + icon: shopIcon, + )); + _markers.add(Marker( + markerId: MarkerId('customer_position'), + position: customerLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .customer, + snippet: order.shippingAddress.addressLine1, + ), + icon: homeIcon, + )); + if (order.shipperPosition.lat != 0.0 && + order.shipperPosition.lng != 0.0) { + _markers.add(Marker( + markerId: MarkerId('shipper_position'), + position: deliveryLatLng, + infoWindow: InfoWindow( + title: S + .of(context) + .delivery_guy, + snippet: '', + ), + icon: deliveryIcon, + )); + } + } + } + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } + + _cancelOrder(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_cancel_the_order), + actions: [ + FlatButton( + child: Text( + S.of(context).no, + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + S.of(context).yes_i_am_sure, + ), + onPressed: () { + Navigator.of(context).pop(); + _processCancelOrder(); + if (mounted) { + setState(() { + order = null; + }); + } + }, + ), + ], + ); + } + ); + } + + _processCancelOrder() { + HttpUtil.httpGet( + 'v1/order-cancel/${widget.orderId}' + ).then((data) { + if (mounted) { + setState(() { + order = Order.fromJson(data); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_orders.dart b/lib/widgets/mobile/mobile_orders.dart new file mode 100644 index 0000000..e379c1f --- /dev/null +++ b/lib/widgets/mobile/mobile_orders.dart @@ -0,0 +1,452 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:flutter_wisetronic/generated/l10n.dart'; +import 'package:flutter_wisetronic/models/order.dart'; +import 'package:flutter_wisetronic/pages/order_detail.dart'; +import 'package:flutter_wisetronic/store/actions.dart'; +import 'package:flutter_wisetronic/store/store.dart'; +import 'package:flutter_wisetronic/utils/double_back_to_close_app.dart'; +import 'package:flutter_wisetronic/utils/http_util.dart'; +import 'package:flutter_wisetronic/utils/utils.dart'; +import '../../constants.dart'; +import '../../routes.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'MobileBottomNav.dart'; + +class MobileOrders extends StatefulWidget { + final Key key; + const MobileOrders({this.key}); + + @override + State createState() { + return MobileOrdersState(); + } + +} + +class MobileOrdersState extends State with SingleTickerProviderStateMixin { + GlobalKey _scaffoldKey = GlobalKey(); + + List orders = []; + + int _page = 1; + int _pageCount = 1; + + bool _isLoading = false; + bool _loadingFinish = false; + + RefreshController _refreshController = RefreshController(initialRefresh: true); + + void _onRefresh() { + _page = 1; + if (orders != null) { + orders.clear(); + } else { + orders = []; + } + _refreshController.resetNoData(); + _loadData(true); + } + + void _onLoadMore() { + // if failed,use loadFailed(),if no data return,use LoadNodata() + if (_pageCount > _page) { + _page += 1; + _loadData(false); + } else { + _refreshController.loadNoData(); + } + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (store.state.user == null) { + store.dispatch(UpdateRedirectRoute('/orders')); + Routes.router.navigateTo(context, '/login', replace: true); + return; + } + }); + + Widget body = SmartRefresher( + enablePullDown: true, + enablePullUp: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode){ + Widget footer; + if(mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center(child: footer,), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoadMore, + child: _buildBody(), + ); + + return Scaffold( + key: _scaffoldKey, + appBar: AppBar( + title: Text( + S.of(context).my_orders, + ), + centerTitle: true, + ), + body: DoubleBackToCloseApp( + snackBar: SnackBar( + content: Text(S.of(context).tap_back_again_to_exit), + ), + child: body, + ), + bottomNavigationBar: MobileBottomNav(currentIndex: 3,), + ); + } + + Widget _buildBody() { + + return ListView.builder( + itemCount: orders != null && orders.length > 0 ? orders.length : 1, + itemBuilder: (BuildContext context, int position) { + Order order; + if (orders != null && orders.length > 0) { + order = orders[position]; + } + if (order == null) { + return Container( + padding: EdgeInsets.all(16.0), + child: Center( + child: Text( + S.of(context).you_have_no_orders_yet, + ), + ), + ); + } + + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + row.children.add(Expanded( + child: Container( + child: Text( + order.cartInfo.productList[0].name, + style: TextStyle( + fontSize: 14.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + )); + if (order.cartInfo.productList.length > 1) { + row.children.add(Container( + child: Text( + S.of(context).and_more_item_token(Utils.getProductLineInOrder(order.cartInfo)), + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + )); + } + row.children.add(Container( + width: 80.0, + alignment: Alignment.centerRight, + child: Text( + '\$${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + )); + + Row row3 = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + RaisedButton( + child: Text( + S.of(context).detail, + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + ], + ); + if (order.status == Constants.STATUS_COMPLETE && !order.hasComment) { + row3.children.add( + Padding( + padding: EdgeInsets.only(left: 10.0), + child: RaisedButton( + child: Text( + S.of(context).comment, + ), + onPressed: () { + Routes.router.navigateTo(context, '/new-comment/${order.id}'); + }, + ), + ), + ); + } + + Row row2 = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID) { + row2.children.add(row3); + if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID + && order.status != Constants.STATUS_CANCELLED + && order.status != Constants.STATUS_COMPLETE) { + row2.children.add(RaisedButton( + child: Text( + S.of(context) + .pay_now, + style: TextStyle( + color: Colors.white, + ), + ), + color: Colors.redAccent, + onPressed: () { + Routes.router.navigateTo(context, '/paynow/${order.id}'); + }, + )); + } else { + row2.children.add(RaisedButton( + child: Text( + S.of(context).order_again, + style: TextStyle( + color: Colors.white, + ), + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + )); + } + } else { + row2.children.add(row3); + row2.children.add(RaisedButton( + child: Text( + S.of(context).order_again, + style: TextStyle( + color: Colors.white, + ), + ), + color: Theme.of(context).primaryColor, + onPressed: () { + Utils.orderAgain(context, order.cartInfo); + }, + )); + } + + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + child: Util.showImage('${order.cartInfo.businessInfo.picUrl}', + width: 32.0, + height: 32.0, + fit: BoxFit.fill, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + margin: EdgeInsets.only(left: 5.0, right: 5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + '${order.cartInfo.businessInfo.name}', + style: TextStyle( + fontSize: 20.0, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + '#${order.orderNum} ${Utils.timestampToString(context, order.createdAt, withTime: true)}', + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + ), + Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: Text( + '${Utils.getOrderStatus(context, order.status)}', + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: SizedBox.shrink(), + ), + ], + ), + ), + ], + ), + Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + width: double.infinity, + child: SizedBox(), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 10.0, bottom: 20.0), + child: row, + ), + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,), + )); + }, + ), + Container( + child: row2, + ), + ], + ), + ); + }, + ); + } + + @override + void initState() { + super.initState(); + + _page = 1; + _pageCount = 1; + eventBus.on().listen((event) { + if (mounted) { + setState(() { + orders = null; + _refreshController.requestRefresh(); + }); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + _loadData(bool isRefresh) { + _loadingFinish = false; + HttpUtil.httpGet('v1/orders', + queryParameters: { + 'expand': 'cart_info', + 'page': _page.toString(), + 'size': Constants.ORDERS_PER_PAGE.toString(), + }, + ).then((data) { +// Utils.jsonPrettyPrint(data); + if (isRefresh) { + _refreshController.refreshCompleted(); + } else { + _refreshController.loadComplete(); + } + + if (int.parse(data['_meta']['currentPage'].toString()) >= int.parse(data['_meta']['pageCount'].toString())) { + _loadingFinish = true; + } + if (_loadingFinish) { + _refreshController.loadNoData(); + } + _page = int.parse(data['_meta']['currentPage'].toString()); + _pageCount = int.parse(data['_meta']['pageCount'].toString()); + + if (mounted) { + setState(() { + _isLoading = false; + orders.addAll((data['items'] as List).map((e) => Order.fromJson(e)).toList()); + }); + } + }).catchError((error) { + if (isRefresh) { + _refreshController.refreshFailed(); + } else { + _refreshController.loadFailed(); + } + if(mounted) { + setState(() { + _isLoading = false; + }); + } + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_pay_now.dart b/lib/widgets/mobile/mobile_pay_now.dart new file mode 100644 index 0000000..5428d14 --- /dev/null +++ b/lib/widgets/mobile/mobile_pay_now.dart @@ -0,0 +1,377 @@ + + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/payment_verification_code_dialog.dart'; + +class MobilePayNow extends StatefulWidget { + final Key key; + final int orderId; + const MobilePayNow(this.orderId, {this.key}); + + @override + State createState() { + return MobilePayNowState(); + } + +} + +class MobilePayNowState extends State { + Order order; + List paymentPlatforms; + User _user; + + bool nativePay; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (order == null ) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + BuildContext mainContext = context; + + ListView listView = ListView.builder( + itemCount: nativePay ? paymentPlatforms.length + 3 : paymentPlatforms.length + 2, + itemBuilder: (BuildContext context, int position) { + if (position == 0) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.only(top: 32.0, right: 16.0, left: 16.0, bottom: 32.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + S.of(context).payment_amount, + style: TextStyle( + fontSize: 15.0, + color: Colors.black26, + ), + ), + Text( + '\$${order.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ) + ) + ), + ), + store.state.deviceId != null && store.state.deviceId.isNotEmpty || store.state.tableNumber != null && store.state.tableNumber.isNotEmpty ? + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.local_cafe, + size: 50.0, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).pay_later, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).pay_after_meal, + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black26, + ) + ) + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true); + }, + ) : + SizedBox.shrink() + ], + ); + } + PaymentPlatform paymentPlatform; + if (position == 1) { + if (_user.stripePaymentMethods.length > 0) { + paymentPlatform = paymentPlatforms[position - 1]; + Column column = Column( + children: [], + ); + column.children.add( + Container( + padding: EdgeInsets.all(16.0), + width: double.infinity, + child: Text( + S.of(context).pay_with_existing_cards, + textAlign: TextAlign.left, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, + color: Colors.black26, + ), + ), + ), + ), + ); + for (StripePaymentMethod stripePaymentMethod in _user.stripePaymentMethods) { + column.children.add( + GestureDetector( + child: Container( + padding: EdgeInsets.only( + top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + stripePaymentMethod.cardBrand == 'visa' ? + Image.asset( + 'assets/images/visa.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : (stripePaymentMethod.cardBrand == 'mastercard' ? + Image.asset( + 'assets/images/master.png', + width: 50.0, + height: 50.0, + fit: BoxFit.fill, + ) : Icon( + Icons.credit_card, size: 50.0, color: Colors.black38,)), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + '**** ${stripePaymentMethod.cardLast4}', + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Container( + margin: EdgeInsets.only(left: 20.0, right: 10.0), + child: Text( + S.of(context).expire_token(stripePaymentMethod.cardExpMonth, stripePaymentMethod.cardExpYear), + style: TextStyle( + fontSize: 14.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + return PaymentVerificationCodeDialog(_user, () { + Util.goPayment(context, order, paymentPlatform, + stripePaymentMethod: stripePaymentMethod); + }, () { + + }); + }, + ); + }, + ), + ); + } + column.children.add( + Container( + width: double.infinity, + child: SizedBox.shrink(), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black26, + ), + ), + ), + ) + ); + return column; + } else { + return SizedBox.shrink(); + } + } + if (position == 2 && nativePay) { + paymentPlatform = paymentPlatforms[position - 2]; + return Util().getNativePay(mainContext, order, paymentPlatform); + } + paymentPlatform = nativePay ? paymentPlatforms[position - 3] : paymentPlatforms[position - 2]; + return GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 10.0), + child: Util.showImage(paymentPlatform.icon, + errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 50.0, color: Colors.transparent,), + fit: BoxFit.cover, + width: 50.0, + height: 50.0, + ), + ), + Expanded( + child: Text( + S.of(context).pay_with_token(PaymentPlatform.getPaymentPlatformName(context, paymentPlatform.code)), + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.black26, + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10, + color: Colors.black26, + ), + ), + ), + ), + onTap: () { + Util.goPayment(context, order, paymentPlatform); + }, + ); + } + ); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).pay_now), + backgroundColor: Theme.of(context).primaryColor, + ), + body: listView, + ); + } + + @override + void initState() { + super.initState(); + HttpUtil.httpGet( + 'v1/${widget.orderId}/paymentplatforms', + ).then((data) { + paymentPlatforms = (data['payment_platforms'] as List).map((e) => + PaymentPlatform.fromJson(e)).toList(); + PaymentPlatform pp = paymentPlatforms[0]; + if (Constants.ENABLE_NATIVE_PAY && pp.publishableKey != null + && pp.publishableKey.isNotEmpty && pp.merchantId != null + && pp.merchantId.isNotEmpty) { + nativePay = true; + } else { + nativePay = false; + } + _user = User.fromJson(data['contact']); + store.dispatch(UpdateCurrentUser(_user)); + eventBus.fire(OnCurrentUserUpdated()); + setState(() { + order = Order.fromJson(data['order']); + }); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.navigateTo(context, "/orders", replace: true); + }); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_plain_page.dart b/lib/widgets/mobile/mobile_plain_page.dart new file mode 100644 index 0000000..44e1f72 --- /dev/null +++ b/lib/widgets/mobile/mobile_plain_page.dart @@ -0,0 +1,63 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; + +import '../../models/blog.dart'; + +class MobilePlainPage extends StatefulWidget { + final Blog blog; + + const MobilePlainPage(this.blog, {Key key}) : super(key: key); + + @override + State createState() { + return MobilePlainPageState(); + } + +} + +class MobilePlainPageState extends State { + + @override + Widget build(BuildContext context) { + Column col = Column( + children: [], + ); + col.children.add(Container( + padding: EdgeInsets.only(top: 20, left: 16.0, right: 16.0, bottom: 16.0), + child: Center( + child: Text( + widget.blog.title, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ), + )); + col.children.add(Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 20.0), + child: MarkdownBody( + shrinkWrap: true, + data: widget.blog.body, + ), + )); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(widget.blog.title), + backgroundColor: Theme.of(context).primaryColor, + ), + body: SingleChildScrollView( + child: col, + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_product_detail_page.dart b/lib/widgets/mobile/mobile_product_detail_page.dart new file mode 100644 index 0000000..bb357ff --- /dev/null +++ b/lib/widgets/mobile/mobile_product_detail_page.dart @@ -0,0 +1,477 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/Subproduct.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../models/product_detail.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/shop_scroll_controller.dart'; +import '../../utils/shop_scroll_coordinator.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/carousel.dart'; +import '../../widgets/general/show_price.dart'; + +MediaQueryData mediaQuery; +double statusBarHeight; +double screenHeight; +double screenWidth; + +class MobileProductDetailPage extends StatefulWidget { + final Business business; + final Product product; + + const MobileProductDetailPage( + {@required this.business, @required this.product, Key key}) + : super(key: key); + + @override + State createState() { + return MobileProductDetailPageState(); + } +} + +class MobileProductDetailPageState extends State + with SingleTickerProviderStateMixin { + ShopScrollCoordinator _shopCoordinator; + ShopScrollController _pageScrollController; + + TabController _tabController; + + double _sliverAppBarInitHeight; + double _sliverAppBarMaxHeight; + final double _tabBarHeight = 50; + + ProductDetail productDetail; + + bool refresh; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + if (productDetail == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + refresh = false; + + mediaQuery ??= MediaQuery.of(context); + screenHeight ??= mediaQuery.size.height; + screenWidth ??= mediaQuery.size.width; + statusBarHeight ??= mediaQuery.padding.top; + + _sliverAppBarInitHeight ??= screenWidth; + _sliverAppBarMaxHeight ??= screenWidth; + + _pageScrollController ??= _shopCoordinator + .pageScrollController(_sliverAppBarMaxHeight - _sliverAppBarInitHeight); + + _shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () { + return statusBarHeight + kToolbarHeight + _tabBarHeight; + }; + + return Scaffold( + body: Listener( + onPointerUp: _shopCoordinator.onPointerUp, + child: CustomScrollView( + controller: _pageScrollController, + physics: ClampingScrollPhysics(), + slivers: [ + SliverAppBar( + pinned: true, + floating: true, + snap: true, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: Colors.black26, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + backgroundColor: Theme.of(context).primaryColor, + expandedHeight: _sliverAppBarMaxHeight - 30.0, + flexibleSpace: FlexibleSpaceBar( + background: Carousel( + height: _sliverAppBarInitHeight + 0.0, + pages: _getProductPictures(context), + autoPlay: false, + ), + ), + ), + SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate( + minHeight: 60.0, + maxHeight: 60.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: + EdgeInsets.only(left: 10.0, top: 5.0, right: 10.0), + child: Text( + productDetail.name, + style: TextStyle( + fontSize: 15.0, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShowPrice( + productDetail.price, + currencySign: '\$', + fontWeight: FontWeight.bold, + smallFontSize: 14, + largeFontSize: 18, + regularPrice: productDetail.regularPrice, + ), + Container( + child: AddRemoveButton( + business: widget.business, + product: widget.product, + ), + padding: EdgeInsets.only(top: 5.0, right: 10.0), + ), + ], + ) + ], + ), + ), + ), + SliverPersistentHeader( + pinned: true, + floating: false, + delegate: _SliverAppBarDelegate( + minHeight: _tabBarHeight, + maxHeight: _tabBarHeight, + child: Container( + color: Colors.white, + child: TabBar( + labelColor: new Color(0xFF3190E8), + unselectedLabelColor: new Color(0xFF666666), + indicatorColor: new Color(0xFF3190E8), + indicatorSize: TabBarIndicatorSize.label, + labelStyle: new TextStyle( + fontSize: 16.0, + ), + controller: _tabController, + tabs: [ + Tab( + text: S.of(context).detail, + ), + Tab( + text: S.of(context).specification, + ), + ], + ), + ), + ), + ), + SliverFillRemaining( + child: TabBarView( + controller: _tabController, + children: [ + SingleChildScrollView( + controller: _shopCoordinator.newChildScrollController(), + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (productDetail.subproducts.length > 0) ? + subProducts(productDetail.subproducts) : + SizedBox.shrink(), + Container( + padding: EdgeInsets.only( + top: 10.0, left: 10.0, right: 10.0), + child: Text( + productDetail.description, + style: TextStyle( + fontSize: 14.0, color: Colors.black54), + ), + ), + Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0), + child: (productDetail.description2 != null && + !productDetail.description2.isEmpty) + ? Text( + '${productDetail.description2}', + style: TextStyle( + fontSize: 14.0, color: Colors.black54), + ) + : SizedBox.shrink(), + ), + productDetail.detailDescription != null + ? Container( + padding: + EdgeInsets.only(left: 10.0, right: 10.0), + child: MarkdownBody( + data: '${productDetail.detailDescription}', + shrinkWrap: true, + ), + ) + : SizedBox.shrink(), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only( + top: 10.0, left: 10.0, right: 10.0, bottom: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).weight_token(productDetail.weight), + style: TextStyle( + fontSize: 12.0, + color: Colors.black54, + ), + ), + ), + Container( + child: Text( + S.of(context).dimentions_token( + productDetail.dimentionsLength, + productDetail.dimentionsWidth, + productDetail.dimentionsHeight), + style: TextStyle( + fontSize: 12.0, + color: Colors.black54, + ), + ), + ) + ], + ), + ) + ], + ), + ) + ], + ), + ), + ); + } + + @override + void initState() { + setState(() { + refresh = false; + }); + super.initState(); + _shopCoordinator = ShopScrollCoordinator(); + _tabController = TabController(vsync: this, length: 2); + HttpUtil.httpGet( + 'v1/product-detail/${widget.product.id}', + businessId: widget.business.id, + ).then((value) { + productDetail = ProductDetail.fromJson(value); + setState(() { + refresh = true; + }); + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + refresh = true; + }); + } + }); + } + + Widget subProducts(List subproducts) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 6, top: 10, bottom: 10), + child: Text( + S.of(context).includes, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ) + ], + ); + for (int i = 0; i < subproducts.length; i++) { + col.children.add(subProduct(subproducts[i])); + } + return col; + } + + Widget subProduct(Subproduct subproduct) { + Row row = Row( + children: [], + ); + row.children.add( + Container( + padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 5.0), + child: Util.showImage( + 'https:${subproduct.product.image}', + width: 48, + height: 48, + fit: BoxFit.contain, + ), + ), + ); + row.children.add( + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 12, top: 5), + child: Text( + subproduct.product.name, + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + ), + Container( + width: 80, + padding: EdgeInsets.only(left: 12, top: 5, right: 12), + child: Text( + '${subproduct.product.price.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 13, + decoration: TextDecoration.lineThrough, + ), + ), + alignment: Alignment.centerRight, + ), + Container( + width: 60, + padding: EdgeInsets.only(left: 12, top: 5, right: 12), + child: Text( + 'x${subproduct.quantity.toStringAsFixed(0)}', + style: TextStyle( + fontSize: 13, + ), + ), + alignment: Alignment.centerRight, + ), + ], + ), + Container( + padding: EdgeInsets.only(left: 12, top: 12, right: 12), + child: Text( + '${subproduct.product.description}', + style: TextStyle( + fontSize: 12, + color: Colors.black45, + ), + ), + ), + ], + ), + ), + ); + return Container( + padding: EdgeInsets.only(left: 0, right: 0, top: 0, bottom: 0), + child: row, + ); + } + + List _getProductPictures(BuildContext context) { + var pages = []; + List images = []; + images.add(productDetail.image); + for (var i = 0; i < productDetail.images.length; i++) { + // print('>>https:' + productDetail.images[i].image); + images.add(productDetail.images[i].image); + } + + for (var i = 0; i < images.length; i++) { + pages.add(new GestureDetector( + child: new Container( + child: Util.showImage( + 'https:' + images[i], + ), + ), + onTap: () {}, + )); + } + return pages; + } + + @override + void dispose() { + _tabController?.dispose(); + _pageScrollController?.dispose(); + super.dispose(); + } +} + +class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { + _SliverAppBarDelegate({ + @required this.minHeight, + @required this.maxHeight, + @required this.child, + }); + + final double minHeight; + final double maxHeight; + final Widget child; + + @override + double get minExtent => this.minHeight; + + @override + double get maxExtent => max(maxHeight, minHeight); + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return SizedBox.expand(child: child); + } + + @override + bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { + return maxHeight != oldDelegate.maxHeight || + minHeight != oldDelegate.minHeight || + child != oldDelegate.child; + } +} diff --git a/lib/widgets/mobile/mobile_product_item.dart b/lib/widgets/mobile/mobile_product_item.dart new file mode 100644 index 0000000..8a46e20 --- /dev/null +++ b/lib/widgets/mobile/mobile_product_item.dart @@ -0,0 +1,141 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../pages/product_detail_page.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/show_price.dart'; +import '../../widgets/general/style.dart'; + +class MobileProductItem extends StatefulWidget { + final Product product; + final Business business; + + MobileProductItem({this.product, this.business, Key key}) + : super(key: key); + + @override + State createState() { + return MobileProductItemState(); + } +} + +class MobileProductItemState extends State { + int _qty = 0; + + @override + Widget build(BuildContext context) { + return new Container( + height: 124.0, + padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0).copyWith(bottom: 5.0), + decoration: new BoxDecoration( + color: Style.backgroundColor, + border: new Border( + bottom: new BorderSide( + color: new Color(0xFFEBEBEB), + ) + ), + ), + child: new SizedBox.expand( + child: new Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + margin: new EdgeInsets.only(right: 10.0), + width: 80.0, + height: 80.0, + child: GestureDetector( + child: Util.showImage('${widget.product.imagePath}', + fit: BoxFit.fill, + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + child: GestureDetector( + child: Text( + '${widget.product.name}', +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + style: new TextStyle( + fontSize: 15.0, + ), + ), + onTap: (){ + _showProductDetail(); + }, + ), + ), + new Text( + widget.product.description, +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: new TextStyle( + fontSize: 12.0, + color: new Color(0xFF999999), + ), + ), + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Container( + child: widget.business.showMonthlySold ? + Text( + S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)), + style: new TextStyle( + fontSize: 9.0 + ), + ) : SizedBox.shrink(), + ), + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + ShowPrice( + widget.product.price, + regularPrice: widget.product.regularPrice, + currencySign: '\$', + ), + ], + ), + ], + ), + new AddRemoveButton(product: widget.product, business: widget.business, addOnly: false,), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + void _showProductDetail() { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => + ProductDetailPage( + product: widget.product, business: widget.business,)), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_renew_license.dart b/lib/widgets/mobile/mobile_renew_license.dart new file mode 100644 index 0000000..5687efb --- /dev/null +++ b/lib/widgets/mobile/mobile_renew_license.dart @@ -0,0 +1,158 @@ + +import 'dart:async'; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileRenewLicense extends StatefulWidget { + final int businessId; + + const MobileRenewLicense(this.businessId, {Key key}) : super(key: key); + + @override + State createState() { + return MobileRenewLicenseState(); + } + +} + +class MobileRenewLicenseState extends State { + + TextEditingController groupNumberController = TextEditingController(); + bool canSubmit = false; + + @override + Widget build(BuildContext context) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + + col.children.add( + Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 10,), + child: Text( + S.of(context).renew_license, + style: TextStyle( + fontSize: 28, + ), + ), + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 10), + child: Text( + S.of(context).group_number_can_be_found, + style: TextStyle( + color: Colors.black45, + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 0, bottom: 10), + child: Image.asset('assets/images/group_number.png',), + ), + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 0, bottom: 10), + child: Text( + S.of(context).group_number, + style: TextStyle( + fontSize: 28, + ), + ), + ) + ); + col.children.add( + Container( + padding: EdgeInsets.only(left: 16, right: 16, bottom: 0), + child: TextFormField( + controller: groupNumberController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).group_number, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).please_enter_group_number; + } + return null; + }, + onChanged: (string) { + if (string.isNotEmpty) { + canSubmit = true; + } else { + canSubmit = false; + } + setState(() {}); + }, + ), + ) + ); + col.children.add(Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: ElevatedButton( + child: Text( + S.of(context).submit, + ), + onPressed: canSubmit ? () { + _submit(); + } : null, + ), + )); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).renew_license), + backgroundColor: Theme.of(context).primaryColor, + ), + body: SingleChildScrollView( + child: col, + ), + ); + } + + void _submit() { + HttpUtil.httpPost('v1/get-license-renewal', + (response) { + Routes.router.navigateTo(context, '/renew-minioffice/${response.data['id']}', replace: true); + }, + body: { + 'group_name': groupNumberController.text.trim(), + }, + isFormData: true, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_renew_minioffice.dart b/lib/widgets/mobile/mobile_renew_minioffice.dart new file mode 100644 index 0000000..6268cb6 --- /dev/null +++ b/lib/widgets/mobile/mobile_renew_minioffice.dart @@ -0,0 +1,159 @@ + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileRenewMiniOffice extends StatefulWidget { + final Map data; + + const MobileRenewMiniOffice(this.data, {Key key}) : super(key: key); + + @override + State createState() { + return MobileRenewMiniOfficeState(); + } + +} + +class MobileRenewMiniOfficeState extends State { + + @override + Widget build(BuildContext context) { + Column col = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [Container( + padding: EdgeInsets.only(bottom: 16), + child: Text( + S.of(context).group_license_renewal, + style: TextStyle( + fontSize: 28, + ), + ), + )], + ); + col.children.add( + buildLine(S.of(context).group_number, widget.data['group']['name'], valueSize: 18), + ); + col.children.add( + buildLine(S.of(context).expiration_date, + Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['expiration_date']), + valueSize: 18 + ), + ); + col.children.add( + buildLine(S.of(context).after_renewed, + Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['renewed_expiration_date']), + valueSize: 18 + ), + ); + col.children.add( + buildLine(S.of(context).renewal_fee, '\$${widget.data['renewal_fee']}', valueSize: 18), + ); + col.children.add( + buildLine(S.of(context).tax, '\$${widget.data['tax']}', valueSize: 18), + ); + col.children.add( + buildLine(S.of(context).total, '\$${widget.data['renewal_total']}', valueSize: 38), + ); + col.children.add( + Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.only(left: 0, right: 0, top: 20, bottom: 20), + child: ElevatedButton( + child: Container( + padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8), + child: Text( + S.of(context).pay_amount_token(widget.data['renewal_total']), + style: TextStyle( + fontSize: 20, + ), + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).renew_license), + backgroundColor: Theme.of(context).primaryColor, + ), + body: SingleChildScrollView( + child: Container( + padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20), + child: col, + ), + ), + ); + } + + Widget buildLine(String name, String value, {double nameSize, double valueSize}) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + row.children.add( + Container( + width: 150, + padding: EdgeInsets.only(), + child: Text( + name, + style: TextStyle( + color: Colors.black38, + fontSize: nameSize, + ), + ), + ), + ); + row.children.add( + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Text( + value, + style: TextStyle( + fontSize: valueSize, + ), + ), + ), + ), + ); + return Container( + padding: EdgeInsets.only(top: 8, bottom: 8), + child: row, + ); + } + + void _submit() { + if (store.state.user == null) { + Utils.requireLogin(context, returnUrl: '/renew-minioffice/${widget.data['group']['id']}'); + return; + } + HttpUtil.httpPost('v1/create-minioffice-renewal-invoice', + (response) { + Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true); + }, + body: widget.data, + ).onError((error, stackTrace) { + Utils.showMessageDialog(context, error); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_reset_password.dart b/lib/widgets/mobile/mobile_reset_password.dart new file mode 100644 index 0000000..7ea0d2d --- /dev/null +++ b/lib/widgets/mobile/mobile_reset_password.dart @@ -0,0 +1,248 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileResetPassword extends StatefulWidget { + final String mobile; + final String code; + + const MobileResetPassword(this.mobile, {this.code, Key key}) + : super(key: key); + + @override + State createState() { + return MobileResetPasswordState(); + } +} + +class MobileResetPasswordState extends State { + final GlobalKey _formKey = GlobalKey(); + + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ListView( + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).reset_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).reset_password_desc, + style: TextStyle( + fontSize: 15.0, + color: Colors.black54, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 16.0, top: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ); + } + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).reset_password_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/login', replace: true, clearStack: false); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'reset_password', + }, + isFormData: true, + body: { + 'mobile': widget.mobile, + 'code': widget.code, + 'password': passwordController.text.trim() + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_search_place.dart b/lib/widgets/mobile/mobile_search_place.dart new file mode 100644 index 0000000..5f385b6 --- /dev/null +++ b/lib/widgets/mobile/mobile_search_place.dart @@ -0,0 +1,132 @@ + +import 'package:dio/dio.dart'; +import 'package:flappy_search_bar/flappy_search_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/located_address.dart'; +import '../../pages/new_address.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileSearchPlace extends StatefulWidget { + final Key key; + final int businessId; + const MobileSearchPlace(this.businessId, {this.key}) : super(key: key); + + @override + State createState() { + return MobileSearchPlaceState(); + } + +} + +class MobileSearchPlaceState extends State { + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + } + ), + title: Text( + S.of(context).search_place, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: SearchBar( + minimumChars: 6, + hintText: S.of(context).enter_delivery_address, + cancellationWidget: Text( + S.of(context).cancel, + ), + onSearch: search, + onItemFound: (LocatedAddress locatedAddress, int index) { + return ListTile( + title: Text(locatedAddress.streetName), + subtitle: Text(locatedAddress.formattedAddress), + onTap: () { + if (widget.businessId == 0) { + _selectPlace(locatedAddress); + } else { + _selectPlaceAsNew(locatedAddress); + } + }, + dense: true, + + ); + }, + onError: (error) { + return Center( + child: Text("$error"), + ); + }, + emptyWidget: Center( + child: widget.businessId > 0 ? + GestureDetector( + child: Text( + S.of(context).empty_address_change_keyword, + ), + onTap: () { + _selectPlaceAsNew(null); + }, + ) : + Text( + S.of(context).empty_result_change_keyword + ), + ), + ), + ), + ), + ); + } + + @override + void initState() { + super.initState(); + } + + Future> search(String keyword) async { + var result = await HttpUtil.httpGet('v1/search-places', + queryParameters: { + 'keyword': keyword, + }, + returnError: true, + ); + if (result is DioError) { + if (result.response != null) { + throw RuntimeError(result.response.data['message']); + } else { + throw RuntimeError(result.message); + } + } else if (result != null && result is List) { + return result.map((e) => LocatedAddress.fromJson(e)).toList(); + } + return []; + } + + void _selectPlace(LocatedAddress locatedAddress) { + store.dispatch(UpdateLocatedAddress(locatedAddress)); + eventBus.fire(OnUpdateLocatedAddressSuccess()); + Navigator.of(context).pop(); + } + + void _selectPlaceAsNew(LocatedAddress locatedAddress) { + print('located address: ${locatedAddress.toString()}'); + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => NewAddress(locatedAddress: locatedAddress, businessId: widget.businessId,), + )); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_set_password.dart b/lib/widgets/mobile/mobile_set_password.dart new file mode 100644 index 0000000..f4c1e48 --- /dev/null +++ b/lib/widgets/mobile/mobile_set_password.dart @@ -0,0 +1,249 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileSetPassword extends StatefulWidget { + final String mobile; + final String code; + + const MobileSetPassword(this.mobile, {Key key, this.code}) : super(key: key); + + @override + State createState() { + return MobileSetPasswordState(); + } + +} + +class MobileSetPasswordState extends State { + + final GlobalKey _formKey = GlobalKey(); + + final passwordController = TextEditingController(); + final passwordAgainController = TextEditingController(); + + bool passwordVisible; + bool passwordAgainVisible; + + bool canReset; + + @override + void initState() { + super.initState(); + canReset = false; + passwordVisible = true; + passwordAgainVisible = true; + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ListView( + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).set_password, + style: TextStyle( + fontSize: 24.0, + color: Colors.black, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).set_password_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black54, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: TextFormField( + controller: passwordController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password, + suffixIcon: IconButton( + icon: Icon( + passwordVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordVisible = !passwordVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + return null; + }, + obscureText: passwordVisible, + onChanged: (string) { + if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + Container( + child: TextFormField( + controller: passwordAgainController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).password_again, + suffixIcon: IconButton( + icon: Icon( + passwordAgainVisible ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColorDark, + ), + onPressed: () { + setState(() { + passwordAgainVisible = !passwordAgainVisible; + }); + }, + ), + ), + style: TextStyle( + fontSize: 18.0 + ), + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).password_is_required; + } + if (value.trim() != passwordController.text.trim()) { + return S.of(context).password_is_not_match_password_again; + } + return null; + }, + obscureText: passwordAgainVisible, + onChanged: (string) { + if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) { + if (mounted) { + setState(() { + canReset = true; + }); + } + } else { + if (mounted) { + setState(() { + canReset = false; + }); + } + } + }, + ), + ), + ], + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 16.0, top: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).submit, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: canReset ? resetPassword : null, + ), + ), + ), + ], + ); + } + + void resetPassword() { + final FormState form = _formKey.currentState; + if (form.validate()) { + HttpUtil.httpPost('v1/users', (response) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).user_account_created_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, '/login', replace: true, clearStack: false); + }, + ), + ], + ); + }, + ); + }, + queryParameters: { + 'action': 'create_user', + }, + isFormData: true, + body: { + 'mobile': widget.mobile, + 'code': widget.code, + 'password': passwordController.text.trim() + } + ).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_shop.dart b/lib/widgets/mobile/mobile_shop.dart new file mode 100644 index 0000000..8ac79d2 --- /dev/null +++ b/lib/widgets/mobile/mobile_shop.dart @@ -0,0 +1,2056 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:badges/badges.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/widgets/general/show_price.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:smooth_star_rating/smooth_star_rating.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/cart_info.dart'; +import '../../models/cart_line_item.dart'; +import '../../models/category_products.dart'; +import '../../models/comment.dart'; +import '../../models/product.dart'; +import '../../models/product_image.dart'; +import '../../pages/product_detail_page.dart'; +import '../../pages/store_product_search.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/shop_scroll_controller.dart'; +import '../../utils/shop_scroll_coordinator.dart'; +import '../../utils/util_web.dart' + if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/animation_point_manager.dart'; +import '../../widgets/general/carousel.dart'; +import '../../widgets/general/product_item.dart'; +import '../../widgets/general/read_more_text.dart'; +import '../../widgets/general/sliding_up_panel.dart'; +import '../../widgets/general/style.dart'; +import 'shopping_cart_bar.dart'; + +class MobileShop extends StatefulWidget { + final int businessId; + + const MobileShop({Key key, this.businessId}) : super(key: key); + + @override + State createState() => MobileShopState(); +} + +MediaQueryData mediaQuery; +double statusBarHeight; +double screenWidth; +double screenHeight; + +class MobileShopState extends State + with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { + GlobalKey _scaffoldKey = new GlobalKey(); + + Business _business; + List _categoryProducts; + List _featuredProducts; + List _hotSaleProducts; + + List _prompts = []; + bool checkCloseFlag = false; + + bool displayProductByCategoryClick = false; + String displayProductByCategoryClickIndicator = ''; + int categoryId = 0; + int _productCurrentPage = 1; + + static const num _categoryHeight = 50.0; + static const num _categoryDescHeight = 50.0; + static const num _productHeight = 115.0; + + int _categoryIndex = 0; + bool _categoryIndexChange = false; + bool _isLoading = false; + + bool refresh = false; + + ShopScrollCoordinator _shopCoordinator; + ShopScrollController _pageScrollController; + TabController _tabController; + final double _sliverAppBarInitHeight = 150.0; + final double _tabBarHeight = 50.0; + double _sliverAppBarMaxHeight; + + ShopScrollController _listScrollController1; + ShopScrollController _listScrollController2; + ShopScrollController _listScrollController3; + + AnimationPointManager _animationPointManager = AnimationPointManager(); + GlobalKey stackKey = GlobalKey(); + GlobalKey endKey = GlobalKey(); + + List comments; + int _commentPage = 1; + int _commentPageCount = 1; + bool _commentLoadingFinish = false; + RefreshController _commentRefreshController = + RefreshController(initialRefresh: true); + + PanelController panelController = PanelController(); + SlidingUpPanel _slidUpShoppingCart; + + SliverPersistentHeader promotHeader; + + bool _animationFinish = true; + + Carousel slidingGellery; + + // StreamSubscription onProductWillAddToCartSubscription; + // StreamSubscription onProductWillRemoveFromCartSubscription; + + void _onCommentRefresh() { + if (_commentPage - 1 > 1) { + _commentPage -= 1; + } else { + _commentPage = 1; + } + _commentRefreshController.resetNoData(); + _loadComment(true); + } + + void _onCommentLoadMore() { + if (_commentPageCount > _commentPage) { + _commentPage += 1; + _loadComment(false); + } else { + _commentRefreshController.loadNoData(); + } + } + + _loadComment(bool isRefresh) { + _commentLoadingFinish = false; + HttpUtil.httpGet( + 'v1/get-comments', + queryParameters: { + 'page': _commentPage.toString(), + 'size': Constants.ORDERS_PER_PAGE.toString(), + }, + businessId: _business.id, + ).then((data) { + if (isRefresh) { + _commentRefreshController.refreshCompleted(); + } else { + _commentRefreshController.loadComplete(); + } + _commentPage = int.parse(data['_meta']['currentPage'].toString()); + _commentPageCount = int.parse(data['_meta']['pageCount'].toString()); + + if (mounted) { + setState(() { + comments = + (data['items'] as List).map((e) => Comment.fromJson(e)).toList(); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error); + }); + } + + itemAddToCart(GlobalKey startKey) { + if (_animationFinish == false) { + return; + } + _animationFinish = false; + + print("start key " + startKey.toString()); + print("stack key " + stackKey.toString()); + print("end key " + endKey.toString()); + + _animationPointManager.addParabolicAniamtion( + vsync: this, + stackKey: stackKey, + startKey: startKey, + endKey: endKey, + color: Colors.red, + statusListener: (AnimationStatus status) { + if (mounted) { + if (status == AnimationStatus.completed) { +// buyOnTap(); + _animationFinish = true; + setState(() {}); + } + } + }, + duration: Duration(milliseconds: 800), + ); + } + + @override + Widget build(BuildContext context) { + if (_business == null) { + return new Scaffold( + body: Center( + child: SpinKitWave( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + mediaQuery ??= MediaQuery.of(context); + screenWidth ??= mediaQuery.size.width; + screenHeight ??= mediaQuery.size.height; + statusBarHeight ??= mediaQuery.padding.top; + + _sliverAppBarMaxHeight ??= screenHeight - 150.0; + + _pageScrollController ??= _shopCoordinator + .pageScrollController(_sliverAppBarMaxHeight - _sliverAppBarInitHeight); + + _shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () { + return statusBarHeight + kToolbarHeight + _tabBarHeight; + }; + + refresh = false; + + _slidUpShoppingCart = SlidingUpPanel( + controller: panelController, + minHeight: 50.0, + maxHeight: 250.0, + isDraggable: true, + backdropEnabled: true, + panel: ShoppingCartBar( + business: _business, + endKey: endKey, + onEmptyCartListener: () { + Future.delayed(Duration(seconds: 1), () { + panelController.close(); + }); + }, + onPanelOpenCloseRequest: () { + if (panelController.isPanelOpen) { + panelController.close(); + } else { + panelController.open(); + } + }, + ), + ); + + Row promotRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (var i = 0; i < _business.promoProducts.length; i++) { + promotRow.children.add(Container( + padding: EdgeInsets.only( + left: 10.0, + top: 10.0, + bottom: 10.0, + ), + width: 150.0, + height: 220.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: Container( + child: Util.showImage( + _business.promoProducts[i].imagePath, + ), + ), + onTap: () { + _showProductDetail(_business.promoProducts[i]); + }, + ), + Container( + child: Text( + _business.promoProducts[i].name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14.0), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShowPrice( + _business.promoProducts[i].price, + currencySign: '\$', + regularPrice: _business.promoProducts[i].regularPrice, + ), + Container( + child: AddRemoveButton( + product: _business.promoProducts[i], + business: _business, + addOnly: true, + ), + ) + ], + ) + ], + ), + )); + } + + if (promotRow.children.length > 0) { + promotHeader = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 250.0, + maxHeight: 260.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, top: 5.0), + child: Text( + S.of(context).promotions, + style: TextStyle(fontSize: 15.0), + ), + ), + Container( + padding: EdgeInsets.only(right: 10.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: promotRow, + ), + ), + ], + ), + ), + ); + } else { + promotHeader = SliverPersistentHeader( + pinned: false, + floating: true, + delegate: _SliverAppBarDelegate2( + minHeight: 0.0, + maxHeight: 0.0, + child: Center( + child: Text(''), + )), + ); + } + + Widget listener = Listener( + onPointerUp: _shopCoordinator.onPointerUp, + child: CustomScrollView( + controller: _pageScrollController, + physics: ClampingScrollPhysics(), + slivers: [ + SliverAppBar( + pinned: true, + floating: true, + snap: true, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + if (_animationFinish) { + Routes.router.pop(context); + } + }), + titleSpacing: 0.0, + actions: [ + Padding( + padding: ButtonTheme.of(context).padding, + child: GestureDetector( + child: Icon(Icons.search), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (BuildContext context) { + return StoreProductSearch(_business); + })); + }, + ), + ), + Padding( + padding: ButtonTheme.of(context).padding, + child: new GestureDetector( + child: new Icon(Icons.phone), + onTap: () { + Utils.launchURL( + 'tel:${Utils.getFirstNumberFromString(_business.phone)}'); + }, + ), + ), + ], + title: Text( + _business != null ? _business.name : '', + style: TextStyle( + color: Colors.white, + ), + ), + backgroundColor: Theme.of(context).primaryColor, + expandedHeight: _sliverAppBarMaxHeight, + flexibleSpace: FlexibleSpaceBar( + background: _buildAppbar(context), + collapseMode: CollapseMode.none, + ), + ), + promotHeader, + SliverPersistentHeader( + pinned: true, + floating: false, + delegate: _SliverAppBarDelegate2( + minHeight: _tabBarHeight, + maxHeight: _tabBarHeight, + child: Container( + color: Colors.white, + child: TabBar( + labelColor: new Color(0xFF3190E8), + unselectedLabelColor: new Color(0xFF666666), + indicatorColor: new Color(0xFF3190E8), + indicatorSize: TabBarIndicatorSize.label, + labelStyle: new TextStyle( + fontSize: 16.0, + ), + controller: _tabController, + tabs: [ + Tab( + text: S.of(context).products, + ), + Tab( + child: Badge( + badgeContent: Text( + '${_business.commentsCount}', + style: TextStyle(color: Colors.white, fontSize: 11.0), + ), + badgeColor: Colors.lightBlueAccent, + child: Text(S.of(context).comments), + ), + ), + ], + ), + ), + ), + ), + SliverFillRemaining( + child: TabBarView( + controller: _tabController, + children: [ + _buildMainContent(), + _buildCommentList(), + ], + ), + ), +// _slidUpShoppingCart, + ], + ), + ); + + Widget mainBody = listener; + + Widget widget = Scaffold( + key: _scaffoldKey, + body: WillPopScope( + child: mainBody, + onWillPop: () async { + if (_animationFinish) { + return true; + } + return false; + }, + ), + ); + + List children = [ + Positioned( + top: 0.0, + right: 0.0, + bottom: 50.0, + left: 0.0, + child: widget, + ), + _slidUpShoppingCart, + ]; + + print("pointers: " + _animationPointManager.list.length.toString()); + children += _animationPointManager.list; + + Stack stack = Stack( + key: stackKey, + children: [], + ); + + stack.children.addAll(children); + + if (!_business.isPublic) { + stack.children.add( + Positioned( + top: 0, + right: 0, + left: screenWidth - 100, + bottom: screenHeight - 100, + child: Image.asset( + 'assets/images/under_renovation.png', + width: 200.0, + height: 200.0, + fit: BoxFit.fill, + ), + ), + ); + } + + return stack; + } + + SnackBar commentImageSnackBar(String url) { + return SnackBar( + elevation: 0.0, + action: SnackBarAction( + label: S.of(context).close, + onPressed: () { + _scaffoldKey.currentState.hideCurrentSnackBar(); + }, + ), + content: Container( + height: 260.0, + child: Util.showImage( + '$url', + width: 180.0, + height: 180.0, + fit: BoxFit.fill, + ), + ), + ); + } + + CupertinoActionSheet commentImageActionSheet(String url) { + return CupertinoActionSheet( + message: Container( + height: 280.0, + child: Util.showImage( + '$url', + fit: BoxFit.fill, + ), + ), + cancelButton: CupertinoActionSheetAction( + child: Text(S.of(context).close), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + + Widget _buildCommentList() { + Widget commentWidget = Center( + child: Text(S.of(context).no_comments_yet), + ); + if (comments != null && comments.length > 0) { + commentWidget = ListView.builder( + controller: _listScrollController3, + itemCount: comments.length, + itemBuilder: (BuildContext context, int position) { + Comment comment = comments[position]; + Row imageRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + if (comment.images.length > 0) { + for (ProductImage image in comment.images) { + imageRow.children.add( + GestureDetector( + child: Container( + padding: EdgeInsets.all(5.0), + child: Util.showImage( + 'https:${image.image}', + width: 40.0, + height: 40.0, + fit: BoxFit.fill, + ), + ), + onTap: () { +// _scaffoldKey.currentState.showSnackBar(commentImageSnackBar('https:${image.image}')); + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return commentImageActionSheet( + 'https:${image.image}'); + }); + }, + ), + ); + } + } + Widget replyWidget = SizedBox.shrink(); + if (comment.replyFromStore != null && + comment.replyFromStore.isNotEmpty) { + replyWidget = Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 10.0, bottom: 5.0), + child: Text( + S.of(context).response_from_store, + style: TextStyle( + fontSize: 12.0, + color: Colors.black38, + ), + ), + ), + Container( + padding: EdgeInsets.only(bottom: 16.0), + child: ReadMoreText( + comment.replyFromStore, + style: TextStyle( + fontSize: 13.0, + color: Colors.black54, + ), + trimLines: 2, + trimLength: 40, + colorClickableText: Colors.black, + trimMode: TrimMode.Line, + trimCollapsedText: S.of(context).show_more, + trimExpandedText: S.of(context).show_less, + ), + ) + ], + ), + ); + } + return Container( + padding: EdgeInsets.only( + left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(bottom: 10.0), + margin: EdgeInsets.only(bottom: 5.0), + width: double.infinity, + child: ReadMoreText( + comment.content, + trimLines: 3, + trimLength: 66, + colorClickableText: Colors.blue, + trimMode: TrimMode.Length, + trimCollapsedText: S.of(context).show_more, + trimExpandedText: S.of(context).show_less, + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: SmoothStarRating( + starCount: 5, + rating: comment.rating.toDouble(), + size: 12.0, + filledIconData: Icons.star, + color: Colors.green, + ), + ), + Container( + child: Text( + Utils.safeString(comment.contact) + + ' ' + + Utils.utcDatetimeStringToLocalDatetimeString( + context, comment.createdAt), + style: TextStyle( + fontSize: 12.0, + color: Colors.black26, + ), + ), + ), + ], + ), + ), + Container( + child: imageRow, + ), + replyWidget, + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 10.0, + color: Colors.black12, + ), + ), + ), + ); + }); + } + + return SmartRefresher( + enablePullUp: true, + enablePullDown: true, + header: WaterDropHeader(), + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode) { + Widget footer; + if (mode == LoadStatus.idle) { + footer = Text(S.of(context).pull_up_to_load_more); + } else if (mode == LoadStatus.loading) { + footer = CircularProgressIndicator(); + } else if (mode == LoadStatus.failed) { + footer = Text(S.of(context).load_failed_retry); + } else if (mode == LoadStatus.canLoading) { + footer = Text(S.of(context).release_to_load_more); + } else if (mode == LoadStatus.noMore) { + footer = Text(S.of(context).no_more_record); + } else { + footer = Text('...'); + } + return Container( + height: 55.0, + child: Center( + child: footer, + ), + ); + }, + ), + controller: _commentRefreshController, + onRefresh: _onCommentRefresh, + onLoading: _onCommentLoadMore, + child: commentWidget, + ); + } + + Widget _buildAppbar(BuildContext context) { + var addressRow = new Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + addressRow.children.add(new Icon( + Icons.location_on, + size: 14.0, + color: const Color(0xFFEEEEEE), + )); + var addressColumn = new Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ); + addressColumn.children.add(new Text( + _business.address.addressLine1 + + (_business.address.addressLine2.isNotEmpty + ? ' ' + _business.address.addressLine2 + : ''), + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + )); + addressColumn.children.add(new Text( + _business.address.city + + ', ' + + _business.address.state + + ', ' + + _business.address.zip, + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + )); + + addressRow.children.add(addressColumn); + + var distanceRow = new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + if (_business.distanceInfo != null) { + distanceRow.children.add(new Icon( + Icons.directions_car, + size: 14.0, + color: Colors.white, + )); + distanceRow.children.add(new Text( + _business.distanceInfo.distance != null + ? _business.distanceInfo.distance.text + : '***' + ' / ', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + var duration = Duration( + seconds: (_business.distanceInfo.duration != null + ? _business.distanceInfo.duration.value + : 30) + + _business.shippingTime * 60); + var hours = duration.inHours.remainder(60); + var minutes = duration.inMinutes.remainder(60); + distanceRow.children.add(new Text( + hours > 0 ? S.of(context).hour_token(hours) : '', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + distanceRow.children.add(new Text( + minutes > 0 + ? (hours > 0 ? ' ' : '') + S.of(context).minute_token(minutes) + : '', + style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0), + )); + distanceRow.children.add( + new Text( + ' / ' + + S.of(context).min_order_amount_token(_business.minPrice) + + ' ', + style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ), + ); + } + + Widget widget = new PreferredSize( + preferredSize: new Size.fromHeight(90.0), + child: new Container( + height: 90.0, + padding: new EdgeInsets.all(10.0), + child: new Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + new Container( + width: 72.0, + height: 72.0, + margin: new EdgeInsets.only(right: 10.0), + child: Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: 0.0, + child: GestureDetector( + child: Util.showImage( + '${_business.picUrl}', + width: 72.0, + height: 72.0, + fit: BoxFit.fill, + ), + onTap: () { + // Utils.addRemoveFavorite(_business.id); + }, + ), + ), + Positioned( + top: 0.0, + left: 0.0, + right: -50.0, + bottom: -50.0, + child: Icon( + Icons.favorite, + color: Colors.transparent, + ), + ), + ], + ), + ), + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + addressRow, + new Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon( + Icons.phone, + size: 14.0, + color: Colors.white, + ), + new Text( + _business.phone, + style: new TextStyle( + fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ) + ], + ), + new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Icon( + Icons.av_timer, + size: 14.0, + color: Colors.white, + ), + new Text( + _business.openingTime[0].openTime + + ':00 - ' + + _business.openingTime[0].closeTime + + ':00', + style: new TextStyle( + fontSize: 13.0, color: const Color(0xFFEEEEEE)), + ) + ], + ) + ], + ), + distanceRow, + ], + ), + ) + ], + ), + ), + ); + + List galleryPages = _buildBanners(context); + slidingGellery = Carousel( + height: 168.0, + pages: galleryPages, + autoPlay: true, + ); + + Widget column = new Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _business.bulletin.isNotEmpty ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: + EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.announcement, + size: 14.0, + color: Colors.white, + )), + Container( + width: mediaQuery.size.width - 30.0, + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + _business.bulletin.isEmpty ? '' : _business.bulletin, + softWrap: true, + style: new TextStyle( + fontSize: 12.0, color: const Color(0xFFDDDDDD)), +// overflow: TextOverflow.ellipsis, + ), + ), + ], + ) : SizedBox.shrink(), + _business.description.isNotEmpty ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.store, + size: 14.0, + color: Colors.white, + )), + Container( + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + S.of(context).store_introduction, + style: new TextStyle( + fontSize: 13.0, + color: const Color(0xFFDDDDDD), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Container( + padding: + EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0), + child: Text( + _business.description, + style: TextStyle( + color: Colors.lightGreen, + fontSize: 12.0, + ), + ), + ), + ], + ) : SizedBox.shrink(), + galleryPages.isNotEmpty ? Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), + child: slidingGellery, + ) : SizedBox.shrink(), + _business.policy.isNotEmpty ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0), + child: Icon( + Icons.store, + size: 14.0, + color: Colors.white, + )), + Container( + padding: EdgeInsets.only(right: 10.0, bottom: 10.0), + child: new Text( + S.of(context).store_policy, + style: new TextStyle( + fontSize: 13.0, + color: const Color(0xFFDDDDDD), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), + child: SingleChildScrollView( + child: Text( + _business.policy, + softWrap: true, + style: TextStyle( + fontSize: 10.0, + color: const Color(0xFFEEEEEE), + ), + ), + ), + ), + ], + ) : SizedBox.shrink(), + ], + ); + + return Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Util.showImage( + _business.bannerImageUrl, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: Container( + color: Color(0x88000000), + ), + ), + Positioned( + top: kToolbarHeight + 20.0, + left: 0, + right: 0, + child: widget, + ), + Positioned( + top: kToolbarHeight + 20.0 + 90.0, + left: 0, + right: 0, + bottom: 0, + child: Container( + child: SingleChildScrollView( + child: column, + ), + ), + ), + ], + ); + } + + List _buildBanners(BuildContext context) { + var pages = []; + for (var i = 0; i < _business.slideImages.length; i++) { + pages.add(new GestureDetector( + child: new Container( + child: Util.showImage( + _business.slideImages[i].imageUrl, + fit: BoxFit.cover, + ), + ), + onTap: () {}, + )); + } + return pages; + } + + Widget _buildMainContent() { + return new Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildCategories(), + Expanded( + child: _buildProducts(), + ) + ], + ); + } + + Widget _buildCategories() { + return new Container( + width: 100.0, + color: new Color(0xFFF5F5F5), + child: new SizedBox.expand( + child: new ListView.builder( + physics: ClampingScrollPhysics(), + controller: _listScrollController2, + itemCount: _categoryProducts == null ? 0 : _categoryProducts.length, + itemBuilder: (BuildContext context, int i) { + CategoryProducts cp = _categoryProducts[i]; + int qtyInCategory = 0; + CartInfo cartInfo = + Utils.getCartInfoByBusiness(store.state.cartInfos, _business); + if (cartInfo != null && + cartInfo.businessInfo.id == _business.id && + cartInfo.productList != null) { + for (var i = 0; i < cartInfo.productList.length; i++) { + if (cartInfo.productList[i].product.categoryId == cp.id) { + qtyInCategory += cartInfo.productList[i].quantity.round(); + } + } + } + Row categoryRow = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [], + ); + categoryRow.children.add( + Expanded( + flex: 1, + child: Container( + margin: EdgeInsets.only(top: 5.0, bottom: 5.0), + child: Text( + cp.name, + style: TextStyle( + fontSize: 13.0, + fontWeight: (cp.id == Constants.FEATURED_PRODUCT_ID || + cp.id == Constants.HOT_SALE_ID) + ? FontWeight.bold + : FontWeight.normal, + color: (cp.id == Constants.HOT_SALE_ID) + ? Colors.redAccent + : ((cp.id == Constants.FEATURED_PRODUCT_ID) + ? Colors.lightGreen + : Colors.black87)), +// overflow: kIsWeb ? null : TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 2, + ), + ), + ), + ); + if (qtyInCategory > 0) { + categoryRow.children.add( + new Badge( + badgeContent: Text( + '$qtyInCategory', + style: TextStyle( + color: Colors.white, + fontSize: 11.0, + ), + ), + animationType: BadgeAnimationType.scale, + ), + ); + } else { + categoryRow.children.add(SizedBox.shrink()); + } + return new GestureDetector( + child: new Container( + height: 70.0, + padding: new EdgeInsets.symmetric(horizontal: 10.0), + decoration: new BoxDecoration( + color: _categoryIndex == i ? Style.backgroundColor : null, + border: new Border( + bottom: + new BorderSide(color: new Color(0xFFEBEBEB)))), + child: categoryRow, + ), + onTap: () => _selectCategory(i), + ); + }), + ), + ); + } + + void _selectCategory(int index) { + if (displayProductByCategoryClick && _categoryProducts[index].id > 0) { + categoryId = _categoryProducts[index].id; + loadProducts(); + return; + } + double height = 0.0; + for (int i = 0; i < index; ++i) { + height += _categoryDescHeight + + _categoryProducts[i].products.length * _productHeight; + } + if (height > _listScrollController1.position.maxScrollExtent) { + height = _listScrollController1.position.maxScrollExtent; + } + _categoryIndexChange = true; + _listScrollController1 + .animateTo(height, + duration: new Duration( + microseconds: 200, + ), + curve: Curves.linear) + .then((value) { + _categoryIndexChange = false; + }); + print( + 'height: $height, index: $index, ${_categoryProducts[0].products.length}'); + if (mounted) { + setState(() { + _categoryIndex = index; + }); + } + } + + CategoryProducts getCategoryProductByCategoryId(int cid) { + for (CategoryProducts cp in _categoryProducts) { + if (cp.id == cid) { + return cp; + } + } + return null; + } + + void _resetProductListScroll() { + if (_listScrollController1 != null && + _listScrollController1.positions.isNotEmpty) { + _listScrollController1.animateTo( + 0, + duration: new Duration( + microseconds: 200, + ), + curve: Curves.linear, + ); + } + } + + int _getCategoryIndexByRightScrollHeight(double height) { + double cHeight = 0.0; + if (height > 0) { + for (int i = 0; i < _categoryProducts.length; ++i) { + double categoryHeight = _categoryDescHeight + + _categoryProducts[i].products.length * _productHeight; + if (height >= cHeight && height < cHeight + categoryHeight) { + return i; + } + cHeight += categoryHeight; + } + } + if (height > cHeight) { + return _categoryProducts.length - 1; + } + return -1; + } + + void _setCategoryByProductScrolledIndex(int index) { + if (index == _categoryIndex) { + return; + } + double height = 0.0; + for (int i = 0; i < index; ++i) { + height += _categoryHeight; + } + if (height > _listScrollController2.position.maxScrollExtent) { + height = _listScrollController2.position.maxScrollExtent; + } + _listScrollController2.animateTo(height, + duration: new Duration( + milliseconds: 200, + ), + curve: Curves.linear); + if (mounted) { + setState(() { + _categoryIndex = index; + }); + } + } + + int numCategoriesHasProducts() { + int num = 0; + for (CategoryProducts cp in _categoryProducts) { + if (cp.products.length > 0) { + num += 1; + } + } + return num; + } + + Widget _buildProducts() { + return new SizedBox.expand( + child: new NotificationListener( + onNotification: (ScrollUpdateNotification notification) { + if (!displayProductByCategoryClick) { + if (_categoryIndexChange) { + return; + } + int index = _getCategoryIndexByRightScrollHeight( + notification.metrics.pixels); + _setCategoryByProductScrolledIndex(index); + } + return; + }, + child: new ListView.builder( + physics: ClampingScrollPhysics(), + controller: _listScrollController1, + // itemCount: displayProductByCategoryClick ? 1 : (_categoryProducts == null ? 0 : _categoryProducts.length), + itemCount: + _categoryProducts == null ? 0 : numCategoriesHasProducts(), + itemBuilder: (BuildContext context, int i) { + CategoryProducts cp; + cp = _categoryProducts[i]; + + int index = -1; + if (displayProductByCategoryClick) { + if (categoryId > 0) { + for (var j = 0; j < _categoryProducts.length; j++) { + if (_categoryProducts[j].id == categoryId) { + cp = _categoryProducts[j]; + index = j; + break; + } + } + if (cp == null) { + index = 0; + cp = _categoryProducts[0]; + } + } + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _setCategoryByProductScrolledIndex(index); + }); + } + + if (cp.products.length == 0) { + return SizedBox.shrink(); + } + + Column col = new Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [], + ); + col.children.add(new Container( + height: _categoryDescHeight, + padding: new EdgeInsets.symmetric(horizontal: 10.0), + color: new Color(0xFFF5F5F5), + child: new Row( + children: [ + new Expanded( + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + textBaseline: TextBaseline.alphabetic, + children: [ + new Padding( + padding: new EdgeInsets.only(right: 5.0), + child: new Text( + cp.name, + style: new TextStyle( + fontSize: 16.0, + fontWeight: + (cp.id == Constants.FEATURED_PRODUCT_ID || + cp.id == Constants.HOT_SALE_ID) + ? FontWeight.bold + : FontWeight.normal, + color: (cp.id == Constants.HOT_SALE_ID) + ? Colors.redAccent + : ((cp.id == + Constants.FEATURED_PRODUCT_ID) + ? Colors.lightGreen + : Colors.black87)), + overflow: TextOverflow.ellipsis, + ), + ), + new Visibility( + visible: cp.description.isNotEmpty, + child: new Text( + cp.description, + overflow: TextOverflow.ellipsis, + style: new TextStyle( + fontSize: 10.0, + color: new Color(0xFF999999)), + ), + replacement: const SizedBox.shrink()), + ], + )) + ], + ))); + for (var p in cp.products) { + var pStack = new Stack( + children: [], + ); + + pStack.children.add(new ProductItem( + product: p, + business: _business, + )); + + col.children.add(pStack); + } + if (displayProductByCategoryClick) { + if (displayProductByCategoryClickIndicator.isNotEmpty) { + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + displayProductByCategoryClickIndicator, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } else { + if (cp.products.length < Constants.ORDERS_PER_PAGE) { + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + S.of(context).end_of_the_list, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } else { + col.children.add( + Container( + padding: EdgeInsets.all(12.0), + child: Center( + child: Text( + S.of(context).pull_up_to_load_more, + style: TextStyle( + color: Colors.black54, + ), + ), + ), + ), + ); + } + } + } + return col; + }), + ), + ); + } + + @override + void initState() { + super.initState(); + + _shopCoordinator = ShopScrollCoordinator(); + _tabController = TabController(vsync: this, length: 2); + + refresh = false; + + _listScrollController1 = _shopCoordinator.newChildScrollController(); + _listScrollController2 = _shopCoordinator.newChildScrollController(); + _listScrollController3 = _shopCoordinator.newChildScrollController(); + + _listScrollController1.addListener(() { + if (_listScrollController1.position.atEdge) { + if (_listScrollController1.position.pixels == 0) { + print('product list at top'); + } else { + print('product list at bottom'); + if (displayProductByCategoryClick && _productCurrentPage != 0) { + loadMoreProducts(); + } + } + } + }); + + eventBus.on().listen((event) { + if (mounted) { + setState(() { + refresh = true; + }); + } + }); + + // onProductWillAddToCartSubscription = + // eventBus.on().listen((event) { + // if (mounted) { + // itemAddToCart(event.buttonKey); + // + // CartInfo cartInfo = + // Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + // + // if (cartInfo == null || cartInfo.productList.length == 0) { + // cartInfo = CartInfo(); + // cartInfo.id = 0; + // cartInfo.amountPaid = 0.0; + // cartInfo.businessInfo = event.business; + // cartInfo.extraFeeList = []; + // cartInfo.discountList = []; + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList = [lineItem]; + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // if (event.product.productAttributes.length > 0) { + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList.add(lineItem); + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // int found = -1; + // for (var i = 0; i < cartInfo.productList.length; i++) { + // if (event.product.id == cartInfo.productList[i].product.id) { + // found = i; + // break; + // } + // } + // if (found == -1) { + // CartLineItem lineItem = _newCartLineItem( + // id: 0, + // price: event.price, + // product: event.product, + // name: event.product.name, + // description: event.description, + // quantity: 1.0); + // cartInfo.productList.add(lineItem); + // store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } else { + // if (cartInfo.productList[found].quantity + 1.0 > + // event.product.leftNum) { + // Fluttertoast.showToast( + // msg: S.of(context).product_insufficient, + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // backgroundColor: Colors.red, + // textColor: Colors.white); + // } else { + // cartInfo.productList[found].quantity += 1; + // store.dispatch(new UpdateCartInfo( + // Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // eventBus.fire(new OnCartInfoUpdated()); + // } + // } + // } + // } + // } + // }); + // onProductWillRemoveFromCartSubscription = + // eventBus.on().listen((event) { + // if (mounted) { + // CartInfo cartInfo = + // Utils.getCartInfoByBusiness(store.state.cartInfos, event.business); + // if (cartInfo != null) { + // if (cartInfo.productList.length > 0) { + // if (event.productListIndex != -1) { + // if (cartInfo.productList[event.productListIndex].quantity <= 1) { + // cartInfo.productList.removeAt(event.productListIndex); + // } else { + // cartInfo.productList[event.productListIndex].quantity -= 1; + // } + // } else { + // int productListIndex = -1; + // for (var i = 0; i < cartInfo.productList.length; i++) { + // if (cartInfo.productList[i].product.id == event.product.id) { + // productListIndex = i; + // break; + // } + // } + // if (productListIndex != -1) { + // if (cartInfo.productList[productListIndex].quantity <= 1) { + // cartInfo.productList.removeAt(productListIndex); + // } else { + // cartInfo.productList[productListIndex].quantity -= 1; + // } + // } + // } + // } + // + // if (cartInfo.productList.length <= 0) { + // store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList( + // store.state.cartInfos, cartInfo))); + // } else { + // store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList( + // store.state.cartInfos, cartInfo))); + // } + // eventBus.fire(new OnCartInfoUpdated()); + // } + // } + // }); + + loadProducts(); + } + + void loadProducts() { + if (categoryId < 0) { + return; + } + if (_categoryProducts != null) { + _isLoading = true; + Utils.showLoadingDialog(context, message: S.of(context).loading); + } + _productCurrentPage = 1; + Utils.loadProducts( + widget.businessId, categoryId, _productCurrentPage, false, + (value) { + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + if (mounted) { + setState(() { + displayProductByCategoryClick = + value['display_product_by_category_click']; + displayProductByCategoryClickIndicator = ''; + _business = Business.fromJson(value['business']); + _categoryProducts = (value['category_products'] as List) + .map((i) => CategoryProducts.fromJson(i)) + .toList(); + _featuredProducts = (value['featured_products'] as List) + .map((i) => Product.fromJson(i)) + .toList(); + _hotSaleProducts = (value['hot_sale_products'] as List) + .map((i) => Product.fromJson(i)) + .toList(); + _prompts = value['prompt'] as List; + + if (_hotSaleProducts.length > 0) { + CategoryProducts hs = CategoryProducts( + Constants.HOT_SALE_ID, + widget.businessId, + S.of(context).hot_sale, + '', + '', + _hotSaleProducts); + _categoryProducts.insert(0, hs); + } + + if (_featuredProducts.length > 0) { + CategoryProducts fe = CategoryProducts( + Constants.FEATURED_PRODUCT_ID, + widget.businessId, + S.of(context).featured_product, + '', + '', + _featuredProducts); + _categoryProducts.insert(0, fe); + } + + checkActionAndClose(context); + + if (displayProductByCategoryClick) { + _resetProductListScroll(); + } + }); + } + }, + (error) { + print('error: $error'); + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + } + ); + } + + void loadMoreProducts() { + if (_productCurrentPage == 0 || categoryId <= 0) { + return; + } + _productCurrentPage += 1; + Utils.loadProducts(widget.businessId, categoryId, _productCurrentPage, true, + (value) { + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + if (mounted) { + setState(() { + List moreCategoryProducts = + (value as List).map((i) => CategoryProducts.fromJson(i)).toList(); + if (moreCategoryProducts.isEmpty) { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } else { + if (moreCategoryProducts[0].products.length < Constants.ORDERS_PER_PAGE) { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } else { + displayProductByCategoryClickIndicator = + S.of(context).pull_up_to_load_more; + } + CategoryProducts currentCp = + getCategoryProductByCategoryId(categoryId); + if (currentCp != null) { + currentCp.products.addAll(moreCategoryProducts[0].products); + } else { + _productCurrentPage = 0; + displayProductByCategoryClickIndicator = + S.of(context).end_of_the_list; + } + } + }); + } + }, + (error) { + print('error: $error'); + if (_isLoading) { + _isLoading = false; + Navigator.of(context).pop(); + } + Utils.showMessageDialog(context, error); + } + ); + } + + CartLineItem _newCartLineItem( + {int id, + double price, + Product product, + String name, + String description, + double quantity}) { + CartLineItem lineItem = CartLineItem(); + lineItem.unitPrice = price; + lineItem.product = product; + lineItem.name = product.name; + lineItem.description = description; + lineItem.quantity = quantity; + return lineItem; + } + + @override + void dispose() { + print('Shop disposed!!!'); + // onProductWillAddToCartSubscription?.cancel(); + // onProductWillRemoveFromCartSubscription?.cancel(); + _tabController?.dispose(); + _pageScrollController?.dispose(); + _commentRefreshController?.dispose(); + _listScrollController1?.dispose(); + _listScrollController2?.dispose(); + _listScrollController3?.dispose(); + _animationPointManager?.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + void _showProductDetail(Product p) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDetailPage( + product: p, + business: _business, + )), + ); + } + + void checkActionAndClose(BuildContext mainContext) { + // if (widget.action == Constants.ACTION_COUPON && store.state.referrerContactId != null && store.state.referrerContactId > 0) { + // showDialog( + // context: context, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text(S.of(context).coupon), + // content: Text( + // S.of(context).a_friend_send_you_a_coupon + // ), + // actions: [ + // FlatButton( + // child: Text(S.of(context).no_thanks), + // onPressed: () { + // Navigator.of(context).pop(); + // checkClose(mainContext, _business); + // }, + // ), + // RaisedButton( + // child: Text( + // S.of(context).yes, + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // color: Theme.of(context).primaryColor, + // onPressed: () { + // Utils.showLoadingDialog(mainContext, message: S.of(context).processing); + // HttpUtil.httpGet('v1/get-referrer-coupon', + // businessId: _business.id, + // queryParameters: { + // 'referrer_contact_id': store.state.referrerContactId, + // } + // ).then((data) { + // Navigator.of(context).pop(); + // Coupon coupon = Coupon.fromJson(data); + // showDialog( + // context: mainContext, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text( + // S.of(context).congratulation, + // ), + // content: Text( + // S.of(context).got_a_coupon_token(coupon.valueAmount) + // ), + // actions: [ + // FlatButton( + // child: Text( + // S.of(context).close, + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // Navigator.of(context).pop(); + // }, + // ), + // ], + // ); + // } + // ); + // }).catchError((error) { + // Navigator.of(context).pop(); + // Navigator.of(context).pop(); + // if (error is DioError) { + // if (error.response.data['code'] == 1013) { + // showDialog( + // context: mainContext, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text(S.of(context).error), + // content: Text(error.response.data['message']), + // actions: [ + // FlatButton( + // child: Text(S.of(context).cancel), + // onPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // RaisedButton( + // color: Theme.of(context).primaryColor, + // child: Text( + // S.of(context).login, + // style: TextStyle( + // color: Colors.white, + // ), + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // store.dispatch(UpdateRedirectRoute('/shop/${widget.businessId}/${widget.deviceId}/${widget.tableNo}/${widget.action}')); + // Routes.router.navigateTo(context, '/login'); + // }, + // ), + // ], + // ); + // } + // ); + // } else { + // Utils.showMessageDialog(mainContext, error); + // } + // } else { + // Utils.showMessageDialog(mainContext, error); + // } + // }); + // }, + // ) + // ], + // ); + // } + // ); + // } else { + // checkClose(context, _business); + // } + } + + void checkClose(BuildContext context, Business business) { + if (checkCloseFlag) { + return; + } + checkCloseFlag = true; + if (business.forceClose) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text( + S.of(context).store_closed, + ), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(0); + }, + ) + ], + ); + }); + } else { + DateTime now = DateTime.now(); + if (!(now.hour >= business.todayOpen && + now.hour <= business.todayClose)) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( +// title: Text( +// S.of(context).warning, +// ), + content: Container( + height: 170.0, + child: Center( + child: Column( + children: [ + Container( + child: Icon( + Icons.add_shopping_cart, + size: 100.0, + color: Colors.black12, + ), + ), + Container( + child: Text( + S.of(context).closed, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: Text( + S.of(context).book_now_delivery_later_token( + business.todayOpen), + style: TextStyle( + color: Colors.black38, + ), + ), + ), + ], + ), + ), + ), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(0); + }, + ) + ], + ); + }); + } else { + showPrompt(0); + } + } + } + + void showPrompt(int idx) { + if (_prompts.length > idx) { + Map _prompt = _prompts[idx] as Map; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(_prompt['title']), + content: Text(_prompt['message']), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + showPrompt(idx + 1); + }, + ), + ], + ); + }); + } + } + + @override + bool get wantKeepAlive => true; +} + +class _SliverAppBarDelegate2 extends SliverPersistentHeaderDelegate { + _SliverAppBarDelegate2({ + @required this.minHeight, + @required this.maxHeight, + @required this.child, + }); + + final double minHeight; + final double maxHeight; + final Widget child; + + @override + double get minExtent => this.minHeight; + + @override + double get maxExtent => max(maxHeight, minHeight); + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return SizedBox.expand(child: child); + } + + @override + bool shouldRebuild(_SliverAppBarDelegate2 oldDelegate) { + return maxHeight != oldDelegate.maxHeight || + minHeight != oldDelegate.minHeight || + child != oldDelegate.child; + } +} diff --git a/lib/widgets/mobile/mobile_store_product_search.dart b/lib/widgets/mobile/mobile_store_product_search.dart new file mode 100644 index 0000000..8cd3769 --- /dev/null +++ b/lib/widgets/mobile/mobile_store_product_search.dart @@ -0,0 +1,144 @@ + +import 'package:dio/dio.dart'; +import 'package:flappy_search_bar/flappy_search_bar.dart'; +import 'package:flutter/material.dart'; + +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/product.dart'; +import '../../pages/product_detail_page.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../widgets/mobile/mobile_product_item.dart'; + +class MobileStoreProductSearch extends StatefulWidget { + final Key key; + final Business business; + + const MobileStoreProductSearch(this.business, {this.key}); + + @override + State createState() { + return MobileStoreProductSearchState(); + } + +} + +class MobileStoreProductSearchState extends State { + + int page = 1; + int numPerPage = 10; + int lastResultSize = 0; + double lastBottomPosition = 0; + String lastKeyword = ''; + + SearchBarController _controller = SearchBarController(); + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: NotificationListener( + child: SearchBar( + searchBarController: _controller, + minimumChars: 2, + hintText: S.of(context).enter_product_keyword, + cancellationWidget: Text( + S.of(context).cancel, + ), + onSearch: search, + onItemFound: (Product searchProduct, int index) { + return MobileProductItem( + product: searchProduct, + business: widget.business, + ); + }, + onError: (error) { + return Center( + child: Text("$error"), + ); + }, + emptyWidget: Center( + child: Text( + S.of(context).empty_result_change_keyword, + ), + ), + ), + onNotification: (notification) { + if (notification.metrics.atEdge) { + print('fff: $page, $lastBottomPosition, ${notification.metrics.pixels}'); + if (page != 0 && lastResultSize >= numPerPage && notification.metrics.pixels != 0) { + if (notification.metrics.pixels > lastBottomPosition) { + lastBottomPosition = notification.metrics.pixels; + page += 1; + _controller.replayLastSearch(); + } + } + } + return true; + }, + ), + ), + ); + } + + Future> search(String keyword) async { + if (lastKeyword != keyword) { + page = 1; + } + lastKeyword = keyword; + var result = await HttpUtil.httpGet('v1/search-store-product2', + queryParameters: { + 'store_id': widget.business.id, + 'keyword': keyword, + 'page': page, + 'num_per_page': numPerPage, + }, + returnError: true, + ); + if (result is DioError) { + if (result.response != null) { + throw RuntimeError(result.response.data); + } else { + throw RuntimeError(result.message); + } + } else if (result != null && result is List) { + lastBottomPosition = 0; + var r = result.map((e) => Product.fromJson(e)).toList(); + lastResultSize = r.length; + if (lastResultSize < numPerPage) { + page = 1; + } + return r; + } + return []; + } + + @override + void initState() { + super.initState(); + eventBus.on().listen((event) { + if (mounted) { + setState(() {}); + } + }); + } + + void _showProductDetail(Product p) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProductDetailPage( + product: p, + business: widget.business, + )), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_stripe_pay_web.dart b/lib/widgets/mobile/mobile_stripe_pay_web.dart new file mode 100644 index 0000000..22dad17 --- /dev/null +++ b/lib/widgets/mobile/mobile_stripe_pay_web.dart @@ -0,0 +1,221 @@ + + +import 'package:flutter/material.dart'; +import 'package:stripe_sdk/stripe_sdk.dart'; +import 'package:stripe_sdk/stripe_sdk_ui.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/order.dart'; +import '../../models/payment_platform.dart'; +import '../../models/stripe_payment_method.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; + + +class MobileStripePayWeb extends StatefulWidget { + final Key key; + final Order order; + final PaymentPlatform paymentPlatform; + final StripePaymentMethod stripePaymentMethod; + const MobileStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod}); + + @override + State createState() { + return MobileStripePayWebState(); + } + +} + +class MobileStripePayWebState extends State { + + GlobalKey _scaffoldKey = GlobalKey(); + final formKey = GlobalKey(); + final card = StripeCard(); + + CardForm form; + + bool isSubmitting; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + Widget body = Center( + child: Icon( + Icons.credit_card, + size: 40.0, + color: Colors.black26, + ), + ); + if (widget.stripePaymentMethod == null) { + form = CardForm(card: card, formKey: formKey,); + body = ListView( + children: [form,], + ); + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (widget.stripePaymentMethod != null) { + _paymentWithPaymentMethod(context); + } + }); + + return Scaffold( + key: _scaffoldKey, + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + title: Text(S.of(context).add_credit_card), + backgroundColor: Theme.of(context).primaryColor, + actions: [ + IconButton( + icon: Icon(Icons.check), + onPressed: widget.stripePaymentMethod == null ? () { + if (formKey.currentState.validate()) { + formKey.currentState.save(); + _paymentRequestWithCard(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + messageSnackBar( + context, S.of(context).this_credit_card_is_invalid + ) + ); + } + } : null, + ), + ], + ), + body: body, + ); + } + + @override + void initState() { + super.initState(); + isSubmitting = false; + StripeApi.init(widget.paymentPlatform.publishableKey); + } + + SnackBar messageSnackBar(BuildContext context, String message) { + Column column = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text( + message, + style: TextStyle( + color: Colors.white, + ), + )], + ); + return SnackBar( + content: Container( + height: 45.0, + child: column, + ), + action: SnackBarAction( + label: S.of(context).ok, + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + }, + ), + ); + } + + _paymentWithPaymentMethod(BuildContext context) async { + Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId, + widget.stripePaymentMethod.paymentMethodId, + widget.stripePaymentMethod.paymentMethodType, (response) async { + + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + await StripeApi.instance.confirmPaymentIntent( + response.data[Constants.STRIPE_CLIENT_SECRET], + data: { + 'payment_method': response.data['payment_method'], + }, + ).then((result2) { + if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + widget.stripePaymentMethod.paymentMethodId, + result2['id'], (response) { + if (isSubmitting) { + Navigator.of(context).pop(); + } + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog)); + + isSubmitting = true; + Utils.showSubmitDialog(context); + } + + _paymentRequestWithCard(BuildContext context) async { + isSubmitting = true; + Utils.showSubmitDialog(context); + await StripeApi.instance.createPaymentMethodFromCard(card) + .then((result) { + Utils.stripePaymentIntent(widget.order, null, result['id'], result['type'], (response) async { + if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) { + await StripeApi.instance.confirmPaymentIntent( + response.data[Constants.STRIPE_CLIENT_SECRET], + data: { + 'payment_method': response.data['payment_method'], + } + ).then((result2) { + if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) { + Utils.stripeChargedSuccess(widget.order, + result['id'], // payment method id + result2['id'], (response) { // payment intent id + if (isSubmitting) { + Navigator.of(context).pop(); + } + eventBus.fire(OnOrderUpdated()); + Routes.router.navigateTo(context, '/orderdetail/${widget + .order.id}', replace: true); + }, + (showErrorDialog) + ); + } else { + showErrorDialog(Exception('Unknown error')); + } + }).catchError(showErrorDialog); + } + }, (showErrorDialog), + cardBrand: result['card']['brand'], + cardCountry: result['card']['country'], + cardExpMonth: result['card']['exp_month'], + cardExpYear: result['card']['exp_year'], + cardFunding: result['card']['funding'], + cardLast4: result['card']['last4'], + ); + }).catchError(showErrorDialog); + } + + void showErrorDialog(dynamic error) { + if (isSubmitting) { + Navigator.of(context).pop(); + isSubmitting = false; + } + Utils.showMessageDialog(context, error, onOk: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_tutorials.dart b/lib/widgets/mobile/mobile_tutorials.dart new file mode 100644 index 0000000..f6f6017 --- /dev/null +++ b/lib/widgets/mobile/mobile_tutorials.dart @@ -0,0 +1,92 @@ + +import '../../constants.dart'; +import '../../utils/iframe_web.dart' if (dart.library.io) '../../utils/fake_iframe_web.dart'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +class MobileTutorials extends StatefulWidget { + const MobileTutorials({Key key}) : super(key: key); + + @override + State createState() { + return MobileTutorialsState(); + } + +} + +class MobileTutorialsState extends State { + InAppWebViewController webView; + String url = ""; + double progress = 0; + bool isLoadStop = false; + + @override + Widget build(BuildContext context) { + if (kIsWeb) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: IFrameWeb( + width: double.maxFinite.toString(), + height: double.maxFinite.toString(), + src: Constants.TUTORIAL_URL, + ), + ), + ], + ); + } else { + print('progress: $progress'); + return Stack( + children: [ + InAppWebView( + initialUrlRequest: URLRequest(url: Uri.parse(Constants.TUTORIAL_URL)), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + + ), + ), + onWebViewCreated: (InAppWebViewController controller) { + webView = controller; + }, + onLoadStart: (controller, url) { + if (mounted) { + setState(() { + this.url = url.toString(); + this.isLoadStop = false; + }); + } + }, + onLoadStop: (controller, url) async { + if (mounted) { + setState(() { + this.url = url.toString(); + this.isLoadStop = true; + }); + } + }, + onProgressChanged: (InAppWebViewController controller, int progress) { + if (mounted) { + setState(() { + if (this.progress < 1.0) { + this.isLoadStop = false; + } else { + this.isLoadStop = true; + } + this.progress = progress / 100; + }); + } + }, + ), + isLoadStop ? Container() : + Center( + child: CircularProgressIndicator(), + ), + ], + ); + } + } + +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_user_profile.dart b/lib/widgets/mobile/mobile_user_profile.dart new file mode 100644 index 0000000..b9d0769 --- /dev/null +++ b/lib/widgets/mobile/mobile_user_profile.dart @@ -0,0 +1,447 @@ + +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; + +import '../../constants.dart'; +import '../../events/eventbus.dart'; +import '../../events/events.dart'; +import '../../generated/l10n.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; +import '../../utils/utils.dart'; + +class MobileUserProfile extends StatefulWidget { + final Key key; + + const MobileUserProfile({this.key}) + : super(key: key); + + @override + State createState() { + return MobileUserProfileState(); + } +} + +class MobileUserProfileState extends State { + User _user; + + bool _showProgress; + double _progress; + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + return ListView( + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 10.0), + child: Text( + S.of(context).basic_info, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey + ), + ), + ), + Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: -85.0, + child: Visibility( + visible: _showProgress, + child: LinearPercentIndicator( + lineHeight: 10.0, + percent: _progress, + backgroundColor: Colors.transparent, + progressColor: Colors.blue, + linearStrokeCap: LinearStrokeCap.butt, + ), + ), + ), + Positioned( + child: GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).avatar, + style: TextStyle( + + ), + ), + ), + Container( + child: Util.showImage('https:${_user.avatarUrl}', + width: 60, + height: 60, + fit: BoxFit.fill, + errorWidget: (context, url, error) => Icon( + Icons.account_circle, + size: 60.0, + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + _getAvatar(context); + }, + ), + ), + ], + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).nick_name, + style: TextStyle( + + ), + ), + ), + Container( + child: Text( + _user.nickname, + style: TextStyle( + color: Colors.grey + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + _changeNickname(context); + }, + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Text( + S.of(context).my_addresses, + style: TextStyle( + + ), + ), + ), + Container( + child: Icon( + Icons.arrow_forward_ios, + size: 14.0, + color: Colors.grey, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/my-addresses/-1'); + }, + ), + Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 36.0, bottom: 10.0), + child: Text( + S.of(context).account_binding, + style: TextStyle( + fontSize: 12.0, + color: Colors.grey, + ), + ), + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.phone_iphone, + size: 16.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).mobile_number, + style: TextStyle( + + ), + ), + ) + ], + ), + ), + Container( + child: Text( + _user.mobile != null && _user.mobile.isNotEmpty ? Utils.safePhoneNumber(_user.mobile) : S.of(context).not_binding, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/change-mobile-email/1'); + }, + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Icon( + Icons.mail_outline, + size: 16.0, + ), + ), + Container( + margin: EdgeInsets.only(left: 5.0), + child: Text( + S.of(context).email, + style: TextStyle( + + ), + ), + ) + ], + ), + ), + Container( + child: Text( + _user.email != null && _user.email.isNotEmpty ? Utils.safePhoneNumber(_user.email) : S.of(context).not_binding, + style: TextStyle( + color: Colors.grey, + ), + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black12, + width: 1.0, + ), + ), + ), + ), + onTap: () { + Routes.router.navigateTo(context, '/change-mobile-email/0'); + }, + ), + ], + ); + } + + @override + void initState() { + super.initState(); + if (store.state.user == null) { + Routes.router.navigateTo(context, '/login', replace: true); + } else { + print(store.state.user.toString()); + setState(() { + _user = store.state.user; + _showProgress = false; + _progress = 0.0; + }); + } + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _user = store.state.user; + }); + } + }); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + _showProgress = event.showProgress; + _progress = event.progress; + }); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + void _getAvatar(BuildContext mainContext) { + showDialog( + context: mainContext, + barrierDismissible: true, + builder: (BuildContext context) { + return Util().getPicture(mainContext, _user); + } + ); + } + + void _updateCurrentUser() { + store.dispatch(new UpdateCurrentUser(_user)); + eventBus.fire(new OnCurrentUserUpdated()); + } + + void _changeNickname(BuildContext context) { + final GlobalKey _formKey = GlobalKey(); + final nicknameController = TextEditingController(); + + nicknameController.text = _user.nickname; + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).change_nickname, + ), + content: Container( + child: Form( + key: _formKey, + child: TextFormField( + controller: nicknameController, + decoration: new InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.black12, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + ), + ), + labelText: S.of(context).enter_new_nickname, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: true, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).nickname_is_required; + } + return null; + }, + ), + ), + ), + actions: [ + FlatButton( + child: Text( + S.of(context).cancel + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + S.of(context).submit_to_change, + ), + onPressed: () { + final FormState form = _formKey.currentState; + if (form.validate()) { + Utils.getBox().then((box) { + int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0); + HttpUtil.httpPut('v1/users/$userId', (response) { + Navigator.of(context).pop(); + if (mounted) { + setState(() { + _user = User.fromJson(response.data); + }); + _updateCurrentUser(); + } + }, + body: { + 'nickname': nicknameController.text + }, + ); + }).catchError((error){ + Navigator.of(context).pop(); + Routes.router.navigateTo(context, '/login'); + }); + } + }, + ) + ], + ); + } + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_view_blog.dart b/lib/widgets/mobile/mobile_view_blog.dart new file mode 100644 index 0000000..37df121 --- /dev/null +++ b/lib/widgets/mobile/mobile_view_blog.dart @@ -0,0 +1,151 @@ + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:photo_view/photo_view.dart'; + +import '../../constants.dart'; +import '../../models/blog.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; + +class MobileViewBlog extends StatefulWidget { + final Key key; + final int bid; + + const MobileViewBlog(this.bid, {this.key}) : super(key: key); + + @override + State createState() { + return MobileViewBlogState(); + } +} + +class MobileViewBlogState extends State { + Blog blog; + + @override + void initState() { + super.initState(); + _loadBlog(); + } + + void _loadBlog() { + HttpUtil.httpGet( + 'v1/blog/${widget.bid}', + businessId: Constants.BUSINESS_ID, + ).then((value) { + if (mounted) { + setState(() { + blog = Blog.fromJson(value); + print('blog: $blog'); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + BuildContext mainContext = context; + + if (blog == null) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + Column view = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Text( + '${blog.title}', + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ) + ], + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0), + child: Text( + '${blog.body}', + style: TextStyle( + color: Colors.black87, + fontSize: 17.0, + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0, right: 16.0), + child: (blog.imageUrl != null) ? + Container( + width: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0, + height: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0, + child: PhotoView( + imageProvider: NetworkImage( + 'https:${blog.imageUrl}', + ), + ), + ) : SizedBox.shrink(), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black45, + ), + ), + ), + ), + ], + ); + + return SingleChildScrollView( + child: view, + ); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/mobile_view_ticket.dart b/lib/widgets/mobile/mobile_view_ticket.dart new file mode 100644 index 0000000..6042162 --- /dev/null +++ b/lib/widgets/mobile/mobile_view_ticket.dart @@ -0,0 +1,706 @@ + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dash/flutter_dash.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_wisetronic/dialog/image_viewer.dart'; +import 'package:flutter_wisetronic/events/eventbus.dart'; +import 'package:flutter_wisetronic/events/events.dart'; +import 'package:flutter_wisetronic/widgets/general/text_link.dart'; +import 'package:percent_indicator/percent_indicator.dart'; + +import '../../constants.dart'; +import '../../generated/l10n.dart'; +import '../../models/ticket.dart'; +import '../../models/user.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/http_util.dart'; +import '../../utils/utils.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +class MobileViewTicket extends StatefulWidget { + final Key key; + final int ticketId; + + const MobileViewTicket(this.ticketId, {this.key}) : super(key: key); + + @override + State createState() { + return MobileViewTicketState(); + } +} + +class MobileViewTicketState extends State { + final GlobalKey _formKey = GlobalKey(); + + Ticket ticket; + + final issueMsgController = TextEditingController(); + + bool onSubmitting = false; + double submitProgress = 0.0; + + final List> galleryImagesFlag = [ + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + {'show': false, 'progress': 0.0, 'path': ''}, + ]; + + @override + void initState() { + super.initState(); + onSubmitting = false; + _loadTicket(); + } + + void _loadTicket() { + HttpUtil.httpGet( + 'v1/get-ticket/${widget.ticketId}', + businessId: Constants.BUSINESS_ID, + + ).then((value) { + if (mounted) { + setState(() { + ticket = Ticket.fromJson(value); + print('ticket: $ticket'); + }); + } + }).catchError((error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }); + } + + @override + Widget build(BuildContext context) { + store.dispatch(UpdateContext(context)); + + BuildContext mainContext = context; + + if (ticket == null) { + return Container( + padding: EdgeInsets.all(50.0), + child: Center( + child: SpinKitThreeBounce( + color: Colors.lightBlueAccent, + size: 40.0, + ), + ), + ); + } + + Column view = Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + S.of(context).ticket_number_token(ticket.id), + style: TextStyle( + fontSize: 24.0, + color: Colors.black + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ) + ], + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0), + child: Text( + '${ticket.issue.msg}', + style: TextStyle( + color: Colors.black54, + fontSize: 14.0, + ), + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0, right: 16.0), + child: showGalleryImages(mainContext, ticket.issue.files), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black45, + ), + ), + ), + ), + Container( + width: double.maxFinite, + height: 10.0, + padding: EdgeInsets.only(bottom: 10.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 8, + color: Colors.black26, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.white70, + ), + ), + ), + ), + ], + ); + + if (ticket.followUps.length > 0) { + view.children.add( + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: Text( + S.of(context).follow_ups, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + for (int i = 0; i < ticket.followUps.length; i++) { + FollowUp followUp = ticket.followUps[i]; + view.children.add( + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${followUp.poster}', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: Colors.black38, + ), + ), + Text( + Utils.utcDatetimeStringToLocalDatetimeString( + context, + followUp.createdAt + ), + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: Text( + '${followUp.msg}', + style: TextStyle( + color: Colors.black87, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 20.0), + child: showGalleryImages( + mainContext, followUp.files, + ), + ), + ], + ), + ), + ); + } + } + + Widget f = Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: Text( + S.of(context).your_reply, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0), + child: TextFormField( + controller: issueMsgController, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + decoration: new InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 1.0, + color: Colors.blue, + ), + ), + labelText: S.of(context).your_reply, + ), + style: TextStyle( + fontSize: 18.0 + ), + autofocus: false, + validator: (String value) { + if (value.trim().isEmpty) { + return S.of(context).this_field_is_required; + } + return null; + }, + maxLines: 5, + maxLength: 1000, + ), + ), + Container( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0), + child: Text( + S.of(context).attach_pictures, + style: TextStyle( + fontSize: 17.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0), + child: Text( + S.of(context).attach_pictures_desc, + style: TextStyle( + fontSize: 14.0, + color: Colors.black38, + ), + ), + ), + Container( + padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0), + child: galleryImages(mainContext), + ), + ], + ), + ); + + Widget s = Align( + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0), + child: RaisedButton( + color: Theme.of(context).primaryColor, + child: Text( + S.of(context).reply, + style: TextStyle( + color: Colors.white, + ), + ), + onPressed: () { + _submit(); + }, + ), + ), + ); + + if (ticket.followUps.length > 0) { + view.children.add( + Container( + width: double.maxFinite, + height: 10.0, + padding: EdgeInsets.only(bottom: 10.0), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 8, + color: Colors.black26, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.white70, + ), + ), + ), + ), + ); + } + + if (ticket.isClosed) { + view.children.add( + Container( + padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), + child: Text( + S.of(context).the_ticket_is_closed_desc, + style: TextStyle( + fontSize: 15.0, + color: Colors.black87, + ), + ), + ), + ); + view.children.add( + Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 0.0, bottom: 30.0), + child: TextLink( + S.of(context).new_ticket, + '/new-ticket/${ticket.store.id}', + ), + ), + ); + } else { + view.children.add( + f, + ); + view.children.add( + s, + ); + } + + return SingleChildScrollView( + child: Stack( + children: [ + Visibility( + visible: onSubmitting, + child: Container( + height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.bottom - MediaQuery.of(context).padding.top, + color: Colors.grey.withOpacity(0.6), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 10, + backgroundColor: Colors.green, + valueColor: new AlwaysStoppedAnimation(Colors.red), + value: submitProgress, + ), + Container( + margin: EdgeInsets.only(top: 16.0), + child: Text( + S.of(context).submitting_please_wait, + style: TextStyle( + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ), + Visibility( + visible: !onSubmitting, + child: view, + ), + ], + ), + ); + } + + Widget showGalleryImages(BuildContext mainContext, List files) { + if (files.length <= 0) { + return SizedBox.shrink(); + } + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < files.length; i++) { + TicketFile image = files[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + 'https:${image.url}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${image.fileName}'), + content: ImageViewer( + NetworkImage( + 'https:${image.url}', + ), + ), + actions: [ + TextButton( + onPressed: () { + Routes.router.pop(context); + }, + child: Text(S.of(context).close) + ), + ], + ); + }, + ); + }, + ); + + row.children.add(imageWidget); + } + return row; + } + + Widget galleryImages(BuildContext mainContext) { + Row row = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + for (int i = 0; i < 3; i++) { + Map image = galleryImagesFlag[i]; + Widget imageWidget; + + imageWidget = GestureDetector( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 20.0), + child: Util.showImage( + '${image['path']}', + width: 80.0, + height: 80.0, + fit: BoxFit.fill + ), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + width: 0.5, + color: Colors.black12, + ), + bottom: BorderSide( + width: 0.5, + color: Colors.black12, + ), + left: BorderSide( + width: 0.5, + color: Colors.black12, + ), + right: BorderSide( + width: 0.5, + color: Colors.black12, + ), + ), + ), + ), + Visibility( + visible: galleryImagesFlag[i]['show'], + child: Container( + padding: EdgeInsets.only(right: 20.0), + child: CircularPercentIndicator( + radius: 60.0, + lineWidth: 10.0, + percent: galleryImagesFlag[i]['progress'], + center: new Text( + '${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15.0, + color: Colors.white, + ), + ), + circularStrokeCap: CircularStrokeCap.round, + progressColor: Colors.orange, + ), + ), + ), + ], + ), + onTap: () { + showDialog( + context: mainContext, + builder: (BuildContext context) { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + return Util().getPicture2(mainContext, i, (int imageId, String path){ + setState(() { + galleryImagesFlag[imageId]['path'] = path; + }); + }); + return null; + } + ); + }, + ); + + row.children.add(Badge( + position: BadgePosition.topEnd(top: -10.0, end: 10.0), + badgeContent: Container( + child: GestureDetector( + child: Icon( + Icons.clear, + color: Colors.white, + size: 14.0, + ), + onTap: () { + _deleteImage(mainContext, i); + }, + ), + ), + showBadge: image['path'].isNotEmpty ? true : false, + child: imageWidget, + )); + } + return row; + } + + void _deleteImage(BuildContext mainContext, int imageId) { + showDialog(context: mainContext, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).warning), + content: Text(S.of(context).are_you_sure_to_remove_the_picture), + actions: [ + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + RaisedButton( + child: Text(S.of(context).yes_i_am_sure), + color: Theme.of(context).primaryColor, + onPressed: () { + Navigator.of(context).pop(); + setState(() { + galleryImagesFlag[imageId]['path'] = ''; + }); + }, + ), + ], + ); + }, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + eventBus.on().listen((event) { + if (mounted) { + setState(() { + onSubmitting = event.showProgress; + submitProgress = event.progress; + }); + } + }); + } + + void _submit() { + final FormState form = _formKey.currentState; + if (form.validate()) { + setState(() { + onSubmitting = true; + }); + Util().createTicket(context, + issueMsgController.text.trim(), + galleryImagesFlag, + (response){ + showDialog(context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).success), + content: Text(S.of(context).ticket_created_success), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Routes.router.navigateTo(context, + '/my-support/${ticket.store.id}', + replace: true, + ); + }, + ), + ], + ); + }); + }, (error) { + Utils.showMessageDialog(context, error, onOk: () { + Routes.router.pop(context); + Routes.router.pop(context); + }); + }, id: widget.ticketId); + } + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/ocr_scan.dart.unuse b/lib/widgets/mobile/ocr_scan.dart.unuse new file mode 100644 index 0000000..99eb1bd --- /dev/null +++ b/lib/widgets/mobile/ocr_scan.dart.unuse @@ -0,0 +1,249 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import '../../models/key_value.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:tesseract_ocr/tesseract_ocr.dart'; +import '../../generated/l10n.dart'; + +class OCRScan extends StatefulWidget { + + @override + State createState() => OCRScanState(); + +} + +class OCRScanState extends State { + String _extractText; + List docTypes = []; + KeyValue selectedDocType; + List docLan = []; + KeyValue selectedDocLan; + bool _reading; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text(S + .of(context) + .ocr_scan), + backgroundColor: Theme + .of(context) + .primaryColor, + ), + body: ListView.builder( + itemCount: 4, + itemBuilder: (BuildContext context, int i) { + Widget item; + switch(i) { + case 0: + item = Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).document_type, + ), + ), + Container( + child: DropdownButton( + items: docTypes.map((e) { + return DropdownMenuItem( + value: e, + child: Text( + e.name + ), + ); + }).toList(), + hint: Text( + S.of(context).select_document_type, + ), + onChanged: (newValue) { + setState(() { + selectedDocType = newValue; + }); + }, + isExpanded: true, + value: selectedDocType, + ), + ), + ], + ), + ); + break; + case 1: + item = Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + S.of(context).document_langage, + ), + ), + Container( + child: DropdownButton( + items: docLan.map((e) { + return DropdownMenuItem( + value: e, + child: Text( + e.name + ), + ); + }).toList(), + hint: Text( + S.of(context).select_document_lanuage, + ), + onChanged: (newValue) { + setState(() { + selectedDocLan = newValue; + }); + }, + isExpanded: true, + value: selectedDocLan, + ), + ), + ], + ), + ); + break; + case 2: + item = Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(right: 10.0), + child: RaisedButton.icon( + elevation: 2.0, + shape: new RoundedRectangleBorder( + borderRadius: new BorderRadius.circular(5.0), + ), + color: const Color(0xFFFFB822), + icon: Icon(Icons.camera_alt_outlined), + label: Text( + S.of(context).from_camera, + ), + onPressed: _reading != null && _reading == true ? null : () { + getPicFromCamera(); + }, + ), + ), + Container( + padding: EdgeInsets.only(right: 10.0), + child: RaisedButton.icon( + elevation: 2.0, + shape: new RoundedRectangleBorder( + borderRadius: new BorderRadius.circular(5.0), + ), + color: const Color(0xFFEFFE00), + icon: Icon(Icons.image_search), + label: Text( + S.of(context).from_gallery, + ), + onPressed: _reading != null && _reading == true ? null : () { + getPicFromGallery(); + }, + ), + ), + ], + ), + ); + break; + case 3: + item = SizedBox.shrink(); + if (_reading != null && _reading) { + item = _reading == null ? SizedBox.shrink() : + Container( + padding: EdgeInsets.only(top: 12.0), + child: _reading ? SpinKitCircle( + size: 24.0, + color: Colors.blue, + ) : Icon(Icons.done), + ); + } else if (_extractText != null) { + item = Container( + child: RaisedButton.icon( + onPressed: () { + + }, + color: const Color(0xEE22EE00), + icon: Icon(Icons.arrow_circle_up), + label: Text( + S.of(context).submit_to_generate + )), + ); + } + break; + } + return Container( + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 8.0, bottom: 8.0), + child: item, + ); + } + ), + ); + } + + Future getPicFromCamera() async { + final picker = ImagePicker(); + var image = await picker.getImage(source: ImageSource.camera); + print(image.path); + extractText(image.path); + } + + Future getPicFromGallery() async { + final picker = ImagePicker(); + var image = await picker.getImage(source: ImageSource.gallery); + print(image.path); + extractText(image.path); + } + + Future extractText(String filePath) async { + int _extractTime = 0; + setState(() { + _reading = true; + _extractText = null; + }); + var watch = Stopwatch()..start(); + _extractText = await TesseractOcr.extractText(filePath, language: selectedDocLan.value); + _extractTime = watch.elapsedMilliseconds; + print(_extractText); + print('Time: $_extractTime'); + setState(() { + _reading = false; + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + docTypes.add(KeyValue(S.of(context).ubereats_receipt, 'ubereats-receipt')); + docTypes.add(KeyValue(S.of(context).business_card, 'business-card')); + var eng = KeyValue(S.of(context).english, 'eng'); + docLan.add(eng); + docLan.add(KeyValue(S.of(context).chinese_simplified, 'chi_sim')); + docLan.add(KeyValue(S.of(context).chinese_traditional, 'chi_tra')); + if (selectedDocLan == null) { + setState(() { + selectedDocLan = eng; + }); + } + } + + @override + void initState() { + super.initState(); + } +} \ No newline at end of file diff --git a/lib/widgets/mobile/shopping_cart_bar.dart b/lib/widgets/mobile/shopping_cart_bar.dart new file mode 100644 index 0000000..b211db9 --- /dev/null +++ b/lib/widgets/mobile/shopping_cart_bar.dart @@ -0,0 +1,326 @@ + +import 'package:flutter/material.dart'; + +import '../../generated/l10n.dart'; +import '../../models/business.dart'; +import '../../models/cart_info.dart'; +import '../../models/cart_line_item.dart'; +import '../../routes.dart'; +import '../../store/actions.dart'; +import '../../store/store.dart'; +import '../../utils/utils.dart'; +import '../../widgets/general/add_remove_button.dart'; +import '../../widgets/general/style.dart'; +import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart'; + +typedef OnEmptyCartListener(); +typedef OnPanelOpenCloseRequest(); + +class ShoppingCartBar extends StatefulWidget { + final GlobalKey endKey; + final Business business; + final OnEmptyCartListener onEmptyCartListener; + final OnPanelOpenCloseRequest onPanelOpenCloseRequest; + final bool barAtBottom; + final bool hasPicture; + ShoppingCartBar({@required this.business, this.endKey, + this.onEmptyCartListener, this.onPanelOpenCloseRequest, + this.barAtBottom = false, this.hasPicture = false + }); + + @override + State createState() { + return new ShoppingCartBarState(); + } +} + +class ShoppingCartBarState extends State { + CartInfo cartInfo; + double totalPrice = 0.0; + + @override + Widget build(BuildContext context) { + totalPrice = 0.0; + cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business); + if (cartInfo != null && cartInfo.businessInfo.id == widget.business.id) { + totalPrice = cartInfo.getTotalPrice(); + } + + Widget cartContent; + + if (cartInfo == null || (cartInfo.businessInfo.id != widget.business.id) + || (totalPrice == 0.0 && cartInfo.productList.length == 0)) { + cartContent = Center( + child: Container( + padding: EdgeInsets.all(20.0), + child: Text(S.of(context).your_basket_is_empty), + ), + ); + } else { + cartContent = Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [], + ); + (cartContent as Column).children.add( + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 12.0, left: 10.0, bottom: 12.0), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.delete, + size: 14.0, + color: Colors.black38, + ), + Text( + S.of(context).empty_basket, + style: TextStyle( + fontSize: 13.0, + color: Colors.black38 + ), + ) + ], + ), + ), + onTap: () { + Utils.clearCart(context, cartInfo, onComplete: (_) { + if (widget.onEmptyCartListener != null) { + widget.onEmptyCartListener(); + } + }); + }, + ), + ); + for (var i = 0; i < cartInfo.productList.length; i++) { + (cartContent as Column).children.add(cartLineItem(cartInfo.productList[i], i)); + } + } + + Stack stack = Stack( + clipBehavior: Clip.hardEdge, + children: [ + Container( + width: double.maxFinite, + height: 50.0, + child: Row( + children: [ + new Expanded( + flex: 2, + child: Container( + padding: new EdgeInsets.symmetric(vertical: 5).copyWith(left: 80.0), + color: new Color(0xFF3D3D3F), + child: new Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new Text( + '\$', + style: new TextStyle( + fontSize: 12.0, + color: Style.backgroundColor + ), + ), + new Text( + '${totalPrice.toStringAsFixed(2)}', + style: new TextStyle( + fontSize: 22.0, + color: Style.backgroundColor + ), + ), + ], + ), + new Text( + S.of(context).delivery_fee(widget.business.shippingFee.toStringAsFixed(2)), + style: new TextStyle( + fontSize: 9.0, + color: Style.backgroundColor, + ), + ), + ], + ), + ) + ), + new Expanded( + flex: 1, + child: GestureDetector( + child: Container( + padding: EdgeInsets.all(10.0), + color: widget.business.minPrice - totalPrice >= 0 ? new Color(0xFF535356) : Colors.lightGreen, + child: Center( + child: Text( + widget.business.minPrice - totalPrice >= 0 ? S.of(context).order_more((widget.business.minPrice - totalPrice).toStringAsFixed(2)) : S.of(context).checkout, + style: TextStyle( + fontSize: 14.0, + color: Style.backgroundColor, + ), + ), + ), + ), + onTap: widget.business.minPrice >= totalPrice ? null : () { + if (store.state.user != null) { + Routes.router.navigateTo(context, '/checkout/${widget.business.id}'); + } else { + store.dispatch(UpdateRedirectRoute('/checkout/${widget.business.id}')); + Routes.router.navigateTo(context, '/login'); + } + }, + ), + ), + ], + ), + ), + Positioned( + top: -10.0, + left: 20.0, + child: GestureDetector( + child: Container( + padding: new EdgeInsets.all(5.0), + decoration: new BoxDecoration( + color: new Color(0xFF3D3D3F), + shape: BoxShape.circle, + border: new Border.all( + color: new Color(0xFF3D3D3F), + width: 5.0, + ) + ), + child: Center( + child: Icon( + Icons.shopping_basket, + key: widget.endKey, + color: totalPrice > 0 ? Colors.orange : new Color(0xFF656565), + size: 36.0, + ), + ), + ), + onTap: () { + if (widget.onPanelOpenCloseRequest != null) { + widget.onPanelOpenCloseRequest(); + } + }, + ), + ), + ], + ); + + List widgets = []; + if (widget.barAtBottom) { + widgets.add(Container( + width: double.maxFinite, + height: 200.0, + child: SingleChildScrollView( + child: cartContent, + ), + )); + widgets.add(stack); + } else { + widgets.add(stack); + widgets.add(Container( + width: double.maxFinite, + height: 200.0, + child: SingleChildScrollView( + child: cartContent, + ), + )); + } + + return Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: widgets, + ), + ); + } + + Widget cartLineItem(CartLineItem item, int pidx) { + Widget lineItem = Container( + padding: EdgeInsets.only(top: 10.0), + height: 78.0, + decoration: BoxDecoration( + border: Border( + bottom: new BorderSide( + color: new Color(0xFFEBEBEB), + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + widget.hasPicture ? Container( + padding: EdgeInsets.all(6.0), + child: Util.showImage( + '${item.product.imagePath}', + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ) : SizedBox.shrink(), + Expanded( + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + item.name, + maxLines: 1, + style: TextStyle( + color: Colors.black87, + fontSize: 14.0 + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + child: Text( + item.description, + style: TextStyle( + color: Colors.black38, + fontSize: 10.0 + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + child: Text( + '${item.totalPrice.toStringAsFixed(2)}', + style: TextStyle( + color: Colors.redAccent, + fontSize: 14.0, + ), + ), + ), + Container( + padding: EdgeInsets.only(right: 10.0), + child: AddRemoveButton( + product: item.product, + business: widget.business, + cartLineItemIndex: pidx, + ), + ) + ], + ), + ), + ], + ), + ); + return lineItem; + } +} \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 026851f..5a2da2c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -2,11 +2,17 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) catcher_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "CatcherPlugin"); + catcher_plugin_register_with_registrar(catcher_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_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h index 9bf7478..e0f0a47 100644 --- a/linux/flutter/generated_plugin_registrant.h +++ b/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1fc8ed3..8cae7e6 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + catcher url_launcher_linux ) diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index 785633d..6064f7d 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #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 5fba960..d583bc1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #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 e2acc1a..5bcd243 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,14 +5,24 @@ import FlutterMacOS import Foundation +import catcher +import device_info_plus_macos +import flutter_local_notifications import package_info +import package_info_plus_macos import path_provider_macos import sqflite import url_launcher_macos +import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) + FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..cc0f4a0 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,59 @@ +PODS: + - FlutterMacOS (1.0.0) + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - package_info (0.0.1): + - FlutterMacOS + - path_provider (0.0.1) + - path_provider_macos (0.0.1): + - FlutterMacOS + - sqflite (0.0.2): + - FlutterMacOS + - FMDB (>= 2.7.5) + - url_launcher (0.0.1) + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral/.symlinks/flutter/darwin-x64`) + - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) + - path_provider (from `Flutter/ephemeral/.symlinks/plugins/path_provider/macos`) + - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - url_launcher (from `Flutter/ephemeral/.symlinks/plugins/url_launcher/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +SPEC REPOS: + trunk: + - FMDB + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral/.symlinks/flutter/darwin-x64 + package_info: + :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos + path_provider: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider/macos + path_provider_macos: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos + sqflite: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos + url_launcher: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: 15bea8a44d2fa024068daa0140371c020b4b6ff9 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 + path_provider: e0848572d1d38b9a7dd099e79cf83f5b7e2cde9f + path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b + sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea + url_launcher: af78307ef9bafff91273b34f1c6c0c86a0004fd7 + url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4 + +PODFILE CHECKSUM: d8ba9b3e9e93c62c74a660b46c6fcb09f03991a7 + +COCOAPODS: 1.9.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 969fc5f..c593541 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3C5456AC1D4202B16031CE29 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8843FFC3C46F990BAFB3D3F /* Pods_Runner.framework */; }; D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -58,9 +59,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1403E83B4B6EA55F1864912F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 288BD27D3EE5BFF1C949FDB9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* flutter_wisetronic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "flutter_wisetronic.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* flutter_wisetronic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_wisetronic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -73,9 +76,11 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 4DCEA134F0A04157104B396E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; + E8843FFC3C46F990BAFB3D3F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,12 +90,24 @@ files = ( D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, + 3C5456AC1D4202B16031CE29 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 28B0062471BB9598C13BE12E /* Pods */ = { + isa = PBXGroup; + children = ( + 4DCEA134F0A04157104B396E /* Pods-Runner.debug.xcconfig */, + 288BD27D3EE5BFF1C949FDB9 /* Pods-Runner.release.xcconfig */, + 1403E83B4B6EA55F1864912F /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -109,6 +126,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 28B0062471BB9598C13BE12E /* Pods */, ); sourceTree = ""; }; @@ -160,6 +178,7 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + E8843FFC3C46F990BAFB3D3F /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -171,11 +190,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 1CEF6B1EE24F95E6E1E84650 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + DA1B9B8911F50E9A913B54FD /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -245,6 +266,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1CEF6B1EE24F95E6E1E84650 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -282,6 +325,21 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + DA1B9B8911F50E9A913B54FD /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..0e0d9dd 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,9 @@ com.apple.security.network.server + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a..168e4ec 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,5 +4,9 @@ com.apple.security.app-sandbox + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + diff --git a/pubspec.lock b/pubspec.lock index 3ae1f4f..08e6aaa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,189 +7,252 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.13" + version: "3.1.2" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.2.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.3" + version: "2.8.1" + awesome_card: + dependency: transitive + description: + name: awesome_card + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" badges: dependency: "direct main" description: name: badges url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "2.0.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" cached_network_image: dependency: "direct main" description: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "3.1.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" carousel_slider: dependency: "direct main" description: name: carousel_slider url: "https://pub.dartlang.org" source: hosted - version: "2.3.1" + version: "4.0.0" catcher: dependency: "direct main" description: name: catcher url: "https://pub.dartlang.org" source: hosted - version: "0.3.23" + version: "0.6.8" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.5" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.3.1" chewie: dependency: transitive description: name: chewie url: "https://pub.dartlang.org" source: hosted - version: "0.12.0" + version: "1.2.2" chewie_audio: dependency: transitive description: name: chewie_audio url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.5" - convert: - dependency: transitive + version: "1.15.0" + countdown: + dependency: "direct main" description: - name: convert + name: countdown url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "0.1.0" + credit_card_type_detector: + dependency: transitive + description: + name: credit_card_type_detector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + credit_card_validator: + dependency: transitive + description: + name: credit_card_validator + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1+4" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" - css_colors: - dependency: transitive - description: - name: css_colors - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" + version: "3.0.1" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.2" + version: "0.17.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" - device_info: + version: "1.0.3" + device_info_plus: dependency: transitive description: - name: device_info + name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" - device_info_platform_interface: + version: "2.1.0" + device_info_plus_linux: dependency: transitive description: - name: device_info_platform_interface + name: device_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.1.0" + device_info_plus_macos: + dependency: transitive + description: + name: device_info_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + device_info_plus_web: + dependency: transitive + description: + name: device_info_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + device_info_plus_windows: + dependency: transitive + description: + name: device_info_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" dio: dependency: "direct main" description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "3.0.10" + version: "4.0.0" email_validator: dependency: "direct main" description: name: email_validator url: "https://pub.dartlang.org" source: hosted - version: "1.0.6" + version: "2.0.1" event_bus: dependency: "direct main" description: name: event_bus url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.0.0" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0" ffi: - dependency: transitive + dependency: "direct main" description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.1.2" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.0.0-nullsafety.4" + version: "6.1.2" flappy_search_bar: dependency: "direct main" description: @@ -203,7 +266,7 @@ packages: name: fluro url: "https://pub.dartlang.org" source: hosted - version: "1.7.8" + version: "2.0.3" flutter: dependency: "direct main" description: flutter @@ -215,35 +278,63 @@ packages: name: flutter_blurhash url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.6.0" flutter_cache_manager: dependency: transitive description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" - flutter_device_locale: + version: "3.1.2" + flutter_dash: dependency: "direct main" description: - name: flutter_device_locale + name: flutter_dash url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.0.1" flutter_html: dependency: "direct main" description: name: flutter_html url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.1.1" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.2" flutter_launcher_icons: dependency: "direct main" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.8.1" + version: "0.9.1" + flutter_layout_grid: + dependency: transitive + description: + name: flutter_layout_grid + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -255,297 +346,514 @@ packages: name: flutter_mailer url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" flutter_markdown: dependency: "direct main" description: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.5.1" + version: "0.6.4" + flutter_math_fork: + dependency: transitive + description: + name: flutter_math_fork + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" flutter_redux: dependency: "direct main" description: name: flutter_redux url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "0.8.2" + flutter_slidable: + dependency: transitive + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" flutter_spinkit: dependency: "direct main" description: name: flutter_spinkit url: "https://pub.dartlang.org" source: hosted - version: "4.1.2+1" + version: "5.0.0" flutter_staggered_grid_view: dependency: transitive description: name: flutter_staggered_grid_view url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" flutter_svg: - dependency: "direct main" + dependency: transitive description: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.19.2+1" + version: "0.22.0" flutter_test: - dependency: "direct dev" + dependency: transitive description: flutter source: sdk version: "0.0.0" flutter_web_plugins: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" fluttertoast: - dependency: transitive + dependency: "direct main" description: name: fluttertoast url: "https://pub.dartlang.org" source: hosted - version: "7.1.6" + version: "8.0.8" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "9.1.0" + gender_selection: + dependency: "direct main" + description: + name: gender_selection + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + url: "https://pub.dartlang.org" + source: hosted + version: "7.4.0" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" hive: dependency: "direct main" description: name: hive url: "https://pub.dartlang.org" source: hosted - version: "1.4.4+1" + version: "2.0.4" hive_flutter: dependency: "direct main" description: name: hive_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "1.1.0" + hovering: + dependency: "direct main" + description: + name: hovering + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" html: dependency: transitive description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+4" + version: "0.15.0" http: dependency: transitive description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.3" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.19" - import_js_library: - dependency: transitive + version: "3.0.2" + image_picker: + dependency: "direct main" description: - name: import_js_library + name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "0.8.3+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" intl: dependency: transitive description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.17.0-nullsafety.2" + version: "0.17.0" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3-nullsafety.3" + version: "0.6.3" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.1" mailer: dependency: transitive description: name: mailer url: "https://pub.dartlang.org" source: hosted - version: "3.2.1" + version: "5.0.1" markdown: dependency: transitive description: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "4.0.0" + mask_text_input_formatter: + dependency: transitive + description: + name: mask_text_input_formatter + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.3" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.6" + version: "1.7.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" + version: "1.0.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + numerus: + dependency: transitive + description: + name: numerus + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" octo_image: dependency: transitive description: name: octo_image url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "1.0.0+1" package_info: dependency: "direct main" description: name: package_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.3+2" + version: "2.0.2" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + package_info_plus_linux: + dependency: transitive + description: + name: package_info_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + package_info_plus_macos: + dependency: transitive + description: + name: package_info_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + package_info_plus_web: + dependency: transitive + description: + name: package_info_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + package_info_plus_windows: + dependency: transitive + description: + name: package_info_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.3" + version: "1.8.0" path_drawing: dependency: transitive description: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.4.1+1" + version: "0.5.1" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.2.1" path_provider: dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.24" + version: "2.0.2" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.0.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+6" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.3" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.1" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.2.0" + photo_view: + dependency: "direct main" + description: + name: photo_view + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0" + pinput: + dependency: "direct main" + description: + name: pinput + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0-nullsafety.4" + version: "3.0.0" + platform_detect: + dependency: "direct main" + description: + name: platform_detect + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.0.0-nullsafety.4" + version: "4.2.3" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" pull_to_refresh: dependency: "direct main" description: name: pull_to_refresh url: "https://pub.dartlang.org" source: hosted - version: "1.6.3" + version: "2.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" redux: dependency: transitive description: name: redux url: "https://pub.dartlang.org" source: hosted - version: "4.0.0+3" + version: "5.0.0" responsive_builder: dependency: "direct main" description: name: responsive_builder url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.1" rxdart: dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.25.0" + version: "0.27.1" searchable_dropdown: dependency: "direct main" description: @@ -559,26 +867,40 @@ packages: name: sentry url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "5.1.0" share: dependency: "direct main" description: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.5+4" + version: "2.0.4" + simple_animations: + dependency: transitive + description: + name: simple_animations + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + smooth_star_rating: + dependency: "direct main" + description: + name: smooth_star_rating + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.4" + version: "1.8.1" splashscreen: dependency: "direct main" description: @@ -592,217 +914,310 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.2+1" + version: "2.0.0+3" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.2+1" + version: "2.0.0+2" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.6" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" + stripe_payment: + dependency: "direct main" + description: + name: stripe_payment + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.4" + stripe_sdk: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: b4e027a18e177653f9d0ad3931f71c9e26711901 + url: "git://github.com/ezet/stripe-sdk.git" + source: git + version: "5.0.0-nullsafety.1" + supercharged: + dependency: transitive + description: + name: supercharged + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + supercharged_dart: + dependency: transitive + description: + name: supercharged_dart + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" synchronized: dependency: transitive description: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "3.0.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.6" + version: "0.4.2" + timezone: + dependency: transitive + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + toggle_switch: + dependency: "direct main" + description: + name: toggle_switch + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + tuple: + dependency: transitive + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.5" + version: "1.3.0" + uni_links2: + dependency: transitive + description: + name: uni_links2 + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0+2" + uni_links2_platform_interface: + dependency: transitive + description: + name: uni_links2_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+2" + uni_links_web2: + dependency: transitive + description: + name: uni_links_web2 + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+2" universal_io: dependency: "direct main" description: name: universal_io url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.4" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.9" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+1" + version: "2.0.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" - usage: - dependency: transitive + version: "2.0.1" + url_strategy: + dependency: "direct main" description: - name: usage + name: url_strategy url: "https://pub.dartlang.org" source: hosted - version: "3.4.2" + version: "0.2.0" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "3.0.4" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.5" + version: "2.1.0" video_player: dependency: transitive description: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.1.12" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "4.1.0" video_player_web: dependency: transitive description: name: video_player_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.4+1" + version: "2.0.2" wakelock: dependency: transitive description: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.5.3+3" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+2" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+1" + version: "0.2.1+2" wakelock_web: dependency: transitive description: name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+3" + version: "0.2.0+2" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" webview_flutter: dependency: transitive description: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "1.0.7" + version: "2.0.10" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "1.7.4" + version: "2.2.5" xdg_directories: dependency: transitive description: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.5.1" + version: "5.1.2" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" - zone_local: - dependency: transitive + version: "3.1.0" + youtube_player_iframe: + dependency: "direct main" description: - name: zone_local + name: youtube_player_iframe url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "2.2.1" sdks: - dart: ">=2.12.0-29.10.beta <3.0.0" - flutter: ">=1.24.0-6.0.pre <2.0.0" + dart: ">=2.13.0 <3.0.0" + flutter: ">=2.2.0" diff --git a/pubspec.yaml b/pubspec.yaml index fb11bc4..bb8d9de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,43 +25,79 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter + flutter_web_plugins: + 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.1 + font_awesome_flutter: ^9.1.0 - responsive_builder: ^0.3.0 - fluro: ^1.7.7 - flutter_redux: ^0.7.0 - event_bus: ^1.1.1 - cached_network_image: ^2.4.1 - pull_to_refresh: ^1.6.3 + responsive_builder: ^0.4.1 + fluro: ^2.0.3 + flutter_redux: ^0.8.2 + event_bus: ^2.0.0 + cached_network_image: ^3.1.0 #^2.5.1 + pull_to_refresh: ^2.0.0 #^1.5.0 splashscreen: ^1.3.5 - carousel_slider: ^2.3.1 - badges: ^1.1.6 - url_launcher: ^5.7.10 - flutter_html: ^1.1.1 - flutter_markdown: ^0.5.1 - hive: ^1.4.4+1 - hive_flutter: ^0.3.1 - flutter_spinkit: ^4.1.2+1 - dio: ^3.0.10 + carousel_slider: ^4.0.0 + badges: ^2.0.1 + url_launcher: ^6.0.9 #^5.7.10 + flutter_html: ^2.1.1 + flutter_markdown: ^0.6.4 #^0.5.1 + hive: ^2.0.4 #^1.4.4+1 + hive_flutter: ^1.1.0 #^0.3.1 + flutter_spinkit: ^5.0.0 + dio: ^4.0.0 #^3.0.10 searchable_dropdown: ^1.1.3 - email_validator: ^1.0.6 + email_validator: ^2.0.1 flappy_search_bar: ^1.7.2 - flutter_launcher_icons: ^0.8.1 - flutter_device_locale: ^0.4.0 - package_info: ^0.4.3+2 - catcher: ^0.3.23 - share: ^0.6.5+4 - flutter_svg: ^0.19.2+1 - universal_io: ^1.0.1 - path_provider: ^1.6.24 + flutter_launcher_icons: ^0.9.1 #^0.8.1 + package_info: ^2.0.2 + catcher: ^0.6.8 #^0.4.2 + share: ^2.0.4 #^0.6.5+4 + universal_io: ^2.0.4 #^1.0.2 + path_provider: ^2.0.2 #^1.6.28 +# path_provider: ^2.0.1 + platform_detect: ^2.0.0 + flutter_inappwebview: ^5.3.2 #^4.0.0+4 + youtube_player_iframe: ^2.2.1 #^1.2.0+2 + fluttertoast: ^8.0.8 #^7.1.8 + percent_indicator: ^3.0.1 + image_picker: ^0.8.0+3 #^0.6.7+22 + countdown: ^0.1.0 + gender_selection: ^1.0.0 +# uni_links: ^0.5.1 #^0.4.0 + photo_view: ^0.12.0 + flutter_dash: ^0.0.1 + smooth_star_rating: ^1.1.1 +# tesseract_ocr: ^0.3.1 +# local_auth: ^1.1.0 + toggle_switch: ^1.2.0 + pinput: ^1.2.0 + google_maps_flutter: ^2.0.6 #^1.2.0 + stripe_payment: ^1.0.10 +# stripe_sdk: +# git: +# url: git://github.com/romme86/stripe-sdk.git + stripe_sdk: + git: + url: git://github.com/ezet/stripe-sdk.git + uuid: ^3.0.4 #^2.2.2 + ffi: ^1.0.0 +# firebase_messaging: ^10.0.4 #^7.0.3 + flutter_local_notifications: ^8.1.1 #^4.0.1+2 + geolocator: ^7.4.0 #^6.2.1 + url_strategy: ^0.2.0 + hovering: ^1.0.4 -dev_dependencies: - flutter_test: - sdk: flutter +#dev_dependencies: +# flutter_test: +# sdk: flutter + +#dependency_overrides: +# path: ^1.8.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -76,7 +112,9 @@ flutter: # To add assets to your application, add an assets section, like this: assets: + - assets/ - assets/images/ + - assets/tessdata/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg @@ -111,3 +149,8 @@ flutter: # see https://flutter.dev/custom-fonts/#from-packages flutter_intl: enabled: true + +flutter_icons: + android: "launcher_icon" + ios: true + image_path: "assets/images/icon.png" \ No newline at end of file diff --git a/re-init.sh b/re-init.sh new file mode 100755 index 0000000..8c7bded --- /dev/null +++ b/re-init.sh @@ -0,0 +1,2 @@ +flutter clean +flutter pub upgrade --major-versions \ No newline at end of file diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..a4e4292 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..a71be57 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..c8a1993 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/index.html b/web/index.html index 108bfe7..5142f9f 100644 --- a/web/index.html +++ b/web/index.html @@ -26,7 +26,7 @@ - flutter_wisetronic + Wisetrnic Inc. Committed to providing a complete Point Of Sale solution。 @@ -41,5 +41,16 @@ } + + + diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index ddfcf7c..0e707e1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -2,11 +2,16 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + CatcherPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CatcherPlugin")); UrlLauncherPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherPlugin")); } diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h index 9846246..dc139d8 100644 --- a/windows/flutter/generated_plugin_registrant.h +++ b/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 411af46..330048d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + catcher url_launcher_windows )