├── android ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ └── styles.xml │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── yourcompany │ │ │ │ └── flutterelm │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── screenshots ├── food.jpeg ├── home.jpeg ├── login.jpeg ├── msite.jpeg ├── search.jpeg └── profile.jpeg ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── AppIcon-1.appiconset │ │ │ ├── icon-29.png │ │ │ ├── icon-40.png │ │ │ ├── icon-50.png │ │ │ ├── icon-57.png │ │ │ ├── icon-72.png │ │ │ ├── icon-76.png │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-50@2x.png │ │ │ ├── icon-57@2x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-72@2x.png │ │ │ ├── icon-76@2x.png │ │ │ ├── icon-20-ipad.png │ │ │ ├── icon-29-ipad.png │ │ │ ├── icon-83.5@2x.png │ │ │ ├── icon-20@2x-ipad.png │ │ │ ├── icon-29@2x-ipad.png │ │ │ └── Contents.json │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── main.m │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── .gitignore ├── Podfile.lock └── Podfile ├── .gitignore ├── lib ├── config │ └── config.dart ├── store │ ├── store.dart │ ├── app_action.dart │ └── app_reducer.dart ├── utils │ ├── location_utils.dart │ ├── utils.dart │ ├── http.dart │ ├── local_storage.dart │ └── api.dart ├── model │ ├── rating_tag.dart │ ├── delivery_mode.dart │ ├── item_rating.dart │ ├── sub_category.dart │ ├── place.dart │ ├── support.dart │ ├── activity.dart │ ├── rating_score.dart │ ├── city.dart │ ├── food_type.dart │ ├── category.dart │ ├── menu.dart │ ├── app_state.dart │ ├── rating.dart │ ├── spec_food.dart │ ├── user.dart │ ├── food.dart │ └── restaurant.dart ├── style │ └── style.dart ├── components │ ├── custom_dialog.dart │ ├── button.dart │ ├── alphabet_bar.dart │ ├── rating_star.dart │ ├── foot_bar.dart │ ├── shopping_cart.dart │ ├── shop_list.dart │ ├── form_input.dart │ ├── carousel.dart │ ├── head_bar.dart │ ├── drop_down.dart │ └── shop_item.dart ├── page │ ├── order.dart │ ├── login.dart │ ├── city.dart │ ├── msite.dart │ ├── profile.dart │ ├── home.dart │ └── search.dart ├── main.dart └── routes │ └── routes.dart ├── .metadata ├── android.iml ├── flutter_elm.iml ├── test └── widget_test.dart ├── flutter_elm_android.iml ├── README.md └── pubspec.yaml /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /screenshots/food.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/food.jpeg -------------------------------------------------------------------------------- /screenshots/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/home.jpeg -------------------------------------------------------------------------------- /screenshots/login.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/login.jpeg -------------------------------------------------------------------------------- /screenshots/msite.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/msite.jpeg -------------------------------------------------------------------------------- /screenshots/search.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/search.jpeg -------------------------------------------------------------------------------- /screenshots/profile.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/screenshots/profile.jpeg -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .vscode/ 5 | .packages 6 | .pub/ 7 | build/ 8 | ios/.generated/ 9 | packages 10 | pubspec.lock 11 | .flutter-plugins 12 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-50.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-72.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /lib/config/config.dart: -------------------------------------------------------------------------------- 1 | class Config { 2 | static const String ImgCdnUrl = 'https://fuss10.elemecdn.com'; 3 | static const String ImgBaseUrl = 'http://cangdu.org:8001/img/'; 4 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon-1.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiaoyuwen/flutter_elm/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/store/store.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import '../model/app_state.dart'; 3 | import '../store/app_reducer.dart'; 4 | 5 | final store = new Store( 6 | appReducer, 7 | initialState: new AppState.loading(), 8 | ); -------------------------------------------------------------------------------- /lib/store/app_action.dart: -------------------------------------------------------------------------------- 1 | import '../model/user.dart'; 2 | 3 | class UpdateGeoHashAction { 4 | final String geoHash; 5 | UpdateGeoHashAction(this.geoHash); 6 | } 7 | 8 | class LoginAction { 9 | final User user; 10 | LoginAction(this.user); 11 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 553fc4264e5c3bd85531e73fb7d89bdfab0fa3ec 8 | channel: alpha 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/store/app_reducer.dart: -------------------------------------------------------------------------------- 1 | import '../model/app_state.dart'; 2 | import 'app_action.dart'; 3 | 4 | AppState appReducer(AppState state, action) { 5 | if (action is UpdateGeoHashAction) { 6 | return state.copyWith(geoHash: action.geoHash); 7 | } else if (action is LoginAction) { 8 | return state.copyWith(user: action.user); 9 | } 10 | return state; 11 | } -------------------------------------------------------------------------------- /lib/utils/location_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:location/location.dart'; 2 | 3 | class LocationUtils { 4 | static final location = new Location(); 5 | 6 | static getLocation() async { 7 | var current; 8 | try { 9 | current = await location.getLocation; 10 | } catch (e) { 11 | print('getLocation error: $e'); 12 | } 13 | return current; 14 | } 15 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/model/rating_tag.dart: -------------------------------------------------------------------------------- 1 | class RatingTag { 2 | RatingTag.fromJson(Map json) 3 | : id = json['_id'], 4 | count = json['count'], 5 | name = json['name'], 6 | unsatisfied = json['unsatisfied']; 7 | 8 | String id; 9 | int count; 10 | String name; 11 | bool unsatisfied; 12 | 13 | Map toJson() => { 14 | 'id': id, 15 | 'count': count, 16 | 'name': name, 17 | 'unsatisfied': unsatisfied, 18 | }; 19 | } -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 7 | [GeneratedPluginRegistrant registerWithRegistry:self]; 8 | // Override point for customization after application launch. 9 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/yourcompany/flutterelm/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yourcompany.flutterelm; 2 | 3 | import android.os.Bundle; 4 | 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | GeneratedPluginRegistrant.registerWith(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/model/delivery_mode.dart: -------------------------------------------------------------------------------- 1 | class DeliveryMode { 2 | DeliveryMode.fromJson(Map json) 3 | : id = json['id'], 4 | text = json['text'], 5 | color = json['color'], 6 | iconColor = json['icon_color'], 7 | isSolid = json['is_solid']; 8 | 9 | int id; 10 | String text; 11 | String color; 12 | String iconColor; 13 | bool isSolid; 14 | 15 | Map toJson() => { 16 | 'id': id, 17 | 'text': text, 18 | 'color': color, 19 | 'is_solid': isSolid, 20 | }; 21 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import '../config/config.dart'; 2 | 3 | class Utils { 4 | //传递过来的图片地址需要处理后才能正常使用 5 | static String getImgPath(String path) { 6 | String suffix; 7 | if (path == null || path.isEmpty) { 8 | return 'http://test.fe.ptdev.cn/elm/elmlogo.jpeg'; 9 | } 10 | if (path.indexOf('jpeg') != -1) { 11 | suffix = '.jpeg'; 12 | } else { 13 | suffix = '.png'; 14 | } 15 | String url = '/' + path.substring(0, 1) + '/' + path.substring(1, 3) + '/' + path.substring(3) + suffix; 16 | return '${Config.ImgCdnUrl}$url'; 17 | } 18 | } -------------------------------------------------------------------------------- /lib/model/item_rating.dart: -------------------------------------------------------------------------------- 1 | class ItemRating { 2 | ItemRating.fromJson(Map json) 3 | : id = json['_id'], 4 | foodId = json['food_id'], 5 | foodName = json['food_name'], 6 | imageHash = json['image_hash'], 7 | isValid = json['is_valid']; 8 | 9 | String id; 10 | int foodId; 11 | String foodName; 12 | String imageHash; 13 | int isValid; 14 | 15 | Map toJson() => { 16 | '_id': id, 17 | 'food_id': foodId, 18 | 'food_name': foodName, 19 | 'image_hash': imageHash, 20 | 'is_valid': isValid, 21 | }; 22 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /lib/model/sub_category.dart: -------------------------------------------------------------------------------- 1 | class SubCategory { 2 | SubCategory.fromJson(Map json) 3 | : id = json['id'], 4 | name = json['name'], 5 | count = json['count'], 6 | imageUrl = json['imageUrl'], 7 | level = json['level'], 8 | _id = json['_id']; 9 | 10 | int id; 11 | String name; 12 | int count; 13 | String imageUrl; 14 | int level; 15 | String _id; 16 | 17 | Map toJson() => { 18 | 'id': id, 19 | 'name': name, 20 | 'count': count, 21 | 'imageUrl': imageUrl, 22 | 'level': level, 23 | '_id': _id, 24 | }; 25 | } -------------------------------------------------------------------------------- /android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/model/place.dart: -------------------------------------------------------------------------------- 1 | class Place { 2 | Place.fromJson(Map json) 3 | : name = json['name'], 4 | address = json['address'], 5 | geohash = json['geohash'], 6 | longitude = num.parse(json['longitude'].toString()), 7 | latitude = num.parse(json['latitude'].toString()); 8 | 9 | String name; 10 | String address; 11 | String geohash; 12 | num longitude; 13 | num latitude; 14 | 15 | Map toJson() => { 16 | 'name': name, 17 | 'address': address, 18 | 'geohash': geohash, 19 | 'longitude': longitude, 20 | 'latitude': latitude, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /lib/model/support.dart: -------------------------------------------------------------------------------- 1 | class Support { 2 | Support.fromJson(Map json) 3 | : id = json['id'], 4 | uid = json['_id'], 5 | name = json['name'], 6 | iconName = json['icon_name'], 7 | iconColor = json['icon_color'], 8 | description = json['description']; 9 | 10 | int id; 11 | String uid; 12 | String name; 13 | String iconName; 14 | String iconColor; 15 | String description; 16 | 17 | Map toJson() => { 18 | 'id': id, 19 | '_id': uid, 20 | 'name': name, 21 | 'icon_name': iconName, 22 | 'icon_color': iconColor, 23 | 'description': description, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | *.pbxuser 16 | *.mode1v3 17 | *.mode2v3 18 | *.perspectivev3 19 | 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | 25 | xcuserdata 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | /Flutter/app.flx 35 | /Flutter/app.zip 36 | /Flutter/flutter_assets/ 37 | /Flutter/App.framework 38 | /Flutter/Flutter.framework 39 | /Flutter/Generated.xcconfig 40 | /ServiceDefinitions.json 41 | 42 | Pods/ 43 | -------------------------------------------------------------------------------- /lib/style/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Style { 4 | static final fontSize = 15.0; 5 | static final primaryColor = const Color(0xFF3190e8); 6 | static final backgroundColor = const Color(0xFFFFFFFF); 7 | static final emptyBackgroundColor = const Color(0xFFF5F5F5); 8 | static final borderColor = const Color(0xFFE4E4E4); 9 | static final gPadding = 16.0; 10 | static final textStyle = new TextStyle( 11 | color: const Color(0xFF333333), 12 | fontSize: fontSize, 13 | ); 14 | 15 | static BoxDecoration testDecoration(Color color) { 16 | return new BoxDecoration( 17 | border: new Border.all(color: color), 18 | ); 19 | } 20 | } -------------------------------------------------------------------------------- /lib/components/custom_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | 4 | class CustomDialog extends StatelessWidget { 5 | CustomDialog({ 6 | String title, 7 | String content, 8 | }) : title = title, 9 | content = content; 10 | 11 | final String title; 12 | final String content; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return new AlertDialog( 17 | title: new Text( 18 | title, 19 | style: Style.textStyle, 20 | ), 21 | content: new Text( 22 | content, 23 | style: Style.textStyle, 24 | ), 25 | actions: [], 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /flutter_elm.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/page/order.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/foot_bar.dart'; 3 | import '../style/style.dart'; 4 | import '../components/head_bar.dart'; 5 | 6 | class Order extends StatefulWidget { 7 | @override 8 | createState() => new OrderState(); 9 | } 10 | 11 | class OrderState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return new Scaffold( 15 | appBar: new HeadBar( 16 | title: '订单列表', 17 | showUser: false, 18 | ), 19 | body: new Column( 20 | children: [ 21 | ], 22 | ), 23 | bottomNavigationBar: new FootBar(currentIndex: 2,), 24 | backgroundColor: Style.emptyBackgroundColor, 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/model/activity.dart: -------------------------------------------------------------------------------- 1 | class Activity { 2 | Activity.fromJson(Map json) 3 | : id = json['id'], 4 | name = json['name'], 5 | iconName = json['icon_name'], 6 | iconColor = json['icon_color'], 7 | description = json['description'], 8 | rankingWeight = json['ranking_weight'], 9 | version = json['__v']; 10 | 11 | int id; 12 | String name; 13 | String iconName; 14 | String iconColor; 15 | String description; 16 | int rankingWeight; 17 | int version; 18 | 19 | Map toJson() => { 20 | 'id': id, 21 | 'name': name, 22 | 'icon_name': iconName, 23 | 'icon_color': iconColor, 24 | 'description': description, 25 | 'ranking_weight': rankingWeight, 26 | '__v': version, 27 | }; 28 | } -------------------------------------------------------------------------------- /lib/model/rating_score.dart: -------------------------------------------------------------------------------- 1 | class RatingScore { 2 | RatingScore.fromJson(Map json) 3 | : compareRating = json['compare_rating'], 4 | deliverTime = json['deliver_time'], 5 | foodScore = json['food_score'], 6 | orderRatingAmount = json['order_rating_amount'], 7 | overallScore = json['overall_score'], 8 | serviceScore = json['service_score']; 9 | 10 | num compareRating; 11 | int deliverTime; 12 | num foodScore; 13 | int orderRatingAmount; 14 | num overallScore; 15 | num serviceScore; 16 | 17 | Map toJson() => { 18 | 'compare_rating': compareRating, 19 | 'deliver_time': deliverTime, 20 | 'food_score': foodScore, 21 | 'order_rating_amount': orderRatingAmount, 22 | 'overall_score': overallScore, 23 | 'service_score': serviceScore, 24 | }; 25 | } -------------------------------------------------------------------------------- /lib/model/city.dart: -------------------------------------------------------------------------------- 1 | class City { 2 | City.fromJson(Map json) 3 | : id = json['id'], 4 | name = json['name'], 5 | pinyin = json['name'], 6 | abbr = json['abbr'], 7 | areaCode = json['areaCode'], 8 | isMap = json['is_map'], 9 | longitude = json['longitude'], 10 | latitude = json['latitude'], 11 | sort = json['sort']; 12 | 13 | int id; 14 | String name; 15 | String pinyin; 16 | String abbr; 17 | String areaCode; 18 | bool isMap; 19 | num longitude; 20 | num latitude; 21 | int sort; 22 | 23 | Map toJson() => { 24 | 'id': id, 25 | 'name': name, 26 | 'pinyin': pinyin, 27 | 'abbr': abbr, 28 | 'areaCode': areaCode, 29 | 'is_map': isMap, 30 | 'longitude': longitude, 31 | 'latitude': latitude, 32 | 'sort': sort, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /lib/model/food_type.dart: -------------------------------------------------------------------------------- 1 | class FoodType { 2 | FoodType.fromJson(Map json) 3 | : id = json['id'], 4 | title = json['title'], 5 | description = json['description'], 6 | isInServing = json['is_in_serving'], 7 | link = json['link'], 8 | imageUrl = json['image_url'], 9 | iconUrl = json['icon_url'], 10 | titleColor = json['title_color'], 11 | version = json['__v']; 12 | 13 | int id; 14 | String title; 15 | String description; 16 | bool isInServing; 17 | String link; 18 | String imageUrl; 19 | String iconUrl; 20 | String titleColor; 21 | int version; 22 | 23 | Map toJson() => { 24 | 'id': id, 25 | 'name': title, 26 | 'abbr': description, 27 | 'is_in_serving': isInServing, 28 | 'link': link, 29 | 'image_url': imageUrl, 30 | 'icon_url': iconUrl, 31 | 'title_olor': titleColor, 32 | '__v': version, 33 | }; 34 | } -------------------------------------------------------------------------------- /lib/model/category.dart: -------------------------------------------------------------------------------- 1 | import 'sub_category.dart'; 2 | 3 | class Category { 4 | Category.fromJson(Map json) 5 | : id = json['id'], 6 | name = json['name'], 7 | count = json['count'], 8 | imageUrl = json['image_url'], 9 | level = json['level'], 10 | ids = (json['ids'] as List).map((item) => item as int).toList(), 11 | subCategories = (json['sub_categories'] as List).map((item) => new SubCategory.fromJson(item)).toList(), 12 | version = json['__v']; 13 | 14 | int id; 15 | String name; 16 | int count; 17 | String imageUrl; 18 | List subCategories; 19 | int level; 20 | List ids; 21 | int version; 22 | 23 | Map toJson() => { 24 | 'id': id, 25 | 'name': name, 26 | 'count': count, 27 | 'image_url': imageUrl, 28 | 'sub_categories': subCategories, 29 | 'level': level, 30 | 'ids': ids, 31 | 'version': version, 32 | }; 33 | } -------------------------------------------------------------------------------- /lib/model/menu.dart: -------------------------------------------------------------------------------- 1 | import 'food.dart'; 2 | 3 | class Menu { 4 | Menu.fromJson(Map json) 5 | : id = json['id'], 6 | name = json['name'], 7 | description = json['description'], 8 | iconUrl = json['icon_url'], 9 | restaurantId = json['restaurant_id'], 10 | isSelected = json['is_selected'], 11 | type = json['type'], 12 | version = json['__v'], 13 | foods = (json['foods'] as List).map((item) => new Food.fromJson(item)).toList(); 14 | 15 | int id; 16 | String name; 17 | String description; 18 | String iconUrl; 19 | int restaurantId; 20 | bool isSelected; 21 | int type; 22 | int version; 23 | List foods; 24 | 25 | Map toJson() => { 26 | 'id': id, 27 | 'name': name, 28 | 'description': description, 29 | 'icon_url': iconUrl, 30 | 'restaurant_id': restaurantId, 31 | 'is_selected': isSelected, 32 | 'type': type, 33 | '__v': version, 34 | }; 35 | } -------------------------------------------------------------------------------- /lib/model/app_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'user.dart'; 3 | 4 | @immutable 5 | class AppState { 6 | final String geoHash; 7 | final User user; 8 | 9 | AppState({ 10 | this.geoHash = '', 11 | this.user, 12 | }); 13 | 14 | factory AppState.loading() => new AppState(); 15 | 16 | AppState copyWith({ 17 | String geoHash, 18 | User user, 19 | }) { 20 | return new AppState( 21 | geoHash: geoHash ?? this.geoHash, 22 | user: user ?? this.user, 23 | ); 24 | } 25 | 26 | @override 27 | int get hashCode => 28 | geoHash.hashCode ^ 29 | user.hashCode; 30 | 31 | @override 32 | bool operator ==(Object other) => 33 | identical(this, other) || 34 | other is AppState && 35 | runtimeType == other.runtimeType && 36 | geoHash == other.geoHash && 37 | user == other.user; 38 | 39 | @override 40 | String toString() { 41 | return 'AppState{geoHash: $geoHash, user: $user}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | MinimumOSVersion 28 | 8.0 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'style/style.dart'; 3 | import 'page/home.dart'; 4 | import 'routes/routes.dart'; 5 | import 'package:flutter_redux/flutter_redux.dart'; 6 | import 'store/store.dart'; 7 | import 'model/app_state.dart'; 8 | import 'model/user.dart'; 9 | import 'utils/local_storage.dart'; 10 | import 'store/app_action.dart'; 11 | 12 | void main() => runApp(new MyApp()); 13 | 14 | class MyApp extends StatelessWidget { 15 | MyApp() { 16 | //配置路由 17 | Routes.configureRoutes(); 18 | _getUser(); 19 | } 20 | 21 | _getUser() async { 22 | User user = await LocalStorage.getUser(); 23 | store.dispatch(new LoginAction(user)); 24 | } 25 | 26 | // This widget is the root of your application. 27 | @override 28 | Widget build(BuildContext context) { 29 | return new MaterialApp( 30 | home: new StoreProvider( 31 | store: store, 32 | child: new Home() 33 | ), 34 | theme: new ThemeData( 35 | primaryColor: Style.primaryColor, 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - location (0.0.1): 4 | - Flutter 5 | - shared_preferences (0.0.1): 6 | - Flutter 7 | 8 | DEPENDENCIES: 9 | - Flutter (from `/Users/touseikun/devtool/flutter/bin/cache/artifacts/engine/ios`) 10 | - location (from `/Users/touseikun/.pub-cache/hosted/pub.dartlang.org/location-1.2.0/ios`) 11 | - shared_preferences (from `/Users/touseikun/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.3.3/ios`) 12 | 13 | EXTERNAL SOURCES: 14 | Flutter: 15 | :path: "/Users/touseikun/devtool/flutter/bin/cache/artifacts/engine/ios" 16 | location: 17 | :path: "/Users/touseikun/.pub-cache/hosted/pub.dartlang.org/location-1.2.0/ios" 18 | shared_preferences: 19 | :path: "/Users/touseikun/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.3.3/ios" 20 | 21 | SPEC CHECKSUMS: 22 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 23 | location: 26c90953ad5916331d0d1d913898779910416487 24 | shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 25 | 26 | PODFILE CHECKSUM: c6c865faff84807a5d35de69eb2cdbdc36b99e15 27 | 28 | COCOAPODS: 1.5.2 29 | -------------------------------------------------------------------------------- /lib/model/rating.dart: -------------------------------------------------------------------------------- 1 | import 'item_rating.dart'; 2 | 3 | class Rating { 4 | Rating.fromJson(Map json) 5 | : id = json['_id'], 6 | avatar = json['avatar'], 7 | highlights = json['highlights'], 8 | ratedAt = json['rated_at'], 9 | ratingStar = json['rating_star'], 10 | ratingText = json['rating_text'], 11 | tags = json['tags'], 12 | timeSpentDesc = json['time_spent_desc'], 13 | username = json['username'], 14 | itemRatings = (json['item_ratings'] as List).map((item) => new ItemRating.fromJson(item)).toList(); 15 | 16 | String id; 17 | String avatar; 18 | List highlights; 19 | String ratedAt; 20 | int ratingStar; 21 | String ratingText; 22 | List tags; 23 | String timeSpentDesc; 24 | String username; 25 | List itemRatings; 26 | 27 | Map toJson() => { 28 | '_id': id, 29 | 'avatar': avatar, 30 | 'highlights': highlights, 31 | 'rated_at': ratedAt, 32 | 'rating_star': ratingStar, 33 | 'tags': tags, 34 | 'time_spent_desc': timeSpentDesc, 35 | 'username': username, 36 | }; 37 | } -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:flutter_elm/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/components/button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | 4 | class Button extends StatelessWidget { 5 | Button( 6 | {double width, 7 | double height, 8 | Widget text, 9 | Color bgColor, 10 | Decoration decoration, 11 | GestureTapCallback onTap}) 12 | : width = width, 13 | height = height, 14 | text = text, 15 | decoration = decoration, 16 | onTap = onTap; 17 | final double width; 18 | final double height; 19 | final Widget text; 20 | final Decoration decoration; 21 | final GestureTapCallback onTap; 22 | 23 | final _defaultDecoration = new BoxDecoration( 24 | color: Style.primaryColor, 25 | borderRadius: new BorderRadius.all( 26 | new Radius.circular(5.0), 27 | ), 28 | ); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return new GestureDetector( 33 | child: new Container( 34 | width: width, 35 | height: height, 36 | decoration: decoration == null ? _defaultDecoration : decoration, 37 | child: new Center( 38 | child: text, 39 | ), 40 | ), 41 | onTap: onTap, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/components/alphabet_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | 4 | typedef void OnTopCallback(String result); 5 | 6 | class AlphabetBar extends StatelessWidget { 7 | AlphabetBar({ 8 | Key key, 9 | this.onTap, 10 | }) : super(key: key); 11 | final OnTopCallback onTap; 12 | final _letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 13 | final _textStyle = new TextStyle( 14 | color: Style.primaryColor, 15 | fontSize: 12.0, 16 | ); 17 | final _padding = new EdgeInsets.symmetric(vertical: 2.0, horizontal: 5.0); 18 | 19 | @override 20 | build(BuildContext context) { 21 | var column = new Column( 22 | mainAxisSize: MainAxisSize.min, 23 | children: [], 24 | ); 25 | for (var letter in _letters) { 26 | column.children.add( 27 | new GestureDetector( 28 | child: new Padding( 29 | padding: _padding, 30 | child: new Text( 31 | letter, 32 | style: _textStyle, 33 | ), 34 | ), 35 | onTap: () => onTap(letter), 36 | ) 37 | ); 38 | } 39 | return column; 40 | } 41 | } -------------------------------------------------------------------------------- /flutter_elm_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/components/rating_star.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RatingStar extends StatelessWidget { 4 | RatingStar(num rating) : _rating = rating; 5 | final num _rating; 6 | final Color _color = const Color(0xFFFF6000); 7 | final num _size = 13.0; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | var starsRow = new Row( 12 | children: [], 13 | ); 14 | var starCount = _rating.floor(); 15 | var halfCount = (_rating % 1).ceil(); 16 | var emptyCount = 5 - starCount - halfCount; 17 | var iconList = []; 18 | for (var i = 0; i < starCount; i++) { 19 | iconList.add(Icons.star); 20 | } 21 | for (var i = 0; i < halfCount; i++) { 22 | iconList.add(Icons.star_half); 23 | } 24 | for (var i = 0; i < emptyCount; i++) { 25 | iconList.add(Icons.star_border); 26 | } 27 | for (var icon in iconList) { 28 | starsRow.children.add( 29 | new Icon( 30 | icon, 31 | size: _size, 32 | color: _color, 33 | ) 34 | ); 35 | } 36 | return new Row( 37 | children: [ 38 | starsRow, 39 | new Container( 40 | margin: new EdgeInsets.symmetric(horizontal: 5.0), 41 | child: new Text( 42 | _rating.toString(), 43 | style: new TextStyle( 44 | color: const Color(0xFFFF6000), 45 | fontSize: 13.0, 46 | ), 47 | ), 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 今年看见了Google新出的跨端框架flutter,了解之后十分感兴趣,所以就想着写一个项目用来学习这个框架。 4 | 5 | 因为是利用业余时间来做的,所以呢暂时项目还在进行中。 6 | 7 | 这个项目的素材是仿照vue中一个比较出名的样例项目,地址:https://github.com/bailicangdu/vue2-elm 。 8 | 9 | ## 项目运行 10 | 11 | For help getting started with Flutter, view our online 12 | [documentation](http://flutter.io/). 13 | 14 | ## 当前的Flutter版本 15 | 16 | 0.4.4 17 | 18 | # 目标功能 19 | - [x] 定位功能 -- 完成 20 | - [x] 选择城市 -- 完成 21 | - [x] 搜索地址 -- 完成 22 | - [x] 展示所选地址附近商家列表 -- 完成 23 | - [x] 搜索美食,餐馆 -- 完成 24 | - [x] 根据距离、销量、评分、特色菜、配送方式等进行排序和筛选 -- 完成 25 | - [ ] 餐馆食品列表页 26 | - [ ] 购物车功能 27 | - [ ] 店铺评价页面 28 | - [ ] 单个食品详情页面 29 | - [ ] 商家详情页 30 | - [x] 登录、注册 -- 完成 31 | - [ ] 修改密码 32 | - [ ] 个人中心 33 | - [ ] 下单功能 34 | - [ ] 订单列表 35 | - [ ] 订单详情 36 | - [ ] 添加、删除、修改收货地址 37 | - [ ] 帐户信息 38 | - [ ] 服务中心 39 | - [ ] 红包 40 | - [ ] 上传头像 41 | 42 | # 部分截图 43 | 44 | ### 地址选择页 45 | 46 | 47 | 48 | ### 商铺列表页 49 | 50 | 51 | 52 | ### 商铺筛选页 53 | 54 | 55 | 56 | ### 搜索页 57 | 58 | 59 | 60 | ### 登录页 61 | 62 | 63 | 64 | ### 个人中心 65 | 66 | 67 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 27 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 26 | applicationId "com.yourcompany.flutterelm" 27 | minSdkVersion 16 28 | targetSdkVersion 27 29 | versionCode 1 30 | versionName "1.0" 31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | release { 36 | // TODO: Add your own signing config for the release build. 37 | // Signing with the debug keys for now, so `flutter run --release` works. 38 | signingConfig signingConfigs.debug 39 | } 40 | } 41 | } 42 | 43 | flutter { 44 | source '../..' 45 | } 46 | 47 | dependencies { 48 | testImplementation 'junit:junit:4.12' 49 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 51 | } 52 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file,seperator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=seperator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname,:path=>podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Flutter Pods 31 | generated_xcode_build_settings = parse_KV_file("./Flutter/Generated.xcconfig") 32 | if generated_xcode_build_settings.empty? 33 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter build or flutter run is executed once first." 34 | end 35 | generated_xcode_build_settings.map{ |p| 36 | if p[:name]=='FLUTTER_FRAMEWORK_DIR' 37 | pod 'Flutter', :path => p[:path] 38 | end 39 | } 40 | 41 | # Plugin Pods 42 | plugin_pods = parse_KV_file("../.flutter-plugins") 43 | plugin_pods.map{ |p| 44 | pod p[:name], :path => File.expand_path("ios",p[:path]) 45 | } 46 | end 47 | 48 | post_install do |installer| 49 | installer.pods_project.targets.each do |target| 50 | target.build_configurations.each do |config| 51 | config.build_settings['ENABLE_BITCODE'] = 'NO' 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /lib/utils/http.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | class HttpUtils { 5 | static final _httpClient = new HttpClient(); 6 | static final List _cookies = []; 7 | 8 | static httpGet(Uri uri) async { 9 | HttpClientRequest request = await _httpClient.getUrl(uri); 10 | request.cookies.addAll(_cookies); 11 | return _httpRequest(request); 12 | } 13 | 14 | static httpPost(Uri uri) async { 15 | HttpClientRequest request = await _httpClient.postUrl(uri); 16 | request.cookies.addAll(_cookies); 17 | return _httpRequest(request); 18 | } 19 | 20 | static httpPostJson( 21 | Uri uri, { 22 | Map jsonBody = const {}, 23 | }) async { 24 | final String requestBody = json.encode(jsonBody); 25 | HttpClientRequest request = await _httpClient.postUrl(uri) 26 | ..headers.add(HttpHeaders.ACCEPT, ContentType.JSON) 27 | ..headers.contentType = ContentType.JSON 28 | ..headers.contentLength = requestBody.length 29 | ..headers.chunkedTransferEncoding = false; 30 | request.cookies.addAll(_cookies); 31 | if (jsonBody != null) { 32 | request.write(requestBody); 33 | } 34 | return _httpRequest(request); 35 | } 36 | 37 | static _httpRequest(HttpClientRequest request) async { 38 | HttpClientResponse response = await request.close(); 39 | if (response.headers.contentType.toString() != ContentType.JSON.toString()) { 40 | throw new UnsupportedError('Server returned an unsupported content type: ' 41 | '${response.headers.contentType} from ${request.uri}'); 42 | } 43 | if (response.statusCode == HttpStatus.OK) { 44 | if (response.cookies.isNotEmpty) { 45 | _cookies.clear(); 46 | _cookies.addAll(response.cookies); 47 | } 48 | var jsonStr = await response.transform(utf8.decoder).join(); 49 | var data = json.decode(jsonStr); 50 | return data; 51 | } else { 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/model/spec_food.dart: -------------------------------------------------------------------------------- 1 | class SpecFood { 2 | SpecFood.fromJson(Map json) 3 | : id = json['_id'], 4 | checkoutMode = json['checkout_mode'], 5 | foodId = json['food_id'], 6 | isEssential = json['is_essential'], 7 | itemId = json['item_id'], 8 | name = json['name'], 9 | originalPrice = json['original_price'], 10 | packingFee = json['packing_fee'], 11 | pinyinName = json['pinyin_name'], 12 | price = json['price'], 13 | promotionStock = json['promotion_stock'], 14 | recentPopularity = json['recent_popularity'], 15 | recentRating = json['recent_rating'], 16 | restaurantId = json['restaurant_id'], 17 | skuId = json['sku_id'], 18 | soldOut = json['sold_out'], 19 | specs = json['specs'], 20 | specsName = json['specs_name'], 21 | stock = json['stock']; 22 | 23 | String id; 24 | int checkoutMode; 25 | int foodId; 26 | bool isEssential; 27 | int itemId; 28 | String name; 29 | int originalPrice; 30 | num packingFee; 31 | String pinyinName; 32 | int price; 33 | int promotionStock; 34 | int recentPopularity; 35 | num recentRating; 36 | int restaurantId; 37 | int skuId; 38 | bool soldOut; 39 | List specs; 40 | String specsName; 41 | int stock; 42 | 43 | 44 | Map toJson() => { 45 | '_id': id, 46 | 'checkout_mode': checkoutMode, 47 | 'food_id': foodId, 48 | 'is_essential': isEssential, 49 | 'item_id': itemId, 50 | 'name': name, 51 | 'original_price': originalPrice, 52 | 'packing_fee': packingFee, 53 | 'pinyin_name': pinyinName, 54 | 'price': price, 55 | 'promotion_stock': promotionStock, 56 | 'recent_popularity': recentPopularity, 57 | 'recent_rating': recentRating, 58 | 'restaurant_id': restaurantId, 59 | 'sku_id': skuId, 60 | 'sold_out': soldOut, 61 | 'specs': specs, 62 | 'specs_name': specsName, 63 | 'stock': stock, 64 | }; 65 | } -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Flutter Ele 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Flutter Ele 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationAlwaysUsageDescription 28 | 本应用需要您的同意,才能始终访问位置 29 | NSLocationWhenInUseUsageDescription 30 | 本应用需要您的同意,才能访问位置 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UIRequiredDeviceCapabilities 36 | 37 | arm64 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/model/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | User.fromJson(Map json) 3 | : id = json['id'], 4 | userId = json['user_id'], 5 | username = json['username'], 6 | city = json['city'], 7 | registerTime = json['registe_time'], 8 | point = json['point'], 9 | mobile = json['mobile'], 10 | isMobileValid = json['is_mobile_valid'], 11 | isEmailValid = json['is_email_valid'], 12 | isActive = json['is_active'], 13 | giftAmount = json['gift_amount'], 14 | email = json['email'], 15 | deliveryCardExpireDays = json['delivery_card_expire_days'], 16 | currentInvoiceId = json['current_invoice_id'], 17 | currentAddressId = json['current_address_id'], 18 | brandMemberNew = json['brand_member_new'], 19 | balance = json['balance'], 20 | avatar = json['avatar'], 21 | version = json['__v']; 22 | 23 | int id; 24 | int userId; 25 | String username; 26 | String city; 27 | String registerTime; 28 | int point; 29 | String mobile; 30 | bool isMobileValid; 31 | bool isEmailValid; 32 | int isActive; 33 | int giftAmount; 34 | String email; 35 | int deliveryCardExpireDays; 36 | int currentInvoiceId; 37 | int currentAddressId; 38 | int brandMemberNew; 39 | int balance; 40 | String avatar; 41 | int version; 42 | 43 | Map toJson() => { 44 | 'id': id, 45 | 'user_id': userId, 46 | 'username': username, 47 | 'city': city, 48 | 'registe_time': registerTime, 49 | 'point': point, 50 | 'mobile': mobile, 51 | 'is_mobile_valid': isMobileValid, 52 | 'is_email_valid': isEmailValid, 53 | 'is_active': isActive, 54 | 'gift_amount': giftAmount, 55 | 'email': email, 56 | 'delivery_card_expire_days': deliveryCardExpireDays, 57 | 'current_invoice_id': currentInvoiceId, 58 | 'current_address_id': currentAddressId, 59 | 'brand_member_new': brandMemberNew, 60 | 'balance': balance, 61 | 'avatar': avatar, 62 | '__v': version, 63 | }; 64 | } -------------------------------------------------------------------------------- /lib/components/foot_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../routes/routes.dart'; 4 | import '../store/store.dart'; 5 | 6 | class FootBar extends StatelessWidget { 7 | FootBar({int currentIndex = 0}) : currentIndex = currentIndex; 8 | 9 | final int currentIndex; 10 | @override 11 | Widget build(BuildContext context) { 12 | return new BottomNavigationBar( 13 | onTap: (index) => go(context, index), 14 | currentIndex: currentIndex, 15 | fixedColor: Style.primaryColor, 16 | type: BottomNavigationBarType.fixed, 17 | items: [ 18 | new BottomNavigationBarItem( 19 | icon: new Icon( 20 | Icons.store, 21 | ), 22 | title: new Text('外卖'), 23 | ), 24 | new BottomNavigationBarItem( 25 | icon: new Icon( 26 | Icons.location_searching, 27 | ), 28 | title: new Text('搜索'), 29 | ), 30 | new BottomNavigationBarItem( 31 | icon: new Icon( 32 | Icons.shopping_cart, 33 | ), 34 | title: new Text('订单'), 35 | ), 36 | new BottomNavigationBarItem( 37 | icon: new Icon( 38 | Icons.person, 39 | ), 40 | title: new Text('我的'), 41 | ), 42 | ], 43 | ); 44 | } 45 | 46 | go(BuildContext context, int index) { 47 | if (index == currentIndex) return; 48 | String geoHash = store.state.geoHash; 49 | if (geoHash.isEmpty) { 50 | geoHash = '0,0'; 51 | } 52 | String path = ''; 53 | switch(index) { 54 | case 0: 55 | path = '/msite/$geoHash'; 56 | break; 57 | case 1: 58 | path = '/search/$geoHash'; 59 | break; 60 | case 2: 61 | path = '/order'; 62 | break; 63 | case 3: 64 | path = '/profile'; 65 | break; 66 | } 67 | if (path.length > 0) { 68 | print('go to: $path'); 69 | Routes.router.navigateTo( 70 | context, 71 | path, 72 | replace: true, 73 | ); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_elm 2 | description: A new Flutter project. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | shared_preferences: ^0.3.3 11 | cupertino_icons: ^0.1.0 12 | location: ^1.1.7 13 | fluro: ^1.3.0 14 | flutter_redux: ^0.5.0 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://www.dartlang.org/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # The following line ensures that the Material Icons font is 27 | # included with your application, so that you can use the icons in 28 | # the material Icons class. 29 | uses-material-design: true 30 | 31 | # To add assets to your application, add an assets section, like this: 32 | # assets: 33 | # - images/a_dot_burr.jpeg 34 | # - images/a_dot_ham.jpeg 35 | 36 | # An image asset can refer to one or more resolution-specific "variants", see 37 | # https://flutter.io/assets-and-images/#resolution-aware. 38 | 39 | # For details regarding adding assets from package dependencies, see 40 | # https://flutter.io/assets-and-images/#from-packages 41 | 42 | # To add custom fonts to your application, add a fonts section here, 43 | # in this "flutter" section. Each entry in this list should have a 44 | # "family" key with the font family name, and a "fonts" key with a 45 | # list giving the asset and other descriptors for the font. For 46 | # example: 47 | # fonts: 48 | # - family: Schyler 49 | # fonts: 50 | # - asset: fonts/Schyler-Regular.ttf 51 | # - asset: fonts/Schyler-Italic.ttf 52 | # style: italic 53 | # - family: Trajan Pro 54 | # fonts: 55 | # - asset: fonts/TrajanPro.ttf 56 | # - asset: fonts/TrajanPro_Bold.ttf 57 | # weight: 700 58 | # 59 | # For details regarding fonts from package dependencies, 60 | # see https://flutter.io/custom-fonts/#from-packages 61 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/model/food.dart: -------------------------------------------------------------------------------- 1 | import 'spec_food.dart'; 2 | 3 | class Food { 4 | Food.fromJson(Map json) 5 | : id = json['_id'], 6 | activity = json['activity'], 7 | attributes = json['attributes'], 8 | attrs = json['attrs'], 9 | categoryId = json['category_id'], 10 | description = json['description'], 11 | displayTimes = json['display_times'], 12 | imagePath = json['image_path'], 13 | isEssential = json['is_essential'], 14 | isFeatured = json['is_featured'], 15 | itemId = json['item_id'], 16 | monthSales = json['month_sales'], 17 | name = json['name'], 18 | pinyinName = json['pinyin_name'], 19 | rating = json['rating'], 20 | ratingCount = json['rating_count'], 21 | restaurantId = json['restaurant_id'], 22 | satisfyCount = json['satisfy_count'], 23 | satisfyRate = json['satisfy_rate'], 24 | serverUtc = json['server_utc'], 25 | specifications = json['specifications'], 26 | tips = json['tips'], 27 | version = json['__v'], 28 | specFoods = (json['specfoods'] as List).map((item) => new SpecFood.fromJson(item)).toList(); 29 | 30 | String id; 31 | Map activity; 32 | List attributes; 33 | List attrs; 34 | int categoryId; 35 | String description; 36 | List displayTimes; 37 | String imagePath; 38 | bool isEssential; 39 | int isFeatured; 40 | int itemId; 41 | int monthSales; 42 | String name; 43 | String pinyinName; 44 | num rating; 45 | int ratingCount; 46 | int restaurantId; 47 | int satisfyCount; 48 | int satisfyRate; 49 | String serverUtc; 50 | List specifications; 51 | String tips; 52 | int version; 53 | List specFoods; 54 | 55 | Map toJson() => { 56 | '_id': id, 57 | 'activity': activity, 58 | 'attributes': attributes, 59 | 'attrs': attrs, 60 | 'category_id': categoryId, 61 | 'description': description, 62 | 'display_times': displayTimes, 63 | 'image_path': imagePath, 64 | 'is_essential': isEssential, 65 | 'is_featured': isFeatured, 66 | 'item_id': itemId, 67 | 'month_sales': monthSales, 68 | 'name': name, 69 | 'pinyin_name': pinyinName, 70 | 'rating': rating, 71 | 'rating_count': ratingCount, 72 | 'restaurant_id': restaurantId, 73 | 'satisfy_count': satisfyCount, 74 | 'satisfy_rate': satisfyRate, 75 | 'server_utc': serverUtc, 76 | 'specifications': specifications, 77 | 'tips': tips, 78 | '__v': version, 79 | }; 80 | } -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/utils/local_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import 'dart:convert'; 3 | import '../model/place.dart'; 4 | import '../model/user.dart'; 5 | 6 | class LocalStorage { 7 | static const String PlaceHistoryKey = 'PlaceHistoryKey'; 8 | static const String UserKey = 'UserKey'; 9 | static const String SearchHistoryKey = 'SearchHistoryKey'; 10 | 11 | static getPlaceHistory() async { 12 | List results = []; 13 | try { 14 | SharedPreferences prefs = await SharedPreferences.getInstance(); 15 | var jsonStr = prefs.getString(PlaceHistoryKey); 16 | if (jsonStr != 'null') { 17 | var data = json.decode(jsonStr) as List; 18 | results = data.map((item) { 19 | return new Place.fromJson(item); 20 | }).toList(); 21 | } 22 | } catch (e) { 23 | print('getPlaceHistory error: $e'); 24 | } 25 | return results; 26 | } 27 | 28 | static setPlaceHistory(List places) async { 29 | var result = true; 30 | try { 31 | SharedPreferences prefs = await SharedPreferences.getInstance(); 32 | prefs.setString(PlaceHistoryKey, json.encode(places)); 33 | } catch (e) { 34 | result = false; 35 | print('setPlaceHistory error: $e'); 36 | } 37 | return result; 38 | } 39 | 40 | static getUser() async { 41 | User user; 42 | try { 43 | SharedPreferences prefs = await SharedPreferences.getInstance(); 44 | var jsonStr = prefs.getString(UserKey); 45 | if (jsonStr != 'null') { 46 | user = new User.fromJson(json.decode(jsonStr)); 47 | } 48 | } catch (e) { 49 | print('getUser error: $e'); 50 | } 51 | return user; 52 | } 53 | 54 | static setUser(User user) async { 55 | var result = true; 56 | try { 57 | SharedPreferences prefs = await SharedPreferences.getInstance(); 58 | prefs.setString(UserKey, json.encode(user)); 59 | } catch (e) { 60 | result = false; 61 | print('setUser error: $e'); 62 | } 63 | return result; 64 | } 65 | 66 | static getSearchHistory() async { 67 | List history = []; 68 | try { 69 | SharedPreferences prefs = await SharedPreferences.getInstance(); 70 | var jsonStr = prefs.getString(SearchHistoryKey); 71 | if (jsonStr != null) { 72 | (json.decode(jsonStr) as List).forEach((item) => history.add(item)); 73 | } 74 | } catch (e) { 75 | print('getSearchHistory error: $e'); 76 | } 77 | return history; 78 | } 79 | 80 | static setSearchHistory(List history) async { 81 | var result = true; 82 | try { 83 | SharedPreferences prefs = await SharedPreferences.getInstance(); 84 | prefs.setString(SearchHistoryKey, json.encode(history)); 85 | } catch (e) { 86 | result = false; 87 | print('setSearchHistory error: $e'); 88 | } 89 | return result; 90 | } 91 | } -------------------------------------------------------------------------------- /lib/routes/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluro/fluro.dart'; 3 | import '../page/home.dart'; 4 | import '../page/login.dart'; 5 | import '../page/city.dart'; 6 | import '../page/msite.dart'; 7 | import '../page/search.dart'; 8 | import '../page/order.dart'; 9 | import '../page/profile.dart'; 10 | import '../page/food.dart'; 11 | import '../page/shop.dart'; 12 | 13 | class Routes { 14 | static final router = new Router(); 15 | 16 | static void configureRoutes() { 17 | router.define('/home', handler: new Handler( 18 | handlerFunc: (BuildContext context, Map> params) { 19 | return new Home(); 20 | }, 21 | )); 22 | router.define('/login', handler: new Handler( 23 | handlerFunc: (BuildContext context, Map> params) { 24 | return new Login(); 25 | }, 26 | )); 27 | router.define('/city/:id', handler: new Handler( 28 | handlerFunc: (BuildContext context, Map> params) { 29 | return new CityPage(int.parse(params['id'][0])); 30 | }, 31 | )); 32 | router.define('/msite/:geohash', handler: new Handler( 33 | handlerFunc: (BuildContext context, Map> params) { 34 | var splits = params['geohash'][0].split(','); 35 | return new MSite(num.tryParse(splits[0]), num.tryParse(splits[1])); 36 | }, 37 | )); 38 | router.define('/search/:geohash', handler: new Handler( 39 | handlerFunc: (BuildContext context, Map> params) { 40 | return new Search(params['geohash'][0]); 41 | }, 42 | )); 43 | router.define('/order', handler: new Handler( 44 | handlerFunc: (BuildContext context, Map> params) { 45 | return new Order(); 46 | }, 47 | )); 48 | router.define('/profile', handler: new Handler( 49 | handlerFunc: (BuildContext context, Map> params) { 50 | return new Profile(); 51 | }, 52 | )); 53 | router.define('/food/:categoryId/:title/:geohash', handler: new Handler( 54 | handlerFunc: (BuildContext context, Map> params) { 55 | var splits = params['geohash'][0].split(','); 56 | return new Food( 57 | title: params['title'][0], 58 | restaurantCategoryId: params['categoryId'][0], 59 | longitude: num.tryParse(splits[0]), 60 | latitude: num.tryParse(splits[1]), 61 | ); 62 | }, 63 | )); 64 | router.define('/shop/:geohash/:id', handler: new Handler( 65 | handlerFunc: (BuildContext context, Map> params) { 66 | var splits = params['geohash'][0].split(','); 67 | return new Shop( 68 | shopId: params['id'][0], 69 | longitude: num.tryParse(splits[0]), 70 | latitude: num.tryParse(splits[1]), 71 | ); 72 | }, 73 | )); 74 | } 75 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/model/restaurant.dart: -------------------------------------------------------------------------------- 1 | import 'support.dart'; 2 | import 'delivery_mode.dart'; 3 | 4 | class Restaurant { 5 | Restaurant.fromJson(Map json) 6 | : id = json['id'], 7 | name = json['name'], 8 | longitude = json['longitude'], 9 | latitude = json['latitude'], 10 | location = (json['location'] as List).map((item) => item as num).toList(), 11 | address = json['address'], 12 | phone = json['phone'], 13 | category = json['category'], 14 | supports = (json['supports'] as List).map((item) => new Support.fromJson(item)).toList(), 15 | status = json['status'], 16 | recentOrderNum = json['recent_order_num'], 17 | ratingCount = json['rating_count'], 18 | rating = json['rating'], 19 | promotionInfo = json['promotion_info'], 20 | piecewiseAgentFee = json['piecewise_agent_fee'], 21 | openingHours = (json['opening_hours'] as List).map((item) => item as String).toList(), 22 | license = json['license'], 23 | isNew = json['is_new'], 24 | isPremium = json['is_premium'], 25 | imagePath = json['image_path'], 26 | floatMinimumOrderAmount = json['float_minimum_order_amount'], 27 | floatDeliveryFee = json['float_delivery_fee'], 28 | distance = json['distance'], 29 | orderLeadTime = json['order_lead_time'], 30 | description = json['description'], 31 | deliveryMode = json['delivery_mode'] == null ? null : new DeliveryMode.fromJson(json['delivery_mode']), 32 | activities = json['activities'], 33 | version = json['__v']; 34 | 35 | int id; 36 | String name; 37 | num longitude; 38 | num latitude; 39 | List location; 40 | String address; 41 | String phone; 42 | String category; 43 | List supports; 44 | int status; 45 | int recentOrderNum; 46 | int ratingCount; 47 | num rating; 48 | String promotionInfo; 49 | Map piecewiseAgentFee; 50 | List openingHours; 51 | Map license; 52 | bool isNew; 53 | bool isPremium; 54 | String imagePath; 55 | num floatMinimumOrderAmount; 56 | num floatDeliveryFee; 57 | String distance; 58 | String orderLeadTime; 59 | String description; 60 | DeliveryMode deliveryMode; 61 | List activities; 62 | int version; 63 | 64 | Map toJson() => { 65 | 'id': id, 66 | 'name': name, 67 | 'longitude': longitude, 68 | 'latitude': latitude, 69 | 'location': location, 70 | 'address': address, 71 | 'phone': phone, 72 | 'category': category, 73 | 'supports': supports, 74 | 'status': status, 75 | 'recent_order_num': recentOrderNum, 76 | 'rating_count': ratingCount, 77 | 'rating': rating, 78 | 'promotion_info': promotionInfo, 79 | 'piecewise_agent_fee': piecewiseAgentFee, 80 | 'opening_hours': openingHours, 81 | 'license': license, 82 | 'is_new': isNew, 83 | 'is_premium': isPremium, 84 | 'image_path': imagePath, 85 | 'float_minimum_order_amount': floatMinimumOrderAmount, 86 | 'float_delivery_fee': floatDeliveryFee, 87 | 'distance': distance, 88 | 'order_lead_time': orderLeadTime, 89 | 'description': description, 90 | 'delivery_mode': deliveryMode, 91 | 'activities': activities, 92 | '__v': version, 93 | }; 94 | } -------------------------------------------------------------------------------- /lib/components/shopping_cart.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | 4 | class ShoppingCart extends StatefulWidget { 5 | @override 6 | State createState() { 7 | return new ShoppingCartState(); 8 | } 9 | } 10 | 11 | class ShoppingCartState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return new SafeArea( 15 | child: new Stack( 16 | overflow: Overflow.visible, 17 | children: [ 18 | new Container( 19 | height: 50.0, 20 | child: new Row( 21 | crossAxisAlignment: CrossAxisAlignment.stretch, 22 | children: [ 23 | new Expanded( 24 | flex: 2, 25 | child: new Container( 26 | padding: new EdgeInsets.symmetric(vertical: 5.0).copyWith(left: 100.0), 27 | color: new Color(0xff3d3d3f), 28 | child: new Column( 29 | crossAxisAlignment: CrossAxisAlignment.start, 30 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 31 | children: [ 32 | new Text( 33 | '¥0.00', 34 | style: new TextStyle( 35 | fontSize: 17.0, 36 | color: Style.backgroundColor, 37 | ), 38 | ), 39 | new Text( 40 | ' 配送费¥5', 41 | style: new TextStyle( 42 | fontSize: 10.0, 43 | color: Style.backgroundColor, 44 | ), 45 | ), 46 | ], 47 | ), 48 | ), 49 | ), 50 | new Expanded( 51 | flex: 1, 52 | child: new Container( 53 | color: new Color(0xff535356), 54 | child: new Center( 55 | child: new Text( 56 | '还差¥20起送', 57 | style: new TextStyle( 58 | fontSize: 15.0, 59 | color: Style.backgroundColor, 60 | ), 61 | ), 62 | ), 63 | ), 64 | ) 65 | ], 66 | ), 67 | ), 68 | new Positioned( 69 | top: -15.0, 70 | left: 30.0, 71 | child: new Container( 72 | padding: new EdgeInsets.all(5.0), 73 | decoration: new BoxDecoration( 74 | color: new Color(0xff3d3d3f), 75 | shape: BoxShape.circle, 76 | border: new Border.all( 77 | color: new Color(0xff444444), 78 | width: 5.0, 79 | ), 80 | ), 81 | child: new Center( 82 | child: new Icon( 83 | Icons.shopping_cart, 84 | color: Style.backgroundColor, 85 | size: 36.0, 86 | ), 87 | ), 88 | ), 89 | ), 90 | ], 91 | ), 92 | ); 93 | } 94 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/components/shop_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../model/restaurant.dart'; 3 | import '../components/shop_item.dart'; 4 | import '../style/style.dart'; 5 | import '../utils/api.dart'; 6 | import '../routes/routes.dart'; 7 | 8 | class ShopList extends StatefulWidget { 9 | ShopList(num longitude, num latitude, String restaurantCategoryId, 10 | {String restaurantCategoryIds = '', String sortByType = '', String deliveryMode = '', List supportsIds = const []}) 11 | : longitude = longitude, 12 | latitude = latitude, 13 | geoHash = '$longitude,$latitude', 14 | restaurantCategoryId = restaurantCategoryId, 15 | restaurantCategoryIds = restaurantCategoryIds, 16 | sortByType = sortByType, 17 | deliveryMode = deliveryMode, 18 | supportsIds = supportsIds, 19 | assert(longitude != null), 20 | assert(latitude != null); 21 | 22 | final num longitude; 23 | final num latitude; 24 | final String geoHash; 25 | final String restaurantCategoryId; 26 | final String restaurantCategoryIds; 27 | final String sortByType; 28 | final String deliveryMode; 29 | final List supportsIds; 30 | 31 | @override 32 | State createState() { 33 | return new ShopListState(); 34 | } 35 | } 36 | 37 | class ShopListState extends State { 38 | final _restaurantLimit = 10; 39 | 40 | List _restaurants = []; 41 | bool _isLoading = false; 42 | bool _loadingFinish = false; 43 | int _offset = 0; 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | getRestaurants(); 49 | } 50 | 51 | @override 52 | void didUpdateWidget(ShopList old) { 53 | super.didUpdateWidget(old); 54 | _restaurants = []; 55 | _offset = 0; 56 | getRestaurants(); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | var itemCount = _restaurants.length; 62 | if (_loadingFinish) { 63 | itemCount += 1; 64 | } 65 | return new ListView.builder( 66 | itemCount: itemCount, 67 | itemBuilder: (context, i) { 68 | if (_loadingFinish && i == itemCount - 1) { 69 | return new Container( 70 | height: 50.0, 71 | color: Style.backgroundColor, 72 | child: new Center( 73 | child: new Text( 74 | '没有更多了', 75 | style: Style.textStyle, 76 | ), 77 | ), 78 | ); 79 | } else { 80 | if (!_loadingFinish && 81 | !_isLoading && 82 | i >= _restaurants.length - 2) { 83 | _offset += _restaurantLimit; 84 | getRestaurants(); 85 | } 86 | return new ShopItem(_restaurants[i], _goShop); 87 | } 88 | }); 89 | } 90 | 91 | getRestaurants() async { 92 | _isLoading = true; 93 | List restaurants = await Api.getRestaurants( 94 | widget.latitude, 95 | widget.longitude, 96 | _offset, 97 | limit: _restaurantLimit, 98 | restaurantCategoryId: widget.restaurantCategoryId, 99 | restaurantCategoryIds: widget.restaurantCategoryIds, 100 | orderBy: widget.sortByType, 101 | deliveryMode: widget.deliveryMode, 102 | supportIds: widget.supportsIds, 103 | ); 104 | if (restaurants.length < _restaurantLimit) { 105 | _loadingFinish = true; 106 | } 107 | setState(() { 108 | _restaurants.addAll(restaurants); 109 | }); 110 | _isLoading = false; 111 | } 112 | 113 | void _goShop(Restaurant restaurant) { 114 | Routes.router.navigateTo( 115 | context, 116 | '/shop/${widget.geoHash}/${restaurant.id}', 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/components/form_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../utils/api.dart'; 4 | import 'dart:convert'; 5 | import 'dart:typed_data'; 6 | 7 | enum FormInputType { 8 | input, 9 | password, 10 | authCode, 11 | } 12 | 13 | class FormInput extends StatefulWidget { 14 | FormInput({ 15 | Key key, 16 | FormInputType type = FormInputType.input, 17 | String hintText = '', 18 | FormFieldSetter onSaved, 19 | bool obscureText = false, 20 | FormFieldValidator validator, 21 | }) 22 | : type = type, 23 | hintText = hintText, 24 | onSaved = onSaved, 25 | obscureText = obscureText, 26 | validator = validator, super(key: key); 27 | 28 | final FormInputType type; 29 | final String hintText; 30 | final FormFieldSetter onSaved; 31 | final bool obscureText; 32 | final FormFieldValidator validator; 33 | 34 | @override 35 | createState() => new FormInputState(); 36 | } 37 | 38 | class FormInputState extends State { 39 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding, vertical: 15.0); 40 | 41 | bool _obscureText = false; 42 | Uint8List _authCodeImg; 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | setState(() { 48 | _obscureText = widget.obscureText; 49 | }); 50 | loadAuthCodeImg(); 51 | } 52 | 53 | void loadAuthCodeImg() async { 54 | if (widget.type == FormInputType.authCode) { 55 | String code = await Api.getAuthCode(); 56 | var splits = code.split(','); 57 | try { 58 | if (splits.length > 1 && mounted) { 59 | setState(() => _authCodeImg = base64.decode(splits[1])); 60 | } 61 | } catch (e) { 62 | print('decode base64 auth code img error: $e'); 63 | } 64 | } 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | Widget suffixIcon; 70 | if (widget.type == FormInputType.password) { 71 | suffixIcon = new Switch( 72 | value: !_obscureText, 73 | onChanged: (val) { 74 | setState(() { 75 | _obscureText = !val; 76 | }); 77 | }, 78 | ); 79 | } else if (widget.type == FormInputType.authCode) { 80 | var row = new Row( 81 | mainAxisSize: MainAxisSize.min, 82 | children: [], 83 | ); 84 | if (_authCodeImg != null) { 85 | row.children.add( 86 | new Image.memory(_authCodeImg), 87 | ); 88 | } 89 | row.children.add( 90 | new Padding( 91 | padding: new EdgeInsets.only(left: 10.0), 92 | child: new Column( 93 | children: [ 94 | new Text('看不清'), 95 | new GestureDetector( 96 | child: new Text( 97 | '换一张', 98 | style: new TextStyle( 99 | color: Style.primaryColor, 100 | ), 101 | ), 102 | onTap: loadAuthCodeImg, 103 | ) 104 | ], 105 | ), 106 | ), 107 | ); 108 | suffixIcon = row; 109 | } 110 | return new TextFormField( 111 | decoration: new InputDecoration( 112 | hintText: widget.hintText, 113 | suffixIcon: suffixIcon, 114 | contentPadding: _gPadding, 115 | hintStyle: Style.textStyle, 116 | border: new UnderlineInputBorder( 117 | borderSide: new BorderSide( 118 | color: Style.borderColor, 119 | width: 0.5, 120 | ) 121 | ) 122 | ), 123 | obscureText: _obscureText, 124 | onSaved: widget.onSaved, 125 | validator: widget.validator, 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/components/carousel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import '../style/style.dart'; 4 | 5 | class Carousel extends StatefulWidget { 6 | Carousel({ 7 | double height = 200.0, 8 | List pages, 9 | bool autoPlay, 10 | Duration duration = const Duration(seconds: 2), 11 | Duration animationDuration = const Duration(milliseconds: 1000), 12 | }) 13 | : height = height, 14 | pages = pages, 15 | autoPlay = autoPlay, 16 | duration = duration, 17 | animationDuration = animationDuration; 18 | 19 | final double height; 20 | final List pages; 21 | final bool autoPlay; 22 | final Duration duration; 23 | final Duration animationDuration; 24 | 25 | @override 26 | createState() => new CarouselState(); 27 | } 28 | 29 | class CarouselState extends State { 30 | final _pageController = new PageController(); 31 | 32 | Timer _timer; 33 | int _currentPage = 0; 34 | bool reverse = false; 35 | GlobalKey _indicatorStateKey = new GlobalKey(); 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | if (widget.autoPlay) { 41 | _timer = new Timer.periodic(widget.duration, (timer) { 42 | _pageController.animateToPage(_currentPage, 43 | duration: widget.animationDuration, curve: Curves.linear); 44 | if (!reverse) { 45 | _currentPage += 1; 46 | if (_currentPage == widget.pages.length) { 47 | _currentPage -= 1; 48 | reverse = true; 49 | } 50 | } else { 51 | _currentPage -= 1; 52 | if (_currentPage < 0) { 53 | _currentPage += 1; 54 | reverse = false; 55 | } 56 | } 57 | }); 58 | } 59 | } 60 | 61 | @override 62 | void dispose() { 63 | super.dispose(); 64 | if (_timer != null) { 65 | _timer.cancel(); 66 | } 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return new Stack( 72 | children: [ 73 | new Container( 74 | height: widget.height, 75 | color: Style.backgroundColor, 76 | child: new PageView( 77 | controller: _pageController, 78 | children: widget.pages, 79 | onPageChanged: (index) { 80 | _currentPage = index; 81 | _indicatorStateKey.currentState.changeIndex(index); 82 | }, 83 | ), 84 | ), 85 | new Positioned( 86 | left: 0.0, 87 | right: 0.0, 88 | bottom: 0.0, 89 | child: new Align( 90 | child: new Indicator( 91 | key: _indicatorStateKey, 92 | count: widget.pages.length, 93 | ), 94 | alignment: Alignment.center, 95 | ), 96 | ), 97 | ], 98 | ); 99 | } 100 | } 101 | 102 | class Indicator extends StatefulWidget { 103 | Indicator({Key key, int count}) 104 | : count = count, 105 | super(key: key); 106 | 107 | final int count; 108 | 109 | @override 110 | createState() => new IndicatorState(); 111 | } 112 | 113 | class IndicatorState extends State { 114 | int _index = 0; 115 | 116 | changeIndex(int index) { 117 | setState(() { 118 | _index = index; 119 | }); 120 | } 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | var indicators = []; 125 | for (var i = 0; i < widget.count; ++i) { 126 | indicators.add(new Container( 127 | width: 5.0, 128 | height: 5.0, 129 | decoration: new BoxDecoration( 130 | borderRadius: new BorderRadius.all(new Radius.circular(5.0)), 131 | color: _index == i ? Style.primaryColor : Style.borderColor, 132 | ), 133 | )); 134 | } 135 | return new SizedBox( 136 | width: widget.count * 15.0, 137 | height: 30.0, 138 | child: new Row( 139 | children: indicators, 140 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 141 | ), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/components/head_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../routes/routes.dart'; 3 | import '../model/user.dart'; 4 | import '../style/style.dart'; 5 | import '../store/store.dart'; 6 | 7 | enum HeadBarLeadingType { 8 | none, 9 | goBack, 10 | search, 11 | } 12 | 13 | class HeadBar extends StatefulWidget implements PreferredSizeWidget { 14 | HeadBar({ 15 | HeadBarLeadingType leadingType: HeadBarLeadingType.none, 16 | String title: '', 17 | bool centerTitle: true, 18 | bool showUser: true, 19 | GestureTapCallback titleOnTap, 20 | PreferredSizeWidget bottom, 21 | Color backgroundColor, 22 | }) 23 | : leadingType = leadingType, 24 | title = title, 25 | centerTitle = centerTitle, 26 | showUser = showUser, 27 | preferredSize = new Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), 28 | bottom=bottom, 29 | titleOnTap=titleOnTap, 30 | backgroundColor = backgroundColor; 31 | final String title; 32 | final bool centerTitle; 33 | final HeadBarLeadingType leadingType; 34 | final bool showUser; 35 | final GestureTapCallback titleOnTap; 36 | final PreferredSizeWidget bottom; 37 | final Color backgroundColor; 38 | 39 | @override 40 | createState() => new HeadBarState(); 41 | 42 | @override 43 | final Size preferredSize; 44 | } 45 | 46 | class HeadBarState extends State { 47 | User _user; 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | _user = store.state.user; 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | final ThemeData themeData = Theme.of(context); 58 | Widget leading; 59 | if (widget.leadingType == HeadBarLeadingType.goBack) { 60 | leading = new IconButton( 61 | icon: new Icon(Icons.arrow_back_ios), 62 | onPressed: () { 63 | Navigator.of(context).pop(); 64 | }); 65 | } else if (widget.leadingType == HeadBarLeadingType.search) { 66 | leading = new IconButton( 67 | icon: new Icon(Icons.search), 68 | color: Style.backgroundColor, 69 | onPressed: _goSearch, 70 | ); 71 | } 72 | 73 | var actions = []; 74 | if (widget.showUser) { 75 | if (_user == null) { 76 | actions.add( 77 | new GestureDetector( 78 | onTap: () => _goLogin(context), 79 | child: new Container( 80 | padding: ButtonTheme 81 | .of(context) 82 | .padding, 83 | child: new Center( 84 | child: new Text( 85 | '登录|注册', 86 | style: themeData.primaryTextTheme.title, 87 | ), 88 | ), 89 | ), 90 | ), 91 | ); 92 | } else { 93 | actions.add( 94 | new Padding( 95 | padding: ButtonTheme 96 | .of(context) 97 | .padding, 98 | child: new GestureDetector( 99 | child: new Icon( 100 | Icons.person, 101 | ), 102 | onTap: _goProfile, 103 | ), 104 | ) 105 | ); 106 | } 107 | } 108 | 109 | return new AppBar( 110 | leading: leading, 111 | title: new GestureDetector( 112 | child: new Text( 113 | widget.title, 114 | overflow: TextOverflow.ellipsis, 115 | ), 116 | onTap: widget.titleOnTap, 117 | ), 118 | centerTitle: widget.centerTitle, 119 | automaticallyImplyLeading: false, 120 | actions: actions, 121 | bottom: widget.bottom, 122 | backgroundColor: widget.backgroundColor, 123 | ); 124 | } 125 | 126 | _goLogin(BuildContext context) { 127 | Routes.router.navigateTo(context, '/login'); 128 | } 129 | 130 | _goSearch() { 131 | String geoHash = store.state.geoHash; 132 | Routes.router.navigateTo(context, '/search/$geoHash'); 133 | } 134 | 135 | _goProfile() { 136 | Routes.router.navigateTo(context, '/profile'); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon-1.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-29.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "icon-29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "icon-40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "57x57", 47 | "idiom": "iphone", 48 | "filename": "icon-57.png", 49 | "scale": "1x" 50 | }, 51 | { 52 | "size": "57x57", 53 | "idiom": "iphone", 54 | "filename": "icon-57@2x.png", 55 | "scale": "2x" 56 | }, 57 | { 58 | "size": "60x60", 59 | "idiom": "iphone", 60 | "filename": "icon-60@2x.png", 61 | "scale": "2x" 62 | }, 63 | { 64 | "size": "60x60", 65 | "idiom": "iphone", 66 | "filename": "icon-60@3x.png", 67 | "scale": "3x" 68 | }, 69 | { 70 | "size": "20x20", 71 | "idiom": "ipad", 72 | "filename": "icon-20-ipad.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "20x20", 77 | "idiom": "ipad", 78 | "filename": "icon-20@2x-ipad.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "29x29", 83 | "idiom": "ipad", 84 | "filename": "icon-29-ipad.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "29x29", 89 | "idiom": "ipad", 90 | "filename": "icon-29@2x-ipad.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "40x40", 95 | "idiom": "ipad", 96 | "filename": "icon-40.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "40x40", 101 | "idiom": "ipad", 102 | "filename": "icon-40@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "50x50", 107 | "idiom": "ipad", 108 | "filename": "icon-50.png", 109 | "scale": "1x" 110 | }, 111 | { 112 | "size": "50x50", 113 | "idiom": "ipad", 114 | "filename": "icon-50@2x.png", 115 | "scale": "2x" 116 | }, 117 | { 118 | "size": "72x72", 119 | "idiom": "ipad", 120 | "filename": "icon-72.png", 121 | "scale": "1x" 122 | }, 123 | { 124 | "size": "72x72", 125 | "idiom": "ipad", 126 | "filename": "icon-72@2x.png", 127 | "scale": "2x" 128 | }, 129 | { 130 | "size": "76x76", 131 | "idiom": "ipad", 132 | "filename": "icon-76.png", 133 | "scale": "1x" 134 | }, 135 | { 136 | "size": "76x76", 137 | "idiom": "ipad", 138 | "filename": "icon-76@2x.png", 139 | "scale": "2x" 140 | }, 141 | { 142 | "size": "83.5x83.5", 143 | "idiom": "ipad", 144 | "filename": "icon-83.5@2x.png", 145 | "scale": "2x" 146 | }, 147 | { 148 | "size": "1024x1024", 149 | "idiom": "ios-marketing", 150 | "filename": "icon-1024.png", 151 | "scale": "1x" 152 | } 153 | ], 154 | } -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /lib/components/drop_down.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import '../style/style.dart'; 4 | 5 | 6 | const Duration _kDropDownSheetDuration = const Duration(milliseconds: 200); 7 | typedef Offset _GetOffsetCall(); 8 | 9 | class DropDown extends StatefulWidget { 10 | DropDown({ 11 | String text, 12 | Color color, 13 | Decoration decoration, 14 | _GetOffsetCall getOffset, 15 | Widget content, 16 | }) : text = text, 17 | color = color, 18 | decoration = decoration, 19 | getOffset = getOffset, 20 | content = content; 21 | final String text; 22 | final Color color; 23 | final Decoration decoration; 24 | final _GetOffsetCall getOffset; 25 | final Widget content; 26 | 27 | @override 28 | createState() => new DropDownState(); 29 | 30 | static AnimationController createAnimationController(TickerProvider vsync) { 31 | return new AnimationController( 32 | duration: _kDropDownSheetDuration, 33 | debugLabel: 'DropDownSheet', 34 | vsync: vsync, 35 | ); 36 | } 37 | } 38 | 39 | class DropDownState extends State { 40 | final _gPadding = new EdgeInsets.symmetric(vertical: 10.0); 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return new GestureDetector( 45 | child: new Container( 46 | padding: _gPadding, 47 | color: widget.color, 48 | decoration: widget.decoration, 49 | child: new Row( 50 | mainAxisAlignment: MainAxisAlignment.center, 51 | crossAxisAlignment: CrossAxisAlignment.center, 52 | children: [ 53 | new Text(widget.text), 54 | new Container( 55 | child: new Icon( 56 | Icons.arrow_drop_down, 57 | size: 20.0, 58 | ), 59 | ) 60 | ], 61 | ), 62 | ), 63 | onTap: _show, 64 | ); 65 | } 66 | 67 | void _show() { 68 | Offset offset = widget.getOffset(); 69 | _showDropDownSheet( 70 | context: context, 71 | builder: (context) { 72 | return new Stack( 73 | children: [ 74 | new Positioned( 75 | top: offset.dy, 76 | right: 0.0, 77 | bottom: 0.0, 78 | left: offset.dx, 79 | child: new GestureDetector( 80 | child: new Container( 81 | color: Colors.black54, 82 | ), 83 | onTap: () => Navigator.pop(context), 84 | ), 85 | ), 86 | new Positioned( 87 | top: 0.0, 88 | left: 0.0, 89 | right: 0.0, 90 | child: new Column( 91 | children: [ 92 | new GestureDetector( 93 | child: new Container( 94 | color: new Color(0x00000000), 95 | height: offset.dy, 96 | ), 97 | onTap: () { 98 | Navigator.pop(context); 99 | }, 100 | ), 101 | widget.content, 102 | ], 103 | ), 104 | ), 105 | ], 106 | ); 107 | }, 108 | ); 109 | } 110 | } 111 | 112 | class _DropDownSheetRoute extends PopupRoute { 113 | _DropDownSheetRoute({ 114 | this.builder, 115 | this.barrierLabel, 116 | RouteSettings settings, 117 | }) : super(settings: settings); 118 | 119 | final WidgetBuilder builder; 120 | 121 | @override 122 | Duration get transitionDuration => _kDropDownSheetDuration; 123 | 124 | @override 125 | bool get barrierDismissible => true; 126 | 127 | @override 128 | final String barrierLabel; 129 | 130 | @override 131 | Color get barrierColor => null; 132 | 133 | AnimationController _animationController; 134 | 135 | @override 136 | AnimationController createAnimationController() { 137 | assert(_animationController == null); 138 | _animationController = DropDown.createAnimationController(navigator.overlay); 139 | return _animationController; 140 | } 141 | 142 | @override 143 | Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { 144 | var dropDownSheet = new Material( 145 | color: new Color.fromRGBO(0, 0, 0, 0.0), 146 | child: new MediaQuery.removePadding( 147 | context: context, 148 | removeTop: true, 149 | child: new _DropDownSheet(route: this), 150 | ) 151 | ); 152 | return dropDownSheet; 153 | } 154 | } 155 | 156 | class _DropDownSheet extends StatefulWidget { 157 | const _DropDownSheet({ Key key, this.route }) : super(key: key); 158 | 159 | final _DropDownSheetRoute route; 160 | 161 | @override 162 | _DropDownSheetState createState() => new _DropDownSheetState(); 163 | } 164 | 165 | class _DropDownSheetState extends State<_DropDownSheet> { 166 | @override 167 | Widget build(BuildContext context) { 168 | return new AnimatedBuilder( 169 | animation: widget.route.animation, 170 | builder: (BuildContext context, Widget child) { 171 | return new ClipRect( 172 | child: widget.route.builder(context), 173 | ); 174 | } 175 | ); 176 | } 177 | } 178 | 179 | Future _showDropDownSheet({ 180 | BuildContext context, 181 | WidgetBuilder builder, 182 | }) { 183 | assert(context != null); 184 | assert(builder != null); 185 | return Navigator.push(context, new _DropDownSheetRoute( 186 | builder: builder, 187 | barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, 188 | )); 189 | } 190 | -------------------------------------------------------------------------------- /lib/page/login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../components/form_input.dart'; 4 | import '../components/button.dart'; 5 | import '../utils/api.dart'; 6 | import '../model/user.dart'; 7 | import '../utils/local_storage.dart'; 8 | import '../components/head_bar.dart'; 9 | import '../components/custom_dialog.dart'; 10 | import '../store/store.dart'; 11 | import '../store/app_action.dart'; 12 | 13 | class Login extends StatefulWidget { 14 | @override 15 | createState() => new LoginState(); 16 | } 17 | 18 | class LoginState extends State { 19 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding); 20 | final _gTopPadding = new EdgeInsets.only(top: 10.0); 21 | final _tipTextStyle = new TextStyle( 22 | color: Colors.red, 23 | ); 24 | final formKey = new GlobalKey(); 25 | final authCodeInputKey = new GlobalKey(); 26 | 27 | String _username; 28 | String _password; 29 | String _authCode; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return new Scaffold( 34 | appBar: new HeadBar( 35 | title: '登录', 36 | leadingType: HeadBarLeadingType.goBack, 37 | showUser: false, 38 | ), 39 | body: new ListView( 40 | children: [ 41 | new Container( 42 | margin: new EdgeInsets.only(top: 15.0), 43 | color: Style.backgroundColor, 44 | child: new Form( 45 | key: formKey, 46 | child: new Column( 47 | children: [ 48 | new FormInput( 49 | hintText: '账号', 50 | onSaved: (val) => _username = val, 51 | validator: (val) => val.length > 0 ? null : '请输入手机号/邮箱/用户名', 52 | ), 53 | new FormInput( 54 | type: FormInputType.password, 55 | hintText: '密码', 56 | onSaved: (val) => _password = val, 57 | obscureText: true, 58 | validator: (val) => val.length > 0 ? null : '请输入密码', 59 | ), 60 | new FormInput( 61 | key: authCodeInputKey, 62 | type: FormInputType.authCode, 63 | hintText: '验证码', 64 | onSaved: (val) => _authCode = val, 65 | validator: (val) => val.length > 0 ? null : '请输入验证码', 66 | ), 67 | ], 68 | ), 69 | ), 70 | ), 71 | new Padding( 72 | padding: _gPadding, 73 | child: new Column( 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [ 76 | new Padding( 77 | padding: _gTopPadding, 78 | child: new Text( 79 | '温馨提示:未注册过的账号,登录时将自动注册', 80 | style: _tipTextStyle, 81 | ), 82 | ), 83 | new Padding( 84 | padding: _gTopPadding, 85 | child: new Text( 86 | '注册过的用户可凭账号密码登录', 87 | style: _tipTextStyle, 88 | ), 89 | ), 90 | new Padding( 91 | padding: _gTopPadding, 92 | child: new Button( 93 | height: 40.0, 94 | text: new Text( 95 | '登录', 96 | style: new TextStyle( 97 | fontSize: 18.0, 98 | color: const Color(0xFFFFFFFF), 99 | ), 100 | ), 101 | onTap: _login, 102 | ), 103 | ), 104 | new Padding( 105 | padding: _gTopPadding, 106 | child: new Row( 107 | mainAxisAlignment: MainAxisAlignment.end, 108 | children: [ 109 | new GestureDetector( 110 | child: new Text( 111 | '重置密码?', 112 | style: new TextStyle( 113 | color: Style.primaryColor, 114 | ), 115 | ), 116 | ), 117 | ], 118 | ), 119 | ), 120 | ], 121 | ), 122 | ) 123 | ], 124 | ), 125 | backgroundColor: Style.emptyBackgroundColor, 126 | ); 127 | } 128 | 129 | _login() async { 130 | var form = formKey.currentState; 131 | if (form.validate()) { 132 | form.save(); 133 | var data = await Api.accountLogin(_username, _password, _authCode); 134 | if (data != null) { 135 | if (data['user_id'] != null) { 136 | try { 137 | var user = new User.fromJson(data); 138 | await LocalStorage.setUser(user); 139 | store.dispatch(new LoginAction(user)); 140 | Navigator.of(context).pop(); 141 | } catch(e) { 142 | showDialog( 143 | context: context, 144 | builder: (context) => new CustomDialog( 145 | title: '错误', 146 | content: '$e', 147 | ), 148 | ); 149 | } 150 | } else { 151 | showDialog( 152 | context: context, 153 | builder: (context) => new CustomDialog( 154 | title: '提示', 155 | content: data['message'], 156 | ), 157 | ); 158 | _loadAuthCode(); 159 | } 160 | } 161 | } 162 | } 163 | 164 | _loadAuthCode() { 165 | authCodeInputKey.currentState.loadAuthCodeImg(); 166 | } 167 | } -------------------------------------------------------------------------------- /lib/page/city.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../utils/api.dart'; 4 | import '../model/city.dart'; 5 | import '../model/place.dart'; 6 | import '../utils/local_storage.dart'; 7 | import '../routes/routes.dart'; 8 | import '../components/head_bar.dart'; 9 | import '../store/store.dart'; 10 | import '../store/app_action.dart'; 11 | 12 | class CityPage extends StatefulWidget { 13 | CityPage(int id) 14 | : id = id, 15 | assert(id != null); 16 | final int id; 17 | 18 | @override 19 | createState() => new CityState(); 20 | } 21 | 22 | class CityState extends State { 23 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding, vertical: 10.0); 24 | final _gMargin = new EdgeInsets.only(bottom: 10.0); 25 | final _textStyle = Style.textStyle; 26 | final _tipTextStyle = new TextStyle( 27 | color: const Color(0xFF9F9F9F), 28 | fontSize: 12.0, 29 | ); 30 | final _topBottomBorder = new Border( 31 | top: new BorderSide( 32 | color: Style.borderColor, 33 | ), 34 | bottom: new BorderSide( 35 | color: Style.borderColor, 36 | ), 37 | ); 38 | 39 | final _bottomBorder = new Border( 40 | bottom: new BorderSide( 41 | color: Style.borderColor, 42 | ), 43 | ); 44 | 45 | City _city; 46 | List _searchPlaces = []; 47 | List _historyPlaces = []; 48 | String _query = ''; 49 | 50 | @override 51 | void initState() { 52 | super.initState(); 53 | getCity(); 54 | getPlaceHistory(); 55 | } 56 | 57 | getCity() async { 58 | City city = await Api.getCityById(widget.id); 59 | setState(() { 60 | _city = city; 61 | }); 62 | } 63 | 64 | getPlaceHistory() async { 65 | List history = await LocalStorage.getPlaceHistory(); 66 | _historyPlaces = history.reversed.toList(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return new Scaffold( 72 | appBar: new HeadBar( 73 | title: _city != null ? _city.name : '', 74 | leadingType: HeadBarLeadingType.goBack, 75 | showUser: false, 76 | ), 77 | body: new Column( 78 | children: [ 79 | _buildSearchContainer(), 80 | new Expanded( 81 | child: _buildResultLists(), 82 | ), 83 | ], 84 | ), 85 | backgroundColor: Style.emptyBackgroundColor, 86 | ); 87 | } 88 | 89 | _searchPlace(String query) async { 90 | setState(() { 91 | _query = query; 92 | }); 93 | if (query.length != 0) { 94 | List places = await Api.searchPlace(_city.id, query); 95 | setState(() { 96 | _searchPlaces = places; 97 | }); 98 | } 99 | } 100 | 101 | Widget _buildSearchContainer() { 102 | return new Container( 103 | margin: _gMargin, 104 | padding: _gPadding, 105 | decoration: new BoxDecoration( 106 | color: Style.backgroundColor, 107 | border: _topBottomBorder, 108 | ), 109 | child: new Container( 110 | padding: new EdgeInsets.symmetric(horizontal: 10.0), 111 | decoration: new BoxDecoration( 112 | border: new Border.all( 113 | color: Style.borderColor, 114 | ), 115 | borderRadius: new BorderRadius.all( 116 | new Radius.circular(5.0), 117 | ), 118 | ), 119 | child: new TextField( 120 | decoration: new InputDecoration( 121 | hintText: '输入学校、商务楼、地址', 122 | hintStyle: _textStyle, 123 | border: InputBorder.none, 124 | ), 125 | onChanged: (value) { 126 | _searchPlace(value); 127 | }, 128 | ), 129 | ), 130 | ); 131 | } 132 | 133 | Widget _buildResultLists() { 134 | bool showHistory = _query.length == 0; 135 | int itemCount = showHistory ? _historyPlaces.length : _searchPlaces.length; 136 | if (showHistory && _historyPlaces.length > 0) { 137 | itemCount += 1; 138 | } 139 | return new ListView.builder( 140 | itemCount: itemCount, 141 | itemBuilder: (context, i) { 142 | Place place; 143 | if ((showHistory && i < _historyPlaces.length) || !showHistory) { 144 | place = showHistory ? _historyPlaces[i] : _searchPlaces[i]; 145 | return new GestureDetector( 146 | child: new Container( 147 | padding: _gPadding, 148 | decoration: new BoxDecoration( 149 | color: Style.backgroundColor, 150 | border: i == 0 ? _topBottomBorder : _bottomBorder, 151 | ), 152 | child: new Column( 153 | crossAxisAlignment: CrossAxisAlignment.start, 154 | children: [ 155 | new Text( 156 | place.name, 157 | style: _textStyle, 158 | ), 159 | new Text( 160 | place.address, 161 | style: _tipTextStyle, 162 | ), 163 | ], 164 | ), 165 | ), 166 | onTap: () => _selectPlace(context, place), 167 | ); 168 | } else { 169 | return new GestureDetector( 170 | child: new Container( 171 | padding: _gPadding, 172 | decoration: new BoxDecoration( 173 | color: Style.backgroundColor, 174 | border: _bottomBorder, 175 | ), 176 | child: new Center( 177 | child: new Text( 178 | '清除所有', 179 | style: _textStyle, 180 | ), 181 | ), 182 | ), 183 | onTap: _clearHistory, 184 | ); 185 | } 186 | }, 187 | ); 188 | } 189 | 190 | ///点击搜索结果进入下一页面时进行判断是否已经有一样的历史记录 191 | ///如果没有则新增,如果有则不做重复储存,判断完成后进入下一页 192 | _selectPlace(BuildContext context, Place place) async { 193 | List history = await LocalStorage.getPlaceHistory(); 194 | bool find = false; 195 | for(var item in history) { 196 | if (item.geohash == place.geohash) { 197 | find = true; 198 | break; 199 | } 200 | } 201 | if (!find) { 202 | history.add(place); 203 | LocalStorage.setPlaceHistory(history); 204 | } 205 | store.dispatch(new UpdateGeoHashAction(place.geohash)); 206 | Routes.router.navigateTo(context, '/msite/${place.geohash}'); 207 | } 208 | 209 | _clearHistory() { 210 | setState(() { 211 | _historyPlaces = []; 212 | LocalStorage.setPlaceHistory(_historyPlaces); 213 | }); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /lib/components/shop_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../model/restaurant.dart'; 3 | import '../style/style.dart'; 4 | import '../components/rating_star.dart'; 5 | import '../config/config.dart'; 6 | 7 | typedef void _ShopItemOnTapCallBack(Restaurant restaurant); 8 | 9 | class ShopItem extends StatelessWidget { 10 | ShopItem(Restaurant restaurant, _ShopItemOnTapCallBack onTap) : restaurant=restaurant, onTap=onTap, assert(restaurant != null); 11 | final Restaurant restaurant; 12 | final _shopPadding = new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0); 13 | final _shopHeight = 100.0; 14 | final _ShopItemOnTapCallBack onTap; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | var detailRow = new Row( 19 | children: [], 20 | ); 21 | if (restaurant.isPremium) { 22 | detailRow.children.add( 23 | new Container( 24 | margin: new EdgeInsets.only(right: 5.0), 25 | color: new Color(0xFFFFd930), 26 | child: new Text( 27 | '品牌', 28 | style: Style.textStyle, 29 | ), 30 | ), 31 | ); 32 | } 33 | detailRow.children.add(new Expanded( 34 | child: new Text( 35 | restaurant.name, 36 | style: Style.textStyle, 37 | overflow: TextOverflow.ellipsis, 38 | ), 39 | )); 40 | var supportsRow = new Row( 41 | children: [], 42 | ); 43 | for (var support in restaurant.supports) { 44 | supportsRow.children.add(new Container( 45 | padding: const EdgeInsets.symmetric(horizontal: 2.0), 46 | decoration: new BoxDecoration( 47 | border: new Border.all( 48 | color: Style.borderColor, 49 | width: 0.5, 50 | ), 51 | ), 52 | child: new Text( 53 | support.iconName, 54 | style: new TextStyle( 55 | color: const Color(0xFF999999), 56 | fontSize: 10.0, 57 | ), 58 | ), 59 | )); 60 | } 61 | var ratingRow = new Row( 62 | children: [ 63 | new RatingStar(restaurant.rating), 64 | new Text( 65 | '月售${restaurant.recentOrderNum}单', 66 | style: new TextStyle( 67 | color: const Color(0xFF999999), 68 | fontSize: 10.0, 69 | ), 70 | ), 71 | ], 72 | ); 73 | var ratingDescRow = new Row( 74 | children: [], 75 | ); 76 | if (restaurant.deliveryMode != null) { 77 | ratingDescRow.children.add( 78 | new Container( 79 | padding: new EdgeInsets.symmetric(horizontal: 2.0), 80 | decoration: new BoxDecoration( 81 | color: Style.primaryColor, 82 | border: new Border.all( 83 | color: Style.primaryColor, 84 | ), 85 | borderRadius: new BorderRadius.all(new Radius.circular(2.0)), 86 | ), 87 | child: new Text( 88 | restaurant.deliveryMode.text, 89 | style: new TextStyle( 90 | color: Style.backgroundColor, 91 | fontSize: 10.0, 92 | ), 93 | ), 94 | ), 95 | ); 96 | } 97 | if (zhunshi(restaurant)) { 98 | ratingDescRow.children.add( 99 | new Container( 100 | margin: new EdgeInsets.only(left: 2.0), 101 | padding: new EdgeInsets.symmetric(horizontal: 2.0), 102 | decoration: new BoxDecoration( 103 | color: Style.backgroundColor, 104 | border: new Border.all( 105 | color: Style.primaryColor, 106 | ), 107 | borderRadius: new BorderRadius.all(new Radius.circular(2.0)), 108 | ), 109 | child: new Text( 110 | '准时达', 111 | style: new TextStyle( 112 | color: Style.primaryColor, 113 | fontSize: 10.0, 114 | ), 115 | ), 116 | ), 117 | ); 118 | } 119 | var distanceRow = new Row( 120 | children: [], 121 | ); 122 | num distance = num.tryParse(restaurant.distance); 123 | if (distance != null) { 124 | distanceRow.children.add( 125 | new Text( 126 | '${distance > 1000 127 | ? '${(distance / 1000).toStringAsFixed(2)}km' 128 | : '${distance}m'}' + 129 | ' / ', 130 | style: new TextStyle( 131 | color: const Color(0xFF999999), 132 | fontSize: 12.0, 133 | ), 134 | ), 135 | ); 136 | } else { 137 | distanceRow.children.add( 138 | new Text( 139 | '${restaurant.distance} / ', 140 | style: new TextStyle( 141 | color: const Color(0xFF999999), 142 | fontSize: 12.0, 143 | ), 144 | ), 145 | ); 146 | } 147 | distanceRow.children.add( 148 | new Text( 149 | '${restaurant.orderLeadTime}', 150 | style: new TextStyle( 151 | color: Style.primaryColor, 152 | fontSize: 12.0, 153 | ), 154 | ), 155 | ); 156 | return new GestureDetector( 157 | child: new Container( 158 | height: _shopHeight, 159 | padding: _shopPadding, 160 | decoration: new BoxDecoration( 161 | color: Style.backgroundColor, 162 | border: new Border( 163 | bottom: new BorderSide( 164 | color: Style.borderColor, 165 | ), 166 | ), 167 | ), 168 | child: new Row( 169 | children: [ 170 | new Container( 171 | margin: new EdgeInsets.only(right: 10.0), 172 | child: new Image.network( 173 | '${Config.ImgBaseUrl}${restaurant.imagePath}', 174 | width: 80.0, 175 | height: 80.0, 176 | fit: BoxFit.fill, 177 | ), 178 | ), 179 | new Expanded( 180 | child: new Column( 181 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 182 | children: [ 183 | new Row( 184 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 185 | children: [ 186 | new Expanded( 187 | child: detailRow, 188 | ), 189 | supportsRow, 190 | ], 191 | ), 192 | new Row( 193 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 194 | children: [ratingRow, ratingDescRow], 195 | ), 196 | new Row( 197 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 198 | children: [ 199 | new Text( 200 | '¥${restaurant.floatMinimumOrderAmount}起送 / ${restaurant 201 | .piecewiseAgentFee['tips']}', 202 | style: new TextStyle( 203 | fontSize: 12.0, 204 | ), 205 | ), 206 | distanceRow, 207 | ], 208 | ), 209 | ], 210 | ), 211 | ), 212 | ], 213 | ), 214 | ), 215 | onTap: () => onTap(restaurant), 216 | ); 217 | } 218 | 219 | bool zhunshi(Restaurant r) { 220 | if (r.supports != null && r.supports.length > 0) { 221 | for (var s in r.supports) { 222 | if (s.iconName == '准') { 223 | return true; 224 | } 225 | } 226 | } 227 | return false; 228 | } 229 | } -------------------------------------------------------------------------------- /lib/page/msite.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:convert'; 3 | import '../style/style.dart'; 4 | import '../components/carousel.dart'; 5 | import '../model/food_type.dart'; 6 | import '../model/place.dart'; 7 | import '../utils/api.dart'; 8 | import '../config/config.dart'; 9 | import '../model/restaurant.dart'; 10 | import '../components/shop_item.dart'; 11 | import '../components/foot_bar.dart'; 12 | import '../components/head_bar.dart'; 13 | import '../routes/routes.dart'; 14 | 15 | class MSite extends StatefulWidget { 16 | MSite(num longitude, num latitude) 17 | : longitude = longitude, 18 | latitude = latitude, 19 | geoHash = '$longitude,$latitude', 20 | assert(longitude != null), 21 | assert(latitude != null); 22 | 23 | final num longitude; 24 | final num latitude; 25 | final String geoHash; 26 | 27 | @override 28 | createState() => new MSiteState(); 29 | } 30 | 31 | class MSiteState extends State { 32 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding); 33 | final _gMargin = new EdgeInsets.only(bottom: 10.0); 34 | final _restaurantLimit = 10; 35 | 36 | Place _place; 37 | List _foodTypes = []; 38 | List _restaurants = []; 39 | bool _isLoading = false; 40 | bool _loadingFinish = false; 41 | int _offset = 0; 42 | final _foodTypePageSize = 8; 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | getPlace(); 48 | getFoodTypes(); 49 | getRestaurants(); 50 | } 51 | 52 | getPlace() async { 53 | Place place = await Api.getPlace(widget.geoHash); 54 | setState(() { 55 | _place = place; 56 | }); 57 | } 58 | 59 | getFoodTypes() async { 60 | List foodTypes = await Api.getFoodTypes(widget.geoHash); 61 | setState(() { 62 | _foodTypes = foodTypes; 63 | }); 64 | } 65 | 66 | getRestaurants() async { 67 | print('loading restaurants'); 68 | _isLoading = true; 69 | List restaurants = await Api.getRestaurants( 70 | widget.latitude, widget.longitude, _offset, 71 | limit: _restaurantLimit); 72 | if (restaurants.length < _restaurantLimit) { 73 | _loadingFinish = true; 74 | } 75 | setState(() { 76 | _restaurants.addAll(restaurants); 77 | }); 78 | print('loading finish'); 79 | _isLoading = false; 80 | } 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return new Scaffold( 85 | appBar: new HeadBar( 86 | title: _place != null ? _place.name : '', 87 | leadingType: HeadBarLeadingType.search, 88 | titleOnTap: _goHome, 89 | ), 90 | body: _buildBody(), 91 | bottomNavigationBar: new FootBar( 92 | currentIndex: 0, 93 | ), 94 | backgroundColor: Style.emptyBackgroundColor, 95 | ); 96 | } 97 | 98 | _goHome() { 99 | Routes.router.navigateTo(context, '/home'); 100 | } 101 | 102 | Widget _buildBody() { 103 | var itemCount = 2 + _restaurants.length; 104 | if (_loadingFinish) { 105 | itemCount += 1; 106 | } 107 | return new ListView.builder( 108 | itemCount: itemCount, 109 | itemBuilder: (context, i) { 110 | if (i == 0) { 111 | return new Container( 112 | margin: _gMargin, 113 | child: new Carousel( 114 | height: 200.0, 115 | pages: _buildFoodTypePages(), 116 | autoPlay: false, 117 | ), 118 | ); 119 | } else if (i == 1) { 120 | return new Container( 121 | height: 40.0, 122 | padding: _gPadding, 123 | decoration: new BoxDecoration( 124 | color: Style.backgroundColor, 125 | border: new Border( 126 | top: new BorderSide( 127 | color: Style.borderColor, 128 | ), 129 | ), 130 | ), 131 | child: new Row( 132 | children: [ 133 | new Text( 134 | '附近商家', 135 | style: Style.textStyle, 136 | ), 137 | ], 138 | ), 139 | ); 140 | } else if (_loadingFinish && i == itemCount - 1) { 141 | return new Container( 142 | height: 50.0, 143 | color: Style.backgroundColor, 144 | child: new Center( 145 | child: new Text( 146 | '没有更多了', 147 | style: Style.textStyle, 148 | ), 149 | ), 150 | ); 151 | } else { 152 | if (!_loadingFinish && !_isLoading && i >= _restaurants.length - 2) { 153 | _offset += _restaurantLimit; 154 | getRestaurants(); 155 | } 156 | return new ShopItem(_restaurants[i - 2], _goShop); 157 | } 158 | }, 159 | ); 160 | } 161 | 162 | List _buildFoodTypePages() { 163 | var pages = []; 164 | for (var i = 0; i < _foodTypes.length; i += _foodTypePageSize) { 165 | int start = i; 166 | int end = i + _foodTypePageSize; 167 | end = end < _foodTypes.length ? end : _foodTypes.length; 168 | var page = _buildFoodTypePage(_foodTypes.sublist(start, end)); 169 | pages.add(page); 170 | } 171 | 172 | return pages; 173 | } 174 | 175 | Widget _buildFoodTypePage(List foodTypes) { 176 | var columnChildren = []; 177 | Row lastRow; 178 | for (var i = 0; i < foodTypes.length; ++i) { 179 | var foodType = foodTypes[i]; 180 | if (i == 0 || i == _foodTypePageSize / 2) { 181 | var container = new Container( 182 | margin: new EdgeInsets.only(top: 10.0), 183 | child: new Row( 184 | children: [], 185 | ), 186 | ); 187 | lastRow = container.child; 188 | columnChildren.add(container); 189 | } 190 | lastRow.children.add(new Expanded( 191 | child: new GestureDetector( 192 | child: new Container( 193 | child: new Column( 194 | children: [ 195 | new Image.network( 196 | '${Config.ImgCdnUrl}${foodType.imageUrl}', 197 | height: 50.0, 198 | ), 199 | new Text( 200 | foodType.title, 201 | style: Style.textStyle, 202 | textAlign: TextAlign.center, 203 | ), 204 | ], 205 | ), 206 | ), 207 | onTap: () => _goFood(foodType), 208 | ), 209 | )); 210 | if (i == foodTypes.length - 1 && 211 | lastRow.children.length != _foodTypePageSize / 2) { 212 | lastRow.children.add( 213 | new Expanded( 214 | flex: (_foodTypePageSize / 2 - lastRow.children.length).floor(), 215 | child: new Container(), 216 | ), 217 | ); 218 | } 219 | } 220 | return new Column( 221 | children: columnChildren, 222 | ); 223 | } 224 | 225 | void _goFood(FoodType foodType) { 226 | Routes.router.navigateTo( 227 | context, 228 | '/food/${_getCategoryId(foodType.link)}/${foodType.title}/${widget 229 | .geoHash}'); 230 | } 231 | 232 | // 解码url地址,获取restaurant_category_id 233 | String _getCategoryId(url) { 234 | String params = url.split('=')[1]; 235 | String urlData = 236 | Uri.decodeComponent(params.replaceFirst('&target_name', '')); 237 | if (urlData.contains('restaurant_category_id')) { 238 | var data = json.decode(urlData); 239 | return data['restaurant_category_id']['id'].toString(); 240 | } else { 241 | return ''; 242 | } 243 | } 244 | 245 | void _goShop(Restaurant restaurant) { 246 | Routes.router.navigateTo( 247 | context, 248 | '/shop/${widget.geoHash}/${restaurant.id}', 249 | ); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /lib/page/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/foot_bar.dart'; 3 | import '../style/style.dart'; 4 | import '../components/head_bar.dart'; 5 | import '../store/store.dart'; 6 | import '../routes/routes.dart'; 7 | import '../config//config.dart'; 8 | 9 | class Profile extends StatefulWidget { 10 | @override 11 | createState() => new ProfileState(); 12 | } 13 | 14 | class ProfileState extends State { 15 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding); 16 | final _gTopMargin = new EdgeInsets.only(top: 10.0); 17 | final _personPadding = 18 | new EdgeInsets.symmetric(horizontal: Style.gPadding, vertical: 10.0); 19 | final _personColor = Style.backgroundColor; 20 | final _infoDataPadding = new EdgeInsets.all(Style.gPadding); 21 | 22 | String _username = '登录/注册'; 23 | String _mobile = '暂无绑定手机号'; 24 | int _balance = 0; 25 | int _count = 0; 26 | int _pointNumber = 0; 27 | String _avatar = ''; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | _initData(); 33 | } 34 | 35 | void _initData() { 36 | var user = store.state.user; 37 | if (user != null) { 38 | setState(() { 39 | _username = user.username; 40 | _mobile = '暂无绑定手机号'; 41 | if (user.mobile != null && user.mobile.isNotEmpty) { 42 | _mobile = user.mobile; 43 | } 44 | _balance = user.balance; 45 | _count = user.giftAmount; 46 | _pointNumber = user.point; 47 | _avatar = user.avatar; 48 | }); 49 | } else { 50 | setState(() { 51 | _username = '登录/注册'; 52 | _mobile = '暂无绑定手机号'; 53 | }); 54 | } 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return new Scaffold( 60 | appBar: new HeadBar( 61 | title: '我的', 62 | showUser: false, 63 | bottom: _buildPersonDetail(), 64 | ), 65 | body: new Column( 66 | children: [ 67 | _buildInfoData(), 68 | _buildOperatorBlock( 69 | children: [ 70 | _buildOperatorItem( 71 | icon: Icons.shopping_cart, 72 | iconColor: Style.primaryColor, 73 | text: '我的订单', 74 | ), 75 | _buildOperatorItem( 76 | icon: Icons.card_giftcard, 77 | iconColor: new Color(0xfffc7b53), 78 | text: '积分商城', 79 | ), 80 | _buildOperatorItem( 81 | icon: Icons.card_membership, 82 | iconColor: new Color(0xffffc636), 83 | text: '饿了么会员卡', 84 | ), 85 | ], 86 | ), 87 | _buildOperatorBlock( 88 | children: [ 89 | _buildOperatorItem( 90 | icon: Icons.business_center, 91 | iconColor: Style.primaryColor, 92 | text: '服务中心', 93 | ), 94 | _buildOperatorItem( 95 | icon: Icons.get_app, 96 | iconColor: Style.primaryColor, 97 | text: '下载饿了么APP', 98 | ), 99 | ], 100 | ), 101 | ], 102 | ), 103 | bottomNavigationBar: new FootBar( 104 | currentIndex: 3, 105 | ), 106 | backgroundColor: Style.emptyBackgroundColor, 107 | ); 108 | } 109 | 110 | PreferredSizeWidget _buildPersonDetail() { 111 | List children = []; 112 | if (_avatar.isNotEmpty) { 113 | children.add( 114 | new CircleAvatar( 115 | backgroundImage: new NetworkImage('${Config.ImgBaseUrl}$_avatar'), 116 | radius: 30.0, 117 | ) 118 | ); 119 | } else { 120 | children.add( 121 | new Icon( 122 | Icons.account_circle, 123 | color: _personColor, 124 | size: 60.0, 125 | ) 126 | ); 127 | } 128 | children.addAll([ 129 | new Expanded( 130 | child: new Padding( 131 | padding: new EdgeInsets.only(left: 5.0), 132 | child: new Column( 133 | crossAxisAlignment: CrossAxisAlignment.start, 134 | children: [ 135 | new Text( 136 | _username, 137 | style: new TextStyle( 138 | fontSize: 20.0, 139 | color: _personColor, 140 | ), 141 | ), 142 | new Row( 143 | children: [ 144 | new Icon( 145 | Icons.smartphone, 146 | color: _personColor, 147 | size: 20.0, 148 | ), 149 | new Text( 150 | _mobile, 151 | style: new TextStyle( 152 | fontSize: 13.0, 153 | color: _personColor, 154 | ), 155 | ), 156 | ], 157 | ) 158 | ], 159 | ), 160 | ), 161 | ), 162 | new Icon( 163 | Icons.arrow_forward_ios, 164 | color: _personColor, 165 | size: 15.0, 166 | ), 167 | ]); 168 | return new PreferredSize( 169 | preferredSize: const Size.fromHeight(80.0), 170 | child: new GestureDetector( 171 | onTap: () { 172 | String path; 173 | if (store.state.user == null) { 174 | path = '/login'; 175 | } else { 176 | path = '/profile/login'; 177 | } 178 | Routes.router.navigateTo(context, path); 179 | }, 180 | child: new Container( 181 | color: Style.primaryColor, 182 | padding: _personPadding, 183 | child: new Row( 184 | children: children, 185 | ), 186 | ), 187 | ), 188 | ); 189 | } 190 | 191 | Widget _buildInfoData() { 192 | return new Container( 193 | color: Style.backgroundColor, 194 | child: new Row( 195 | children: [ 196 | _buildInfoDataSingle( 197 | count: _balance.toStringAsFixed(2), 198 | countColor: new Color(0xffff9900), 199 | unit: '元', 200 | desc: '我的余额', 201 | ), 202 | _buildInfoDataSingle( 203 | count: '$_count', 204 | countColor: new Color(0xffff5f3e), 205 | unit: '个', 206 | desc: '我的优惠', 207 | ), 208 | _buildInfoDataSingle( 209 | count: '$_pointNumber', 210 | countColor: new Color(0xff6ac20b), 211 | unit: '分', 212 | desc: '我的积分', 213 | showBorder: false, 214 | ), 215 | ], 216 | ), 217 | ); 218 | } 219 | 220 | Widget _buildInfoDataSingle({ 221 | String count, 222 | Color countColor = Colors.black, 223 | String unit, 224 | String desc, 225 | bool showBorder = true, 226 | }) { 227 | return new Expanded( 228 | child: new Container( 229 | padding: _infoDataPadding, 230 | decoration: showBorder 231 | ? new BoxDecoration( 232 | border: new Border( 233 | right: new BorderSide( 234 | color: Style.borderColor, 235 | ), 236 | ), 237 | ) 238 | : null, 239 | child: new Column( 240 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 241 | children: [ 242 | new Row( 243 | mainAxisAlignment: MainAxisAlignment.center, 244 | crossAxisAlignment: CrossAxisAlignment.end, 245 | children: [ 246 | new Text( 247 | count, 248 | style: new TextStyle( 249 | fontSize: 25.0, 250 | color: countColor, 251 | ), 252 | ), 253 | new Text(unit) 254 | ], 255 | ), 256 | new Padding( 257 | padding: new EdgeInsets.only(top: 5.0), 258 | child: new Text( 259 | desc, 260 | ), 261 | ), 262 | ], 263 | ), 264 | ), 265 | ); 266 | } 267 | 268 | Widget _buildOperatorBlock({ 269 | List children = const [], 270 | }) { 271 | return new Container( 272 | margin: _gTopMargin, 273 | color: Style.backgroundColor, 274 | child: new Column( 275 | children: children, 276 | ), 277 | ); 278 | } 279 | 280 | Widget _buildOperatorItem({ 281 | IconData icon, 282 | Color iconColor, 283 | String text, 284 | }) { 285 | return new Container( 286 | padding: _gPadding.copyWith(top: 10.0, bottom: 10.0), 287 | decoration: new BoxDecoration( 288 | border: new Border( 289 | bottom: new BorderSide( 290 | color: Style.borderColor, 291 | ), 292 | ), 293 | ), 294 | child: new Row( 295 | children: [ 296 | new Expanded( 297 | child: new Row( 298 | children: [ 299 | new Icon( 300 | icon, 301 | color: iconColor, 302 | size: 18.0, 303 | ), 304 | new Padding( 305 | padding: new EdgeInsets.only(left: 5.0), 306 | child: new Text( 307 | text, 308 | style: new TextStyle( 309 | fontSize: 15.0, 310 | ), 311 | ), 312 | ), 313 | ], 314 | ), 315 | ), 316 | new Icon( 317 | Icons.arrow_forward_ios, 318 | color: new Color(0xffbbbbbb), 319 | size: 15.0, 320 | ), 321 | ], 322 | ), 323 | ); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /lib/page/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../utils/api.dart'; 4 | import '../model/city.dart'; 5 | import '../routes/routes.dart'; 6 | import '../components/alphabet_bar.dart'; 7 | import '../components/head_bar.dart'; 8 | 9 | class Home extends StatefulWidget { 10 | @override 11 | createState() => new HomeState(); 12 | } 13 | 14 | class HomeState extends State { 15 | final _lineHeight = 40.0; 16 | final _gPadding = new EdgeInsets.symmetric(horizontal: Style.gPadding); 17 | final _gMargin = new EdgeInsets.only(bottom: 10.0); 18 | final _btnPadding = new EdgeInsets.symmetric(horizontal: 5.0); 19 | final _textStyle = Style.textStyle; 20 | final _tipTextStyle = new TextStyle( 21 | color: const Color(0xFF9F9F9F), 22 | fontSize: 12.0, 23 | ); 24 | final _hotTextStyle = new TextStyle( 25 | color: Style.primaryColor, 26 | fontSize: Style.fontSize, 27 | ); 28 | final _bottomBorder = new Border( 29 | bottom: new BorderSide( 30 | color: Style.borderColor, 31 | ), 32 | ); 33 | 34 | final _bottomRightBorder = new Border( 35 | bottom: new BorderSide( 36 | color: Style.borderColor, 37 | ), 38 | right: new BorderSide( 39 | color: Style.borderColor, 40 | ), 41 | ); 42 | final ScrollController _scrollController = new ScrollController(); 43 | 44 | List _hotCities = []; 45 | Map> _citiesGroup = new Map(); 46 | List _citiesGroupKeys = []; 47 | City _guessCity; 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | getHotCities(); 53 | getCitiesGroup(); 54 | getGuessCity(); 55 | //LocalStorage.setUser(null); 56 | } 57 | 58 | getHotCities() async { 59 | List cities = await Api.getHotCities(); 60 | cities.sort((c1, c2) => c1.sort - c2.sort); 61 | setState(() { 62 | _hotCities = cities; 63 | }); 64 | } 65 | 66 | getCitiesGroup() async { 67 | Map> citiesGroup = await Api.getCitiesGroup(); 68 | setState(() { 69 | _citiesGroupKeys = citiesGroup.keys.toList(); 70 | _citiesGroupKeys.sort((s1, s2) => s1.compareTo(s2)); 71 | _citiesGroup = citiesGroup; 72 | }); 73 | } 74 | 75 | getGuessCity() async { 76 | City city = await Api.getGuessCity(); 77 | setState(() { 78 | _guessCity = city; 79 | }); 80 | } 81 | 82 | _goCity(BuildContext context, City city) { 83 | Routes.router.navigateTo(context, '/city/${city.id.toString()}'); 84 | } 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | return new Scaffold( 89 | appBar: new HeadBar( 90 | title: 'elm.qyw', 91 | centerTitle: false, 92 | showUser: false, 93 | ), 94 | body: _buildBody(), 95 | backgroundColor: Style.emptyBackgroundColor, 96 | ); 97 | } 98 | 99 | Widget _buildBody() { 100 | var listView = new ListView.builder( 101 | controller: _scrollController, 102 | itemCount: 2 + _citiesGroupKeys.length, 103 | itemBuilder: (context, i) { 104 | if (i == 0) { 105 | return _buildCityLocation(); 106 | } else if (i == 1) { 107 | return _buildHotCityBlock(); 108 | } else { 109 | return _buildCityGroupBlock(i - 2); 110 | } 111 | }, 112 | ); 113 | return new Stack( 114 | children: [ 115 | listView, 116 | new Positioned( 117 | top: 0.0, 118 | right: 0.0, 119 | bottom: 0.0, 120 | child: new Center( 121 | child: new AlphabetBar( 122 | onTap: _goCityGroup, 123 | ), 124 | ), 125 | ) 126 | ], 127 | ); 128 | } 129 | 130 | void _goCityGroup(String letter) { 131 | letter = letter.toUpperCase(); 132 | double sep = 10.0; 133 | double height = 5 * _lineHeight + 2 * sep; 134 | int index = _citiesGroupKeys.indexOf(letter); 135 | if (index != -1) { 136 | for (int i = 0; i < index; ++i) { 137 | var cities = _citiesGroup[_citiesGroupKeys[i]]; 138 | height += sep + (((cities.length / 4).ceil() + 1) * _lineHeight); 139 | } 140 | } 141 | _scrollController.animateTo( 142 | height, 143 | duration: new Duration( 144 | milliseconds: 200, 145 | ), 146 | curve: Curves.linear, 147 | ); 148 | } 149 | 150 | Widget _buildBlockContainer(Widget child) { 151 | return new Container( 152 | margin: _gMargin, 153 | color: Style.backgroundColor, 154 | child: child, 155 | ); 156 | } 157 | 158 | Widget _buildRowContainer(Widget child) { 159 | return new Container( 160 | padding: _gPadding, 161 | height: _lineHeight, 162 | decoration: new BoxDecoration( 163 | border: _bottomBorder, 164 | ), 165 | child: child, 166 | ); 167 | } 168 | 169 | Widget _buildCityLocation() { 170 | return _buildBlockContainer( 171 | new Column( 172 | children: [ 173 | _buildRowContainer( 174 | new Row( 175 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 176 | children: [ 177 | new Text( 178 | '当前定位城市:', 179 | style: _textStyle, 180 | ), 181 | new Text( 182 | '定位不准时,请在城市列表中选择', 183 | style: _tipTextStyle, 184 | ), 185 | ], 186 | ), 187 | ), 188 | new GestureDetector( 189 | child: _buildRowContainer( 190 | new Row( 191 | children: [ 192 | new Text( 193 | _guessCity == null ? '' : _guessCity.name, 194 | style: _hotTextStyle, 195 | ), 196 | new Expanded( 197 | child: new Align( 198 | alignment: Alignment.centerRight, 199 | child: new Icon( 200 | Icons.arrow_forward_ios, 201 | color: const Color(0xFF999999), 202 | size: 20.0, 203 | ), 204 | ), 205 | ), 206 | ], 207 | ), 208 | ), 209 | onTap: () { 210 | if (_guessCity != null) { 211 | this._goCity(context, _guessCity); 212 | } 213 | }, 214 | ), 215 | ], 216 | ), 217 | ); 218 | } 219 | 220 | Widget _buildHotCityBlock() { 221 | return _buildBlockContainer( 222 | new Column( 223 | children: [ 224 | _buildRowContainer( 225 | new Row( 226 | children: [ 227 | new Text( 228 | '热门城市', 229 | style: Style.textStyle, 230 | ), 231 | ], 232 | ), 233 | ), 234 | _buildHotCities(), 235 | ], 236 | ), 237 | ); 238 | } 239 | 240 | Widget _buildHotCities() { 241 | var hotCityColumn = new Column( 242 | children: [], 243 | ); 244 | Row lastRow; 245 | for (var i = 0; i < _hotCities.length; ++i) { 246 | if (i % 4 == 0) { 247 | lastRow = new Row(children: [],); 248 | hotCityColumn.children.add(lastRow); 249 | } 250 | lastRow.children.add( 251 | new Expanded( 252 | child: _buildCityBtn(i, _hotCities[i], true), 253 | ), 254 | ); 255 | if (i == _hotCities.length - 1 && lastRow.children.length < 4) { 256 | lastRow.children.add( 257 | new Expanded( 258 | flex: 4 - lastRow.children.length, 259 | child: new Container(), 260 | ), 261 | ); 262 | } 263 | } 264 | return hotCityColumn; 265 | } 266 | 267 | Widget _buildCityBtn(int index, City city, isHot) { 268 | return new GestureDetector( 269 | child: new Container( 270 | height: _lineHeight, 271 | decoration: new BoxDecoration( 272 | border: index % 4 == 3 ? _bottomBorder : _bottomRightBorder, 273 | ), 274 | child: new Container( 275 | padding: _btnPadding, 276 | alignment: Alignment.center, 277 | child: new Text( 278 | city.name, 279 | style: isHot ? _hotTextStyle : _textStyle, 280 | overflow: TextOverflow.ellipsis, 281 | softWrap: false, 282 | ), 283 | ), 284 | ), 285 | onTap: () => this._goCity(context, city), 286 | ); 287 | } 288 | 289 | Widget _buildCityGroupBlock(int index) { 290 | return _buildBlockContainer( 291 | new Column( 292 | children: [ 293 | _buildRowContainer( 294 | new Row( 295 | children: [ 296 | new Text( 297 | _citiesGroupKeys[index], 298 | style: Style.textStyle, 299 | ), 300 | ], 301 | ), 302 | ), 303 | _buildCityGroup(index), 304 | ], 305 | ), 306 | ); 307 | } 308 | 309 | Widget _buildCityGroup(int index) { 310 | var groupColumn = new Column( 311 | children: [], 312 | ); 313 | Row lastRow; 314 | var cities = _citiesGroup[_citiesGroupKeys[index]]; 315 | for (var i = 0; i < cities.length; ++i) { 316 | if (i % 4 == 0) { 317 | lastRow = new Row(children: [],); 318 | groupColumn.children.add(lastRow); 319 | } 320 | lastRow.children.add( 321 | new Expanded( 322 | child: _buildCityBtn(i, cities[i], false), 323 | ), 324 | ); 325 | if (i == cities.length - 1 && lastRow.children.length < 4) { 326 | lastRow.children.add( 327 | new Expanded( 328 | flex: 4 - lastRow.children.length, 329 | child: new Container(), 330 | ), 331 | ); 332 | } 333 | } 334 | return groupColumn; 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /lib/utils/api.dart: -------------------------------------------------------------------------------- 1 | import 'http.dart'; 2 | import '../model/city.dart'; 3 | import '../model/place.dart'; 4 | import '../model/food_type.dart'; 5 | import '../model/restaurant.dart'; 6 | import '../model/category.dart'; 7 | import '../model/delivery_mode.dart'; 8 | import '../model/activity.dart'; 9 | import '../model/menu.dart'; 10 | import '../model/rating.dart'; 11 | import '../model/rating_score.dart'; 12 | import '../model/rating_tag.dart'; 13 | 14 | class Api { 15 | static final String _host = 'http://cangdu.org:8001'; 16 | 17 | static getGuessCity() async { 18 | City city; 19 | var uri = Uri.parse('$_host/v1/cities?type=guess'); 20 | try { 21 | var data = await HttpUtils.httpGet(uri); 22 | city = new City.fromJson(data); 23 | } catch (e) { 24 | print('getGuessCity error: $e'); 25 | } 26 | return city; 27 | } 28 | 29 | static getHotCities() async { 30 | var cities = const []; 31 | var uri = Uri.parse('$_host/v1/cities?type=hot'); 32 | try { 33 | var data = (await HttpUtils.httpGet(uri)) as List; 34 | cities = data.map((item) { 35 | return new City.fromJson(item); 36 | }).toList(); 37 | } catch (e) { 38 | print('getHotCites error: $e'); 39 | } 40 | return cities; 41 | } 42 | 43 | static getCitiesGroup() async { 44 | var citiesGroup = new Map>(); 45 | var uri = Uri.parse('$_host/v1/cities?type=group'); 46 | try { 47 | var data = (await HttpUtils.httpGet(uri)) as Map; 48 | for (String key in data.keys) { 49 | var group = data[key]; 50 | if (group is List) { 51 | citiesGroup[key] = group.map((item) { 52 | return new City.fromJson(item); 53 | }).toList(); 54 | } 55 | } 56 | } catch (e) { 57 | print('getCitesGroup error: $e'); 58 | } 59 | return citiesGroup; 60 | } 61 | 62 | static getCityById(int id) async { 63 | City city; 64 | try { 65 | var uri = Uri.parse('$_host/v1/cities/${id.toString()}'); 66 | var data = await HttpUtils.httpGet(uri); 67 | city = new City.fromJson(data); 68 | } catch (e) { 69 | print('getCityById error: $e'); 70 | } 71 | return city; 72 | } 73 | 74 | static searchPlace(int cityId, String query) async { 75 | List places = []; 76 | try { 77 | var uri = Uri 78 | .parse('$_host/v1/pois?type=search&city_id=$cityId&keyword=$query'); 79 | var data = await HttpUtils.httpGet(uri); 80 | if (data is List) { 81 | places = data.map((item) { 82 | return new Place.fromJson(item); 83 | }).toList(); 84 | } 85 | } catch (e) { 86 | print('searchPlace error: $e'); 87 | } 88 | return places; 89 | } 90 | 91 | static getPlace(String geohash) async { 92 | Place place; 93 | try { 94 | var uri = Uri.parse('$_host/v2/pois/$geohash'); 95 | var data = await HttpUtils.httpGet(uri); 96 | place = new Place.fromJson(data); 97 | } catch (e) { 98 | print('getPlace error: $e'); 99 | } 100 | return place; 101 | } 102 | 103 | static getFoodTypes(String geohash) async { 104 | List foodTypes = []; 105 | try { 106 | var uri = 107 | Uri.parse('$_host/v2/index_entry?geohash=$geohash&group_type=1&${Uri 108 | .encodeComponent('flags[]')}=F'); 109 | var data = await HttpUtils.httpGet(uri); 110 | if (data is List) { 111 | foodTypes = data.map((item) { 112 | return new FoodType.fromJson(item); 113 | }).toList(); 114 | } 115 | } catch (e) { 116 | print('getFoodTypes error: $e'); 117 | } 118 | return foodTypes; 119 | } 120 | 121 | static getRestaurants( 122 | num latitude, 123 | num longitude, 124 | int offset, { 125 | String restaurantCategoryId = '', 126 | String restaurantCategoryIds = '', 127 | String orderBy = '', 128 | String deliveryMode = '', 129 | List supportIds = const [], 130 | int limit = 20, 131 | }) async { 132 | List restaurants = []; 133 | try { 134 | String url = 135 | '$_host/shopping/restaurants?latitude=$latitude&longitude=$longitude&offset=$offset&limit=$limit'; 136 | url += '&${Uri.encodeComponent('extras[]')}=activities'; 137 | url += '&keyword='; 138 | url += '&restaurant_category_id=$restaurantCategoryId'; 139 | url += '&${Uri.encodeComponent( 140 | 'restaurant_category_ids[]')}=$restaurantCategoryIds'; 141 | url += '&order_by=$orderBy'; 142 | 143 | String supportStr = ''; 144 | for (var support in supportIds) { 145 | supportStr += '&${Uri.encodeComponent('support_ids[]')}=$support'; 146 | } 147 | url += 148 | '&${Uri.encodeComponent('delivery_mode[]')}=${deliveryMode + supportStr}'; 149 | var uri = Uri.parse(url); 150 | var data = await HttpUtils.httpGet(uri); 151 | if (data is List) { 152 | restaurants = data.map((item) { 153 | return new Restaurant.fromJson(item); 154 | }).toList(); 155 | } 156 | } catch (e) { 157 | print('getRestaurants error: $e'); 158 | } 159 | return restaurants; 160 | } 161 | 162 | static getAuthCode() async { 163 | var code = ''; 164 | try { 165 | var uri = Uri.parse('$_host/v1/captchas'); 166 | var data = await HttpUtils.httpPost(uri); 167 | code = data['code']; 168 | } catch (e) { 169 | print('getAuthCode error: $e'); 170 | } 171 | return code; 172 | } 173 | 174 | static accountLogin(String username, String password, String authCode) async { 175 | var data; 176 | try { 177 | var uri = Uri.parse('$_host/v2/login'); 178 | data = await HttpUtils.httpPostJson( 179 | uri, 180 | jsonBody: { 181 | 'username': username, 182 | 'password': password, 183 | 'captcha_code': authCode, 184 | }, 185 | ); 186 | } catch (e) { 187 | print('accountLogin error: $e'); 188 | } 189 | return data; 190 | } 191 | 192 | static searchRestaurant(String geoHash, String keyword) async { 193 | List result = []; 194 | try { 195 | var uri = Uri.parse('$_host/v4/restaurants?${Uri.encodeComponent( 196 | 'extras[]')}=restaurant_activity&geohash=$geoHash&keyword=$keyword&type=search'); 197 | var data = await HttpUtils.httpGet(uri); 198 | if (data is List) { 199 | result = data.map((item) => new Restaurant.fromJson(item)).toList(); 200 | } 201 | } catch (e) { 202 | print('searchRestaurant error: $e'); 203 | } 204 | return result; 205 | } 206 | 207 | static getFoodCategory({num latitude, num longitude}) async { 208 | List result = []; 209 | try { 210 | var uri = Uri.parse('$_host/shopping/v2/restaurant/category?latitude=$latitude&longitude=$longitude'); 211 | var data = await HttpUtils.httpGet(uri); 212 | if (data is List) { 213 | result = data.map((item) => new Category.fromJson(item)).toList(); 214 | } 215 | } catch (e) { 216 | print('getFoodCategory error: $e'); 217 | } 218 | return result; 219 | } 220 | 221 | static getFoodDelivery({num latitude, num longitude}) async { 222 | List result = []; 223 | try { 224 | var uri = Uri.parse('$_host/shopping/v1/restaurants/delivery_modes?latitude=$latitude&longitude=$longitude&kw='); 225 | var data = await HttpUtils.httpGet(uri); 226 | if (data is List) { 227 | result = data.map((item) => new DeliveryMode.fromJson(item)).toList(); 228 | } 229 | } catch (e) { 230 | print('getFoodDelivery error: $e'); 231 | } 232 | return result; 233 | } 234 | 235 | static getFoodActivity({num latitude, num longitude}) async { 236 | List result = []; 237 | try { 238 | var uri = Uri.parse('$_host/shopping/v1/restaurants/activity_attributes?latitude=$latitude&longitude=$longitude&kw='); 239 | var data = await HttpUtils.httpGet(uri); 240 | if (data is List) { 241 | result = data.map((item) => new Activity.fromJson(item)).toList(); 242 | } 243 | } catch (e) { 244 | print('getFoodActivity error: $e'); 245 | } 246 | return result; 247 | } 248 | 249 | static getShopById({ 250 | String shopId, 251 | num latitude, 252 | num longitude, 253 | }) async { 254 | Restaurant result; 255 | try { 256 | String extrasEncode = Uri.encodeComponent('extras[]'); 257 | String url = '$_host/shopping/restaurant/$shopId?latitude=$latitude&longitude=$longitude'; 258 | url += '&$extrasEncode=activities&$extrasEncode=album&$extrasEncode=license&$extrasEncode=identification&$extrasEncode=statistics'; 259 | var uri = Uri.parse(url); 260 | var data = await HttpUtils.httpGet(uri); 261 | result = new Restaurant.fromJson(data); 262 | } catch (e) { 263 | print('getShopById error: $e'); 264 | } 265 | return result; 266 | } 267 | 268 | static getMenus(String shopId) async { 269 | List result = []; 270 | try { 271 | var uri = Uri.parse('$_host/shopping/v2/menu?restaurant_id=$shopId'); 272 | var data = await HttpUtils.httpGet(uri); 273 | if (data is List) { 274 | result = data.map((item) => new Menu.fromJson(item)).toList(); 275 | } 276 | } catch (e) { 277 | print('getMenu error: $e'); 278 | } 279 | return result; 280 | } 281 | 282 | static getRatings({ 283 | String shopId, 284 | int offset, 285 | String tagName = '', 286 | }) async { 287 | List result = []; 288 | try { 289 | var uri = Uri.parse('$_host/ugc/v2/restaurants/$shopId/ratings?has_content=true&offset=$offset&limit=10&tag_name=$tagName'); 290 | var data = await HttpUtils.httpGet(uri); 291 | if (data is List) { 292 | result = data.map((item) => new Rating.fromJson(item)).toList(); 293 | } 294 | } catch (e) { 295 | print('getRatings error: $e'); 296 | } 297 | return result; 298 | } 299 | 300 | static getRatingScore(String shopId) async { 301 | RatingScore result; 302 | try { 303 | var uri = Uri.parse('$_host/ugc/v2/restaurants/$shopId/ratings/scores'); 304 | var data = await HttpUtils.httpGet(uri); 305 | result = new RatingScore.fromJson(data); 306 | } catch (e) { 307 | print('getRatingScores error: $e'); 308 | } 309 | return result; 310 | } 311 | 312 | static getRatingTags(String shopId) async { 313 | List result = []; 314 | try { 315 | var uri = Uri.parse('$_host/ugc/v2/restaurants/$shopId/ratings/tags'); 316 | var data = await HttpUtils.httpGet(uri); 317 | if (data is List) { 318 | result = data.map((item) => new RatingTag.fromJson(item)).toList(); 319 | } 320 | } catch (e) { 321 | print('getRatingTags error: $e'); 322 | } 323 | return result; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /lib/page/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../style/style.dart'; 3 | import '../components/foot_bar.dart'; 4 | import '../components/button.dart'; 5 | import '../components/head_bar.dart'; 6 | import '../model/restaurant.dart'; 7 | import '../utils/local_storage.dart'; 8 | import '../utils/api.dart'; 9 | import '../config/config.dart'; 10 | 11 | class Search extends StatefulWidget { 12 | Search(String geoHash) 13 | : geoHash = geoHash, 14 | assert(geoHash != null); 15 | final String geoHash; 16 | 17 | @override 18 | createState() => new SearchState(); 19 | } 20 | 21 | class SearchState extends State { 22 | final _gPadding = 23 | new EdgeInsets.symmetric(horizontal: Style.gPadding, vertical: 10.0); 24 | final _gMargin = new EdgeInsets.only(bottom: 10.0); 25 | final _textStyle = Style.textStyle; 26 | final _topBottomBorder = new Border( 27 | top: new BorderSide( 28 | color: Style.borderColor, 29 | ), 30 | bottom: new BorderSide( 31 | color: Style.borderColor, 32 | ), 33 | ); 34 | final _iconColor = new Color(0xff999999); 35 | final _payTextColor = new Color(0xffff6000); 36 | final _bottomBorder = new Border( 37 | bottom: new BorderSide( 38 | color: Style.borderColor, 39 | ), 40 | ); 41 | 42 | String _searchValue = ''; 43 | List _restaurants = []; 44 | List _history = []; 45 | bool _showHistory = true; 46 | bool _emptyResult = false; 47 | 48 | @override 49 | void initState() { 50 | super.initState(); 51 | _getHistory(); 52 | } 53 | 54 | _getHistory() async { 55 | var history = await LocalStorage.getSearchHistory(); 56 | setState(() { 57 | _history = history; 58 | }); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | var itemCount = 64 | 2 + (_showHistory ? _history.length + 1 : (_restaurants.isEmpty ? 1 : _restaurants.length)); 65 | return new Scaffold( 66 | appBar: new HeadBar( 67 | title: '搜索周边', 68 | showUser: false, 69 | ), 70 | body: new ListView.builder( 71 | itemCount: itemCount, 72 | itemBuilder: (context, i) { 73 | if (i == 0) { 74 | return _buildSearchContainer(); 75 | } else if (i == 1) { 76 | if ((_showHistory && _history.isEmpty) || _restaurants.isEmpty) { 77 | return new Container(); 78 | } else { 79 | return new Container( 80 | padding: _gPadding, 81 | decoration: new BoxDecoration( 82 | color: Style.backgroundColor, 83 | border: _bottomBorder, 84 | ), 85 | child: new Text( 86 | _showHistory ? '搜索历史' : '商家', 87 | style: Style.textStyle, 88 | ), 89 | ); 90 | } 91 | } else { 92 | var index = i - 2; 93 | if (_showHistory) { 94 | if (index == _history.length) { 95 | if (_history.isEmpty) { 96 | return new Container(); 97 | } else { 98 | return new GestureDetector( 99 | child: new Container( 100 | color: Style.backgroundColor, 101 | padding: _gPadding, 102 | child: new Center( 103 | child: new Text( 104 | '清空搜索历史', 105 | style: new TextStyle(color: Style.primaryColor), 106 | ), 107 | ), 108 | ), 109 | onTap: _clearHistory, 110 | ); 111 | } 112 | } else { 113 | return _buildHistoryItem(index); 114 | } 115 | } else { 116 | if (_restaurants.isEmpty) { 117 | return new Container( 118 | color: Style.backgroundColor, 119 | padding: _gPadding, 120 | child: new Center( 121 | child: new Text('很抱歉!无搜索结果'), 122 | ), 123 | ); 124 | } else { 125 | return _buildRestaurantItem(index); 126 | } 127 | } 128 | } 129 | }, 130 | ), 131 | bottomNavigationBar: new FootBar( 132 | currentIndex: 1, 133 | ), 134 | backgroundColor: Style.emptyBackgroundColor, 135 | ); 136 | } 137 | 138 | Widget _buildSearchContainer() { 139 | return new Container( 140 | margin: _gMargin, 141 | padding: _gPadding, 142 | decoration: new BoxDecoration( 143 | color: Style.backgroundColor, 144 | border: _topBottomBorder, 145 | ), 146 | child: new Row( 147 | children: [ 148 | new Expanded( 149 | child: new Container( 150 | margin: new EdgeInsets.only(right: 10.0), 151 | padding: new EdgeInsets.symmetric(horizontal: 10.0), 152 | decoration: new BoxDecoration( 153 | border: new Border.all( 154 | color: Style.borderColor, 155 | ), 156 | borderRadius: new BorderRadius.all( 157 | new Radius.circular(5.0), 158 | ), 159 | ), 160 | child: new TextField( 161 | controller: new TextEditingController(text: _searchValue) 162 | ..selection = new TextSelection( 163 | baseOffset: _searchValue.length, 164 | extentOffset: _searchValue.length), 165 | decoration: new InputDecoration( 166 | hintText: '请输入商家或美食的名称', 167 | hintStyle: _textStyle, 168 | border: InputBorder.none, 169 | ), 170 | onChanged: (val) { 171 | _searchValue = val; 172 | if (_searchValue.isEmpty) { 173 | _showHistory = true; 174 | _getHistory(); 175 | } 176 | }, 177 | ), 178 | ), 179 | ), 180 | new Button( 181 | width: 60.0, 182 | height: 46.0, 183 | text: new Text( 184 | '搜索', 185 | style: new TextStyle( 186 | fontSize: 18.0, 187 | color: const Color(0xFFFFFFFF), 188 | ), 189 | ), 190 | onTap: _search, 191 | ) 192 | ], 193 | ), 194 | ); 195 | } 196 | 197 | _search() async { 198 | if (_searchValue.isEmpty) { 199 | return; 200 | } 201 | // 隐藏搜索历史 202 | var showHistory = false; 203 | // 查询结果 204 | var restaurants = await Api.searchRestaurant(widget.geoHash, _searchValue); 205 | var emptyResult = restaurants.isEmpty; 206 | // 如果不存在此次搜索历史则新增 207 | List history = await LocalStorage.getSearchHistory(); 208 | if (!history.contains(_searchValue)) { 209 | history.add(_searchValue); 210 | LocalStorage.setSearchHistory(history); 211 | } 212 | 213 | setState(() { 214 | _showHistory = showHistory; 215 | _restaurants = restaurants; 216 | _emptyResult = emptyResult; 217 | }); 218 | } 219 | 220 | Widget _buildHistoryItem(int index) { 221 | String history = _history[index]; 222 | return new GestureDetector( 223 | child: new Container( 224 | padding: _gPadding, 225 | decoration: new BoxDecoration( 226 | color: Style.backgroundColor, 227 | border: _bottomBorder, 228 | ), 229 | child: new Row( 230 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 231 | children: [ 232 | new Text(history), 233 | new GestureDetector( 234 | child: new Icon( 235 | Icons.delete_forever, 236 | color: _iconColor, 237 | ), 238 | onTap: () => _removeHistory(history), 239 | ), 240 | ], 241 | ), 242 | ), 243 | onTap: () { 244 | _searchValue = history; 245 | _search(); 246 | }, 247 | ); 248 | } 249 | 250 | Widget _buildRestaurantItem(int index) { 251 | Restaurant restaurant = _restaurants[index]; 252 | return new Container( 253 | padding: _gPadding, 254 | decoration: new BoxDecoration( 255 | color: Style.backgroundColor, 256 | border: _bottomBorder, 257 | ), 258 | child: new Row( 259 | children: [ 260 | new Container( 261 | margin: new EdgeInsets.only(right: 10.0), 262 | child: new Image.network( 263 | '${Config.ImgBaseUrl}${restaurant.imagePath}', 264 | width: 60.0, 265 | ), 266 | ), 267 | new Expanded( 268 | child: new Column( 269 | crossAxisAlignment: CrossAxisAlignment.start, 270 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 271 | children: [ 272 | new Row( 273 | children: [ 274 | new Expanded( 275 | child: new Text( 276 | restaurant.name, 277 | overflow: TextOverflow.ellipsis, 278 | ), 279 | ), 280 | new Container( 281 | padding: new EdgeInsets.symmetric(horizontal: 1.0), 282 | margin: new EdgeInsets.only(left: 5.0), 283 | decoration: new BoxDecoration( 284 | border: new Border.all( 285 | color: _payTextColor, 286 | ), 287 | ), 288 | child: new Text( 289 | '支付', 290 | style: new TextStyle( 291 | color: _payTextColor, 292 | fontSize: 8.0, 293 | ), 294 | ), 295 | ), 296 | ], 297 | ), 298 | new Text('月售 ${restaurant.recentOrderNum} 单'), 299 | new Text( 300 | '${restaurant.floatMinimumOrderAmount} 元起送 / 距离 ${restaurant 301 | .distance}'), 302 | ], 303 | ), 304 | ), 305 | ], 306 | ), 307 | ); 308 | } 309 | 310 | void _removeHistory(String value) { 311 | _history.remove(value); 312 | LocalStorage.setSearchHistory(_history); 313 | setState(() {}); 314 | } 315 | 316 | void _clearHistory() { 317 | List history = []; 318 | LocalStorage.setSearchHistory(history); 319 | setState(() { 320 | _history = history; 321 | }); 322 | } 323 | } 324 | --------------------------------------------------------------------------------