├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── key.jks │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── wayne │ │ │ │ └── flutter_wtrip │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── splash_screen.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── asr_plugin │ ├── .gitignore │ ├── build.gradle │ ├── libs │ │ └── bdasr_V3_20191210_81acdf5.jar │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── asr_plugin │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── asr_plugin │ │ │ │ ├── AsrManager.java │ │ │ │ ├── AsrPlugin.java │ │ │ │ ├── OnAsrListener.java │ │ │ │ ├── RecogEventAdapter.java │ │ │ │ ├── RecogResult.java │ │ │ │ └── ResultStateful.java │ │ ├── jniLibs │ │ │ ├── arm64-v8a │ │ │ │ ├── libBaiduSpeechSDK.so │ │ │ │ └── libvad.dnn.so │ │ │ ├── armeabi-v7a │ │ │ │ ├── libBaiduSpeechSDK.so │ │ │ │ └── libvad.dnn.so │ │ │ ├── x86 │ │ │ │ ├── libBaiduSpeechSDK.so │ │ │ │ └── libvad.dnn.so │ │ │ └── x86_64 │ │ │ │ ├── libBaiduSpeechSDK.so │ │ │ │ └── libvad.dnn.so │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── asr_plugin │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── data │ └── cities.json ├── doc └── Flutter版携程AppDemo接口文档.md ├── images ├── launch_image.png └── 君伟说.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── dao │ ├── home_dao.dart │ ├── search_dao.dart │ ├── travel_page_dao.dart │ └── travel_tab_dao.dart ├── main.dart ├── model │ ├── common_model.dart │ ├── config_model.dart │ ├── gridNav_model.dart │ ├── home_model.dart │ ├── sales_box_model.dart │ ├── search_model.dart │ ├── travel_page_model.dart │ └── travel_tab_model.dart ├── navigator │ └── tab_navigator.dart ├── pages │ ├── city_page.dart │ ├── home_page.dart │ ├── my_page.dart │ ├── search_page.dart │ ├── speak_page.dart │ ├── travel_page.dart │ └── travel_tab_page.dart ├── plugin │ └── asr_manager.dart ├── util │ └── navigator_util.dart └── widgets │ ├── cached_image.dart │ ├── grid_nav.dart │ ├── loading_container.dart │ ├── local_nav.dart │ ├── sales_box.dart │ ├── search_bar.dart │ ├── sub_nav.dart │ └── webview.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /.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: 659dc8129d4edb9166e9a0d600439d135740933f 8 | channel: dev 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter版高仿携程App 2 | 3 | #### 这是一个Flutter版本的仿携程App的demo,相对完整的学习一个APP的开发过程,更加高效的进行学习Flutter开发 4 | 5 | ## 模块与特性 6 | - 首页(已完成) 7 | - 搜索(已完成部分,缺少语音搜索功能) 8 | - 旅拍(瀑布流展示) 9 | - 我的(Webview展示) 10 | - 实现网络图片本地缓存 11 | - 选择定位城市列表 12 | 13 | ## 项目中使用到第三方 14 | - cupertino_icons: ^0.1.2(图标库) 15 | - flutter_swiper: ^1.1.6(首页轮播图) 16 | - http: ^0.12.0+4(网络请求) 17 | - flutter_webview_plugin: ^0.3.10+1(webview插件) 18 | - flutter_staggered_grid_view: ^0.3.0(网格布局,旅拍模块) 19 | 20 | 21 | ## Flutter中常用第三方 22 | - [cached_network_image](https://pub.dev/packages/cached_network_image) 图片缓存框架 23 | - [http](https://pub.dev/packages/http) 网络请求框架 24 | - [fluttertoast](https://pub.dev/packages/fluttertoast) 吐司提示框架 25 | - [flutter_webview_plugin](https://pub.dev/packages/flutter_webview_plugin) webView框架 26 | - [webview_flutter](https://pub.dev/packages/webview_flutter)WebView 组件 27 | - [flutter_swiper](https://pub.dev/packages/flutter_swiper) 轮播图框架 28 | - [flustars](https://pub.dev/packages/flustars)flustars(Flutter常用工具类库) 29 | - [pull_to_refresh](https://pub.dev/packages/pull_to_refresh) 上拉刷新,下拉加载框架 30 | - [chewie](https://pub.dev/packages/chewie)Video 视频播放器框架 31 | - [shared_preferences](https://pub.dev/packages/shared_preferences) 本地缓存框架 32 | - [flutter_splash_screen](https://pub.dev/packages/flutter_splash_screen) 启动白屏处理框架 33 | - [image_picker](https://pub.dev/packages/image_picker) 相册取图/拍照框架 34 | - [multi_image_picker](https://pub.dev/packages/multi_image_picker) 多图选择 35 | - [share](https://pub.dev/packages/share) 分享框架 36 | - [provider](https://pub.dev/packages/provider) 状态框架 37 | - [Get](https://pub.dev/packages/get) 路由框架 38 | - [azlistview](https://github.com/flutterchina/azlistview)(Flutter 城市列表,联系人列表,自定义Header,索引,悬停效果) 39 | - [dio](https://github.com/flutterchina/dio)dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等... 40 | - [cached_network_image](https://pub.flutter-io.cn/packages/cached_network_image)网络图片本地缓存 41 | - [event_bus](https://pub.flutter-io.cn/packages/event_bus#-installing-tab-)事件总线,用户消息传递 42 | - [Json_model](https://pub.dev/packages/json_model)根据Json文件自动生成DartModal类 43 | - [flutter_spinkit](https://pub.dev/packages/flutter_spinkit)丰富多样的加载指示器合集 44 | 45 | - [Flukit](https://github.com/flutterchina/flukit)flukit (Flutter UI Kit)是一个Flutter Widget**库。 46 | 47 | 48 | ## 注意内容 49 | - 新AzListView data需要a-z排过序的列表,请自行调用SuspensionUtil.sortListBySuspensionTag(list)。 50 | 51 | ## 关于我 52 | 53 | 我曾是一个Android开发工程师,现在主要使用ReactNative跨平台框架开发App,始终保持对新技术的学习和探索。 54 | 55 | - WX: wayne214 56 | - 博客:https://blog.csdn.net/wayne214 57 | - 掘金:https://juejin.im/user/58fdbbcbda2f60005dcbe47d 58 | - 技术网站:https://wayne214.github.io/ 59 | 60 | 61 | > 技术交流可关注微信公众号【君伟说】,加我好友(vx:wayne214)一起探讨 62 | 63 | > 微信交流群:加好友(备注技术交流)邀你入群,抱团学习共进步 64 | 65 | ![君伟说](https://github.com/wayne214/flutter_wtrip/blob/master/images/%E5%90%9B%E4%BC%9F%E8%AF%B4.png) -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | defaultConfig { 32 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 33 | applicationId "com.wayne.flutter_wtrip" 34 | minSdkVersion 16 35 | targetSdkVersion 28 36 | versionCode flutterVersionCode.toInteger() 37 | versionName flutterVersionName 38 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 39 | 40 | ndk{ 41 | abiFilters "arm64-v7a","arm64-v8a","x86_64","x86" 42 | } 43 | } 44 | 45 | sourceSets { 46 | main.java.srcDirs += 'src/main/kotlin' 47 | } 48 | 49 | lintOptions { 50 | disable 'InvalidPackage' 51 | } 52 | 53 | signingConfigs { 54 | release { 55 | if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) { 56 | storeFile file(MYAPP_RELEASE_STORE_FILE) 57 | storePassword MYAPP_RELEASE_STORE_PASSWORD 58 | keyAlias MYAPP_RELEASE_KEY_ALIAS 59 | keyPassword MYAPP_RELEASE_KEY_PASSWORD 60 | } 61 | } 62 | } 63 | 64 | buildTypes { 65 | release { 66 | // TODO: Add your own signing config for the release build. 67 | // Signing with the debug keys for now, so `flutter run --release` works. 68 | signingConfig signingConfigs.release 69 | 70 | minifyEnabled true 71 | useProguard true 72 | 73 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 74 | } 75 | 76 | debug { 77 | signingConfig signingConfigs.debug 78 | } 79 | } 80 | 81 | 82 | //百度语音集成添加 83 | packagingOptions { 84 | // 确保app与asr_plugin都依赖的libflutter.so merge时不冲突@https://github.com/card-io/card.io-Android-SDK/issues/186#issuecomment-427552552 85 | pickFirst 'lib/x86_64/libflutter.so' 86 | pickFirst 'lib/x86/libflutter.so' 87 | pickFirst 'lib/arm64-v8a/libflutter.so' 88 | pickFirst 'lib/arm64-v8a/libapp.so' 89 | pickFirst 'lib/armeabi-v7a/libapp.so' 90 | } 91 | } 92 | 93 | flutter { 94 | source '../..' 95 | } 96 | 97 | dependencies { 98 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 99 | testImplementation 'junit:junit:4.12' 100 | androidTestImplementation 'androidx.test:runner:1.1.1' 101 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 102 | 103 | 104 | implementation project(path: ':asr_plugin') 105 | // implementation project(':flutter') 106 | } 107 | -------------------------------------------------------------------------------- /android/app/key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/key.jks -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.baidu.speech.**{*;} 24 | 25 | #Flutter Wrapper 26 | -keep class io.flutter.app.** { *; } 27 | -keep class io.flutter.plugin.** { *; } 28 | -keep class io.flutter.util.** { *; } 29 | -keep class io.flutter.view.** { *; } 30 | -keep class io.flutter.** { *; } 31 | -keep class io.flutter.plugins.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/wayne/flutter_wtrip/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wayne.flutter_wtrip 2 | 3 | import android.os.Bundle 4 | import android.os.PersistableBundle 5 | import androidx.annotation.NonNull; 6 | //import io.flutter.embedding.android.FlutterActivity 7 | import io.flutter.embedding.engine.FlutterEngine 8 | import io.flutter.plugins.GeneratedPluginRegistrant 9 | import com.example.asr_plugin.AsrPlugin 10 | import io.flutter.app.FlutterActivity 11 | 12 | class MainActivity: FlutterActivity() { 13 | // fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 14 | // GeneratedPluginRegistrant.registerWith(flutterEngine); 15 | // } 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | GeneratedPluginRegistrant.registerWith(FlutterEngine(this)); 20 | AsrPlugin.registerWith(registrarFor("com.example.asr_plugin.AsrPlugin")); 21 | } 22 | 23 | // private void fun registerSelfPlugin() { 24 | // AsrPlugin.registerWith(registrarFor("com.example.asr_plugin.AsrPlugin")); 25 | // } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/splash_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/app/src/main/res/mipmap-xxxhdpi/splash_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/asr_plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/asr_plugin/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 | 11 | apply plugin: 'com.android.library' 12 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 13 | 14 | android { 15 | compileSdkVersion 28 16 | 17 | 18 | 19 | defaultConfig { 20 | minSdkVersion 16 21 | targetSdkVersion 28 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 26 | 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | } 37 | 38 | flutter { 39 | source '../..' 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(dir: 'libs', include: ['*.jar']) 44 | 45 | implementation 'com.android.support:appcompat-v7:29.+' 46 | testImplementation 'junit:junit:4.12' 47 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 48 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 49 | 50 | implementation files('libs/bdasr_V3_20191210_81acdf5.jar') 51 | } 52 | -------------------------------------------------------------------------------- /android/asr_plugin/libs/bdasr_V3_20191210_81acdf5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/libs/bdasr_V3_20191210_81acdf5.jar -------------------------------------------------------------------------------- /android/asr_plugin/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.baidu.speech.**{*;} 24 | 25 | -------------------------------------------------------------------------------- /android/asr_plugin/src/androidTest/java/com/example/asr_plugin/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.asr_plugin.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/AsrManager.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.baidu.speech.EventManager; 7 | import com.baidu.speech.EventManagerFactory; 8 | import com.baidu.speech.asr.SpeechConstant; 9 | 10 | import org.json.JSONObject; 11 | 12 | import java.util.Map; 13 | 14 | public class AsrManager { 15 | private static final String TAG = "AsrManager"; 16 | /** 17 | * SDK 内部核心 EventManager 类 18 | */ 19 | private EventManager asr; 20 | 21 | // SDK 内部核心 事件回调类, 用于开发者写自己的识别回调逻辑 22 | private RecogEventAdapter eventListener; 23 | 24 | // 未release前,只能new一个 25 | private static volatile boolean isInited = false; 26 | 27 | /** 28 | * 初始化 提供 EventManagerFactory需要的Context和RecogEventAdapter 29 | * 30 | * @param context 31 | * @param listener 识别状态和结果回调 32 | */ 33 | public AsrManager(Context context, OnAsrListener listener) { 34 | if (isInited) { 35 | Log.e(TAG, "还未调用release(),请勿新建一个新类"); 36 | throw new RuntimeException("还未调用release(),请勿新建一个新类"); 37 | } 38 | isInited = true; 39 | // SDK集成步骤 初始化asr的EventManager示例,多次得到的类,只能选一个使用 40 | asr = EventManagerFactory.create(context, "asr"); 41 | // SDK集成步骤 设置回调event, 识别引擎会回调这个类告知重要状态和识别结果 42 | asr.registerListener(eventListener = new RecogEventAdapter(listener)); 43 | } 44 | 45 | /** 46 | * @param params 47 | */ 48 | public void start(Map params) { 49 | // SDK集成步骤 拼接识别参数 50 | String json = new JSONObject(params).toString(); 51 | Log.i(TAG + ".Debug", "识别参数(反馈请带上此行日志)" + json); 52 | asr.send(SpeechConstant.ASR_START, json, null, 0, 0); 53 | } 54 | 55 | 56 | /** 57 | * 提前结束录音等待识别结果。 58 | */ 59 | public void stop() { 60 | Log.i(TAG, "停止录音"); 61 | // SDK 集成步骤(可选)停止录音 62 | if (!isInited) { 63 | throw new RuntimeException("release() was called"); 64 | } 65 | asr.send(SpeechConstant.ASR_STOP, "{}", null, 0, 0); 66 | } 67 | 68 | /** 69 | * 取消本次识别,取消后将立即停止不会返回识别结果。 70 | * cancel 与stop的区别是 cancel在stop的基础上,完全停止整个识别流程, 71 | */ 72 | public void cancel() { 73 | Log.i(TAG, "取消识别"); 74 | if (!isInited) { 75 | throw new RuntimeException("release() was called"); 76 | } 77 | // SDK集成步骤 (可选) 取消本次识别 78 | asr.send(SpeechConstant.ASR_CANCEL, "{}", null, 0, 0); 79 | } 80 | 81 | public void release() { 82 | if (asr == null) { 83 | return; 84 | } 85 | cancel(); 86 | // SDK 集成步骤(可选),卸载listener 87 | asr.unregisterListener(eventListener); 88 | asr = null; 89 | isInited = false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/AsrPlugin.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.pm.PackageManager; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.core.app.ActivityCompat; 10 | import androidx.core.content.ContextCompat; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Map; 14 | 15 | import io.flutter.plugin.common.MethodCall; 16 | import io.flutter.plugin.common.MethodChannel; 17 | import io.flutter.plugin.common.PluginRegistry; 18 | 19 | public class AsrPlugin implements MethodChannel.MethodCallHandler { 20 | private final static String TAG = "AsrPlugin"; 21 | private final Activity activity; 22 | private ResultStateful resultStateful; 23 | private AsrManager asrManager; 24 | 25 | public static void registerWith(PluginRegistry.Registrar registrar) { 26 | MethodChannel channel = new MethodChannel(registrar.messenger(), "asr_plugin"); 27 | AsrPlugin instance = new AsrPlugin(registrar); 28 | channel.setMethodCallHandler(instance); 29 | } 30 | 31 | public AsrPlugin(PluginRegistry.Registrar registrar) { 32 | this.activity = registrar.activity(); 33 | } 34 | 35 | @Override 36 | public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { 37 | initPermission(); 38 | switch (methodCall.method) { 39 | case "start": 40 | resultStateful = ResultStateful.of(result); 41 | start(methodCall, resultStateful); 42 | break; 43 | case "stop": 44 | stop(methodCall, result); 45 | break; 46 | case "cancel": 47 | cancel(methodCall, result); 48 | break; 49 | default: 50 | result.notImplemented(); 51 | } 52 | } 53 | 54 | private void start(MethodCall call, ResultStateful result) { 55 | if (activity == null) { 56 | Log.e(TAG, "Ignored start, current activity is null."); 57 | result.error("Ignored start, current activity is null.", null, null); 58 | return; 59 | } 60 | if (getAsrManager() != null) { 61 | getAsrManager().start(call.arguments instanceof Map ? (Map) call.arguments : null); 62 | } else { 63 | Log.e(TAG, "Ignored start, current getAsrManager is null."); 64 | result.error("Ignored start, current getAsrManager is null.", null, null); 65 | } 66 | } 67 | 68 | private void stop(MethodCall call, MethodChannel.Result result) { 69 | if (asrManager != null) { 70 | asrManager.stop(); 71 | } 72 | } 73 | 74 | private void cancel(MethodCall call, MethodChannel.Result result) { 75 | if (asrManager != null) { 76 | asrManager.cancel(); 77 | } 78 | } 79 | 80 | @Nullable 81 | private AsrManager getAsrManager() { 82 | if (asrManager == null) { 83 | if (activity != null && !activity.isFinishing()) { 84 | asrManager = new AsrManager(activity, onAsrListener); 85 | } 86 | } 87 | return asrManager; 88 | } 89 | 90 | /** 91 | * android 6.0 以上需要动态申请权限 92 | */ 93 | private void initPermission() { 94 | String permissions[] = {Manifest.permission.RECORD_AUDIO, 95 | Manifest.permission.ACCESS_NETWORK_STATE, 96 | Manifest.permission.INTERNET, 97 | Manifest.permission.READ_PHONE_STATE, 98 | Manifest.permission.WRITE_EXTERNAL_STORAGE 99 | }; 100 | 101 | ArrayList toApplyList = new ArrayList(); 102 | 103 | for (String perm : permissions) { 104 | if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity, perm)) { 105 | toApplyList.add(perm); 106 | //进入到这里代表没有权限. 107 | 108 | } 109 | } 110 | String tmpList[] = new String[toApplyList.size()]; 111 | if (!toApplyList.isEmpty()) { 112 | ActivityCompat.requestPermissions(activity, toApplyList.toArray(tmpList), 123); 113 | } 114 | 115 | } 116 | 117 | private OnAsrListener onAsrListener = new OnAsrListener() { 118 | @Override 119 | public void onAsrReady() { 120 | 121 | } 122 | 123 | @Override 124 | public void onAsrBegin() { 125 | 126 | } 127 | 128 | @Override 129 | public void onAsrEnd() { 130 | 131 | } 132 | 133 | @Override 134 | public void onAsrPartialResult(String[] results, RecogResult recogResult) { 135 | 136 | } 137 | 138 | @Override 139 | public void onAsrOnlineNluResult(String nluResult) { 140 | 141 | } 142 | 143 | @Override 144 | public void onAsrFinalResult(String[] results, RecogResult recogResult) { 145 | if (resultStateful != null) { 146 | resultStateful.success(results[0]); 147 | } 148 | } 149 | 150 | @Override 151 | public void onAsrFinish(RecogResult recogResult) { 152 | 153 | } 154 | 155 | @Override 156 | public void onAsrFinishError(int errorCode, int subErrorCode, String descMessage, RecogResult recogResult) { 157 | if (resultStateful != null) { 158 | resultStateful.error(descMessage, null, null); 159 | } 160 | } 161 | 162 | @Override 163 | public void onAsrLongFinish() { 164 | 165 | } 166 | 167 | @Override 168 | public void onAsrVolume(int volumePercent, int volume) { 169 | 170 | } 171 | 172 | @Override 173 | public void onAsrAudio(byte[] data, int offset, int length) { 174 | 175 | } 176 | 177 | @Override 178 | public void onAsrExit() { 179 | 180 | } 181 | 182 | @Override 183 | public void onOfflineLoaded() { 184 | 185 | } 186 | 187 | @Override 188 | public void onOfflineUnLoaded() { 189 | 190 | } 191 | }; 192 | } 193 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/OnAsrListener.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | /** 4 | * 与SDK中回调参数的对应关系定义在RecogEventAdapter类中 5 | */ 6 | 7 | public interface OnAsrListener { 8 | 9 | /** 10 | * CALLBACK_EVENT_ASR_READY 11 | * ASR_START 输入事件调用后,引擎准备完毕 12 | */ 13 | void onAsrReady(); 14 | 15 | /** 16 | * CALLBACK_EVENT_ASR_BEGIN 17 | * onAsrReady后检查到用户开始说话 18 | */ 19 | void onAsrBegin(); 20 | 21 | /** 22 | * CALLBACK_EVENT_ASR_END 23 | * 检查到用户开始说话停止,或者ASR_STOP 输入事件调用后, 24 | */ 25 | void onAsrEnd(); 26 | 27 | /** 28 | * CALLBACK_EVENT_ASR_PARTIAL resultType=partial_result 29 | * onAsrBegin 后 随着用户的说话,返回的临时结果 30 | * 31 | * @param results 可能返回多个结果,请取第一个结果 32 | * @param recogResult 完整的结果 33 | */ 34 | void onAsrPartialResult(String[] results, RecogResult recogResult); 35 | 36 | /** 37 | * 语音的在线语义结果 38 | *

39 | * CALLBACK_EVENT_ASR_PARTIAL resultType=nlu_result 40 | * 41 | * @param nluResult 42 | */ 43 | void onAsrOnlineNluResult(String nluResult); 44 | 45 | /** 46 | * CALLBACK_EVENT_ASR_PARTIAL resultType=final_result 47 | * 最终的识别结果 48 | * 49 | * @param results 可能返回多个结果,请取第一个结果 50 | * @param recogResult 完整的结果 51 | */ 52 | void onAsrFinalResult(String[] results, RecogResult recogResult); 53 | 54 | /** 55 | * CALLBACK_EVENT_ASR_FINISH 56 | * 57 | * @param recogResult 完整的结果 58 | */ 59 | void onAsrFinish(RecogResult recogResult); 60 | 61 | /** 62 | * CALLBACK_EVENT_ASR_FINISH error!=0 63 | * 64 | * @param errorCode 65 | * @param subErrorCode 66 | * @param descMessage 67 | * @param recogResult 68 | */ 69 | void onAsrFinishError(int errorCode, int subErrorCode, String descMessage, RecogResult recogResult); 70 | 71 | /** 72 | * 长语音识别结束 73 | */ 74 | void onAsrLongFinish(); 75 | 76 | /** 77 | * CALLBACK_EVENT_ASR_VOLUME 78 | * 音量回调 79 | * 80 | * @param volumePercent 音量的相对值,百分比,0-100 81 | * @param volume 音量绝对值 82 | */ 83 | void onAsrVolume(int volumePercent, int volume); 84 | 85 | /** 86 | * CALLBACK_EVENT_ASR_AUDIO 87 | * 88 | * @param data pcm格式,16bits 16000采样率 89 | * @param offset 90 | * @param length 91 | */ 92 | void onAsrAudio(byte[] data, int offset, int length); 93 | 94 | /** 95 | * CALLBACK_EVENT_ASR_EXIT 96 | * 引擎完成整个识别,空闲中 97 | */ 98 | void onAsrExit(); 99 | 100 | /** 101 | * CALLBACK_EVENT_ASR_LOADED 102 | * 离线命令词资源加载成功 103 | */ 104 | void onOfflineLoaded(); 105 | 106 | /** 107 | * CALLBACK_EVENT_ASR_UNLOADED 108 | * 离线命令词资源释放成功 109 | */ 110 | void onOfflineUnLoaded(); 111 | } 112 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/RecogEventAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import android.util.Log; 4 | 5 | import com.baidu.speech.EventListener; 6 | import com.baidu.speech.asr.SpeechConstant; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | /** 12 | * Created by fujiayi on 2017/6/14. 13 | */ 14 | 15 | public class RecogEventAdapter implements EventListener { 16 | 17 | private OnAsrListener listener; 18 | 19 | private static final String TAG = "RecogEventAdapter"; 20 | 21 | public RecogEventAdapter(OnAsrListener listener) { 22 | this.listener = listener; 23 | } 24 | 25 | // 基于DEMO集成3.1 开始回调事件 26 | @Override 27 | public void onEvent(String name, String params, byte[] data, int offset, int length) { 28 | String currentJson = params; 29 | String logMessage = "name:" + name + "; params:" + params; 30 | 31 | // logcat 中 搜索RecogEventAdapter,即可以看见下面一行的日志 32 | Log.i(TAG, logMessage); 33 | if (false) { // 可以调试,不需要后续逻辑 34 | return; 35 | } 36 | if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_LOADED)) { 37 | listener.onOfflineLoaded(); 38 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_UNLOADED)) { 39 | listener.onOfflineUnLoaded(); 40 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_READY)) { 41 | // 引擎准备就绪,可以开始说话 42 | listener.onAsrReady(); 43 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_BEGIN)) { 44 | // 检测到用户的已经开始说话 45 | listener.onAsrBegin(); 46 | 47 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_END)) { 48 | // 检测到用户的已经停止说话 49 | listener.onAsrEnd(); 50 | 51 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL)) { 52 | RecogResult recogResult = RecogResult.parseJson(params); 53 | // 临时识别结果, 长语音模式需要从此消息中取出结果 54 | String[] results = recogResult.getResultsRecognition(); 55 | if (recogResult.isFinalResult()) { 56 | listener.onAsrFinalResult(results, recogResult); 57 | } else if (recogResult.isPartialResult()) { 58 | listener.onAsrPartialResult(results, recogResult); 59 | } else if (recogResult.isNluResult()) { 60 | listener.onAsrOnlineNluResult(new String(data, offset, length)); 61 | } 62 | 63 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_FINISH)) { 64 | // 识别结束, 最终识别结果或可能的错误 65 | RecogResult recogResult = RecogResult.parseJson(params); 66 | if (recogResult.hasError()) { 67 | int errorCode = recogResult.getError(); 68 | int subErrorCode = recogResult.getSubError(); 69 | Log.e(TAG, "asr error:" + params); 70 | listener.onAsrFinishError(errorCode, subErrorCode, recogResult.getDesc(), recogResult); 71 | } else { 72 | listener.onAsrFinish(recogResult); 73 | } 74 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_LONG_SPEECH)) { // 长语音 75 | listener.onAsrLongFinish(); // 长语音 76 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_EXIT)) { 77 | listener.onAsrExit(); 78 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_VOLUME)) { 79 | // Logger.info(TAG, "asr volume event:" + params); 80 | Volume vol = parseVolumeJson(params); 81 | listener.onAsrVolume(vol.volumePercent, vol.volume); 82 | } else if (name.equals(SpeechConstant.CALLBACK_EVENT_ASR_AUDIO)) { 83 | if (data.length != length) { 84 | Log.e(TAG, "internal error: asr.audio callback data length is not equal to length param"); 85 | } 86 | listener.onAsrAudio(data, offset, length); 87 | } 88 | } 89 | 90 | private Volume parseVolumeJson(String jsonStr) { 91 | Volume vol = new Volume(); 92 | vol.origalJson = jsonStr; 93 | try { 94 | JSONObject json = new JSONObject(jsonStr); 95 | vol.volumePercent = json.getInt("volume-percent"); 96 | vol.volume = json.getInt("volume"); 97 | } catch (JSONException e) { 98 | e.printStackTrace(); 99 | } 100 | return vol; 101 | } 102 | 103 | private class Volume { 104 | private int volumePercent = -1; 105 | private int volume = -1; 106 | private String origalJson; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/RecogResult.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | /** 8 | * Created by fujiayi on 2017/6/24. 9 | */ 10 | public class RecogResult { 11 | private static final int ERROR_NONE = 0; 12 | 13 | private String origalJson; 14 | private String[] resultsRecognition; 15 | private String origalResult; 16 | private String sn; // 日志id, 请求有问题请提问带上sn 17 | private String desc; 18 | private String resultType; 19 | private int error = -1; 20 | private int subError = -1; 21 | 22 | public static RecogResult parseJson(String jsonStr) { 23 | RecogResult result = new RecogResult(); 24 | result.setOrigalJson(jsonStr); 25 | try { 26 | JSONObject json = new JSONObject(jsonStr); 27 | int error = json.optInt("error"); 28 | int subError = json.optInt("sub_error"); 29 | result.setError(error); 30 | result.setDesc(json.optString("desc")); 31 | result.setResultType(json.optString("result_type")); 32 | result.setSubError(subError); 33 | if (error == ERROR_NONE) { 34 | result.setOrigalResult(json.getString("origin_result")); 35 | JSONArray arr = json.optJSONArray("results_recognition"); 36 | if (arr != null) { 37 | int size = arr.length(); 38 | String[] recogs = new String[size]; 39 | for (int i = 0; i < size; i++) { 40 | recogs[i] = arr.getString(i); 41 | } 42 | result.setResultsRecognition(recogs); 43 | } 44 | 45 | 46 | } 47 | } catch (JSONException e) { 48 | e.printStackTrace(); 49 | } 50 | 51 | return result; 52 | } 53 | 54 | public boolean hasError() { 55 | return error != ERROR_NONE; 56 | } 57 | 58 | public boolean isFinalResult() { 59 | return "final_result".equals(resultType); 60 | } 61 | 62 | 63 | public boolean isPartialResult() { 64 | return "partial_result".equals(resultType); 65 | } 66 | 67 | public boolean isNluResult() { 68 | return "nlu_result".equals(resultType); 69 | } 70 | 71 | public String getOrigalJson() { 72 | return origalJson; 73 | } 74 | 75 | public void setOrigalJson(String origalJson) { 76 | this.origalJson = origalJson; 77 | } 78 | 79 | public String[] getResultsRecognition() { 80 | return resultsRecognition; 81 | } 82 | 83 | public void setResultsRecognition(String[] resultsRecognition) { 84 | this.resultsRecognition = resultsRecognition; 85 | } 86 | 87 | public String getSn() { 88 | return sn; 89 | } 90 | 91 | public void setSn(String sn) { 92 | this.sn = sn; 93 | } 94 | 95 | public int getError() { 96 | return error; 97 | } 98 | 99 | public void setError(int error) { 100 | this.error = error; 101 | } 102 | 103 | public String getDesc() { 104 | return desc; 105 | } 106 | 107 | public void setDesc(String desc) { 108 | this.desc = desc; 109 | } 110 | 111 | public String getOrigalResult() { 112 | return origalResult; 113 | } 114 | 115 | public void setOrigalResult(String origalResult) { 116 | this.origalResult = origalResult; 117 | } 118 | 119 | public String getResultType() { 120 | return resultType; 121 | } 122 | 123 | public void setResultType(String resultType) { 124 | this.resultType = resultType; 125 | } 126 | 127 | public int getSubError() { 128 | return subError; 129 | } 130 | 131 | public void setSubError(int subError) { 132 | this.subError = subError; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/java/com/example/asr_plugin/ResultStateful.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import io.flutter.plugin.common.MethodChannel; 8 | 9 | public class ResultStateful implements MethodChannel.Result { 10 | private final static String TAG = "ResultStateful"; 11 | private MethodChannel.Result result; 12 | private boolean called; 13 | 14 | public static ResultStateful of(MethodChannel.Result result) { 15 | return new ResultStateful(result); 16 | } 17 | 18 | private ResultStateful(MethodChannel.Result result) { 19 | this.result = result; 20 | } 21 | 22 | @Override 23 | public void success(@Nullable Object o) { 24 | if (called) { 25 | printError(); 26 | return; 27 | } 28 | called = true; 29 | result.success(o); 30 | } 31 | 32 | @Override 33 | public void error(String s, @Nullable String s1, @Nullable Object o) { 34 | if (called) { 35 | printError(); 36 | return; 37 | } 38 | called = true; 39 | result.error(s, s1, o); 40 | } 41 | 42 | @Override 43 | public void notImplemented() { 44 | if (called) { 45 | printError(); 46 | return; 47 | } 48 | called = true; 49 | result.notImplemented(); 50 | 51 | } 52 | 53 | private void printError() { 54 | Log.e(TAG, "error:result called"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/arm64-v8a/libBaiduSpeechSDK.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/arm64-v8a/libvad.dnn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/arm64-v8a/libvad.dnn.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libBaiduSpeechSDK.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/armeabi-v7a/libvad.dnn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/armeabi-v7a/libvad.dnn.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/x86/libBaiduSpeechSDK.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86/libBaiduSpeechSDK.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/x86/libvad.dnn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86/libvad.dnn.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/x86_64/libBaiduSpeechSDK.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86_64/libBaiduSpeechSDK.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/jniLibs/x86_64/libvad.dnn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/android/asr_plugin/src/main/jniLibs/x86_64/libvad.dnn.so -------------------------------------------------------------------------------- /android/asr_plugin/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | asr_plugin 3 | 4 | -------------------------------------------------------------------------------- /android/asr_plugin/src/test/java/com/example/asr_plugin/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.asr_plugin; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | 6 | 7 | MYAPP_RELEASE_STORE_FILE=key.jks 8 | MYAPP_RELEASE_KEY_ALIAS=key 9 | MYAPP_RELEASE_STORE_PASSWORD=junwei214 10 | MYAPP_RELEASE_KEY_PASSWORD=junwei214 -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jan 31 10:48:39 CST 2020 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.10.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':asr_plugin' 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 | 17 | //setBinding(new Binding([gradle: this])) 18 | 19 | //evaluate(new File( 20 | // settingsDir.parentFile, 21 | // 'flutter_module/.android/include_flutter.groovy' 22 | //)) 23 | -------------------------------------------------------------------------------- /assets/data/cities.json: -------------------------------------------------------------------------------- 1 | [{"code":"1301","name":"石家庄市","provinceCode":"13"},{"code":"1302","name":"唐山市","provinceCode":"13"},{"code":"1303","name":"秦皇岛市","provinceCode":"13"},{"code":"1304","name":"邯郸市","provinceCode":"13"},{"code":"1305","name":"邢台市","provinceCode":"13"},{"code":"1306","name":"保定市","provinceCode":"13"},{"code":"1307","name":"张家口市","provinceCode":"13"},{"code":"1308","name":"承德市","provinceCode":"13"},{"code":"1309","name":"沧州市","provinceCode":"13"},{"code":"1310","name":"廊坊市","provinceCode":"13"},{"code":"1311","name":"衡水市","provinceCode":"13"},{"code":"1401","name":"太原市","provinceCode":"14"},{"code":"1402","name":"大同市","provinceCode":"14"},{"code":"1403","name":"阳泉市","provinceCode":"14"},{"code":"1404","name":"长治市","provinceCode":"14"},{"code":"1405","name":"晋城市","provinceCode":"14"},{"code":"1406","name":"朔州市","provinceCode":"14"},{"code":"1407","name":"晋中市","provinceCode":"14"},{"code":"1408","name":"运城市","provinceCode":"14"},{"code":"1409","name":"忻州市","provinceCode":"14"},{"code":"1410","name":"临汾市","provinceCode":"14"},{"code":"1411","name":"吕梁市","provinceCode":"14"},{"code":"1501","name":"呼和浩特市","provinceCode":"15"},{"code":"1502","name":"包头市","provinceCode":"15"},{"code":"1503","name":"乌海市","provinceCode":"15"},{"code":"1504","name":"赤峰市","provinceCode":"15"},{"code":"1505","name":"通辽市","provinceCode":"15"},{"code":"1506","name":"鄂尔多斯市","provinceCode":"15"},{"code":"1507","name":"呼伦贝尔市","provinceCode":"15"},{"code":"1508","name":"巴彦淖尔市","provinceCode":"15"},{"code":"1509","name":"乌兰察布市","provinceCode":"15"},{"code":"1522","name":"兴安盟","provinceCode":"15"},{"code":"1525","name":"锡林郭勒盟","provinceCode":"15"},{"code":"1529","name":"阿拉善盟","provinceCode":"15"},{"code":"2101","name":"沈阳市","provinceCode":"21"},{"code":"2102","name":"大连市","provinceCode":"21"},{"code":"2103","name":"鞍山市","provinceCode":"21"},{"code":"2104","name":"抚顺市","provinceCode":"21"},{"code":"2105","name":"本溪市","provinceCode":"21"},{"code":"2106","name":"丹东市","provinceCode":"21"},{"code":"2107","name":"锦州市","provinceCode":"21"},{"code":"2108","name":"营口市","provinceCode":"21"},{"code":"2109","name":"阜新市","provinceCode":"21"},{"code":"2110","name":"辽阳市","provinceCode":"21"},{"code":"2111","name":"盘锦市","provinceCode":"21"},{"code":"2112","name":"铁岭市","provinceCode":"21"},{"code":"2113","name":"朝阳市","provinceCode":"21"},{"code":"2114","name":"葫芦岛市","provinceCode":"21"},{"code":"2201","name":"长春市","provinceCode":"22"},{"code":"2202","name":"吉林市","provinceCode":"22"},{"code":"2203","name":"四平市","provinceCode":"22"},{"code":"2204","name":"辽源市","provinceCode":"22"},{"code":"2205","name":"通化市","provinceCode":"22"},{"code":"2206","name":"白山市","provinceCode":"22"},{"code":"2207","name":"松原市","provinceCode":"22"},{"code":"2208","name":"白城市","provinceCode":"22"},{"code":"2224","name":"延边朝鲜族自治州","provinceCode":"22"},{"code":"2301","name":"哈尔滨市","provinceCode":"23"},{"code":"2302","name":"齐齐哈尔市","provinceCode":"23"},{"code":"2303","name":"鸡西市","provinceCode":"23"},{"code":"2304","name":"鹤岗市","provinceCode":"23"},{"code":"2305","name":"双鸭山市","provinceCode":"23"},{"code":"2306","name":"大庆市","provinceCode":"23"},{"code":"2307","name":"伊春市","provinceCode":"23"},{"code":"2308","name":"佳木斯市","provinceCode":"23"},{"code":"2309","name":"七台河市","provinceCode":"23"},{"code":"2310","name":"牡丹江市","provinceCode":"23"},{"code":"2311","name":"黑河市","provinceCode":"23"},{"code":"2312","name":"绥化市","provinceCode":"23"},{"code":"2327","name":"大兴安岭地区","provinceCode":"23"},{"code":"3201","name":"南京市","provinceCode":"32"},{"code":"3202","name":"无锡市","provinceCode":"32"},{"code":"3203","name":"徐州市","provinceCode":"32"},{"code":"3204","name":"常州市","provinceCode":"32"},{"code":"3205","name":"苏州市","provinceCode":"32"},{"code":"3206","name":"南通市","provinceCode":"32"},{"code":"3207","name":"连云港市","provinceCode":"32"},{"code":"3208","name":"淮安市","provinceCode":"32"},{"code":"3209","name":"盐城市","provinceCode":"32"},{"code":"3210","name":"扬州市","provinceCode":"32"},{"code":"3211","name":"镇江市","provinceCode":"32"},{"code":"3212","name":"泰州市","provinceCode":"32"},{"code":"3213","name":"宿迁市","provinceCode":"32"},{"code":"3301","name":"杭州市","provinceCode":"33"},{"code":"3302","name":"宁波市","provinceCode":"33"},{"code":"3303","name":"温州市","provinceCode":"33"},{"code":"3304","name":"嘉兴市","provinceCode":"33"},{"code":"3305","name":"湖州市","provinceCode":"33"},{"code":"3306","name":"绍兴市","provinceCode":"33"},{"code":"3307","name":"金华市","provinceCode":"33"},{"code":"3308","name":"衢州市","provinceCode":"33"},{"code":"3309","name":"舟山市","provinceCode":"33"},{"code":"3310","name":"台州市","provinceCode":"33"},{"code":"3311","name":"丽水市","provinceCode":"33"},{"code":"3401","name":"合肥市","provinceCode":"34"},{"code":"3402","name":"芜湖市","provinceCode":"34"},{"code":"3403","name":"蚌埠市","provinceCode":"34"},{"code":"3404","name":"淮南市","provinceCode":"34"},{"code":"3405","name":"马鞍山市","provinceCode":"34"},{"code":"3406","name":"淮北市","provinceCode":"34"},{"code":"3407","name":"铜陵市","provinceCode":"34"},{"code":"3408","name":"安庆市","provinceCode":"34"},{"code":"3410","name":"黄山市","provinceCode":"34"},{"code":"3411","name":"滁州市","provinceCode":"34"},{"code":"3412","name":"阜阳市","provinceCode":"34"},{"code":"3413","name":"宿州市","provinceCode":"34"},{"code":"3415","name":"六安市","provinceCode":"34"},{"code":"3416","name":"亳州市","provinceCode":"34"},{"code":"3417","name":"池州市","provinceCode":"34"},{"code":"3418","name":"宣城市","provinceCode":"34"},{"code":"3501","name":"福州市","provinceCode":"35"},{"code":"3502","name":"厦门市","provinceCode":"35"},{"code":"3503","name":"莆田市","provinceCode":"35"},{"code":"3504","name":"三明市","provinceCode":"35"},{"code":"3505","name":"泉州市","provinceCode":"35"},{"code":"3506","name":"漳州市","provinceCode":"35"},{"code":"3507","name":"南平市","provinceCode":"35"},{"code":"3508","name":"龙岩市","provinceCode":"35"},{"code":"3509","name":"宁德市","provinceCode":"35"},{"code":"3601","name":"南昌市","provinceCode":"36"},{"code":"3602","name":"景德镇市","provinceCode":"36"},{"code":"3603","name":"萍乡市","provinceCode":"36"},{"code":"3604","name":"九江市","provinceCode":"36"},{"code":"3605","name":"新余市","provinceCode":"36"},{"code":"3606","name":"鹰潭市","provinceCode":"36"},{"code":"3607","name":"赣州市","provinceCode":"36"},{"code":"3608","name":"吉安市","provinceCode":"36"},{"code":"3609","name":"宜春市","provinceCode":"36"},{"code":"3610","name":"抚州市","provinceCode":"36"},{"code":"3611","name":"上饶市","provinceCode":"36"},{"code":"3701","name":"济南市","provinceCode":"37"},{"code":"3702","name":"青岛市","provinceCode":"37"},{"code":"3703","name":"淄博市","provinceCode":"37"},{"code":"3704","name":"枣庄市","provinceCode":"37"},{"code":"3705","name":"东营市","provinceCode":"37"},{"code":"3706","name":"烟台市","provinceCode":"37"},{"code":"3707","name":"潍坊市","provinceCode":"37"},{"code":"3708","name":"济宁市","provinceCode":"37"},{"code":"3709","name":"泰安市","provinceCode":"37"},{"code":"3710","name":"威海市","provinceCode":"37"},{"code":"3711","name":"日照市","provinceCode":"37"},{"code":"3712","name":"莱芜市","provinceCode":"37"},{"code":"3713","name":"临沂市","provinceCode":"37"},{"code":"3714","name":"德州市","provinceCode":"37"},{"code":"3715","name":"聊城市","provinceCode":"37"},{"code":"3716","name":"滨州市","provinceCode":"37"},{"code":"3717","name":"菏泽市","provinceCode":"37"},{"code":"4101","name":"郑州市","provinceCode":"41"},{"code":"4102","name":"开封市","provinceCode":"41"},{"code":"4103","name":"洛阳市","provinceCode":"41"},{"code":"4104","name":"平顶山市","provinceCode":"41"},{"code":"4105","name":"安阳市","provinceCode":"41"},{"code":"4106","name":"鹤壁市","provinceCode":"41"},{"code":"4107","name":"新乡市","provinceCode":"41"},{"code":"4108","name":"焦作市","provinceCode":"41"},{"code":"4109","name":"濮阳市","provinceCode":"41"},{"code":"4110","name":"许昌市","provinceCode":"41"},{"code":"4111","name":"漯河市","provinceCode":"41"},{"code":"4112","name":"三门峡市","provinceCode":"41"},{"code":"4113","name":"南阳市","provinceCode":"41"},{"code":"4114","name":"商丘市","provinceCode":"41"},{"code":"4115","name":"信阳市","provinceCode":"41"},{"code":"4116","name":"周口市","provinceCode":"41"},{"code":"4117","name":"驻马店市","provinceCode":"41"},{"code":"4201","name":"武汉市","provinceCode":"42"},{"code":"4202","name":"黄石市","provinceCode":"42"},{"code":"4203","name":"十堰市","provinceCode":"42"},{"code":"4205","name":"宜昌市","provinceCode":"42"},{"code":"4206","name":"襄阳市","provinceCode":"42"},{"code":"4207","name":"鄂州市","provinceCode":"42"},{"code":"4208","name":"荆门市","provinceCode":"42"},{"code":"4209","name":"孝感市","provinceCode":"42"},{"code":"4210","name":"荆州市","provinceCode":"42"},{"code":"4211","name":"黄冈市","provinceCode":"42"},{"code":"4212","name":"咸宁市","provinceCode":"42"},{"code":"4213","name":"随州市","provinceCode":"42"},{"code":"4228","name":"恩施土家族苗族自治州","provinceCode":"42"},{"code":"4301","name":"长沙市","provinceCode":"43"},{"code":"4302","name":"株洲市","provinceCode":"43"},{"code":"4303","name":"湘潭市","provinceCode":"43"},{"code":"4304","name":"衡阳市","provinceCode":"43"},{"code":"4305","name":"邵阳市","provinceCode":"43"},{"code":"4306","name":"岳阳市","provinceCode":"43"},{"code":"4307","name":"常德市","provinceCode":"43"},{"code":"4308","name":"张家界市","provinceCode":"43"},{"code":"4309","name":"益阳市","provinceCode":"43"},{"code":"4310","name":"郴州市","provinceCode":"43"},{"code":"4311","name":"永州市","provinceCode":"43"},{"code":"4312","name":"怀化市","provinceCode":"43"},{"code":"4313","name":"娄底市","provinceCode":"43"},{"code":"4331","name":"湘西土家族苗族自治州","provinceCode":"43"},{"code":"4401","name":"广州市","provinceCode":"44"},{"code":"4402","name":"韶关市","provinceCode":"44"},{"code":"4403","name":"深圳市","provinceCode":"44"},{"code":"4404","name":"珠海市","provinceCode":"44"},{"code":"4405","name":"汕头市","provinceCode":"44"},{"code":"4406","name":"佛山市","provinceCode":"44"},{"code":"4407","name":"江门市","provinceCode":"44"},{"code":"4408","name":"湛江市","provinceCode":"44"},{"code":"4409","name":"茂名市","provinceCode":"44"},{"code":"4412","name":"肇庆市","provinceCode":"44"},{"code":"4413","name":"惠州市","provinceCode":"44"},{"code":"4414","name":"梅州市","provinceCode":"44"},{"code":"4415","name":"汕尾市","provinceCode":"44"},{"code":"4416","name":"河源市","provinceCode":"44"},{"code":"4417","name":"阳江市","provinceCode":"44"},{"code":"4418","name":"清远市","provinceCode":"44"},{"code":"4419","name":"东莞市","provinceCode":"44"},{"code":"4420","name":"中山市","provinceCode":"44"},{"code":"4451","name":"潮州市","provinceCode":"44"},{"code":"4452","name":"揭阳市","provinceCode":"44"},{"code":"4453","name":"云浮市","provinceCode":"44"},{"code":"4501","name":"南宁市","provinceCode":"45"},{"code":"4502","name":"柳州市","provinceCode":"45"},{"code":"4503","name":"桂林市","provinceCode":"45"},{"code":"4504","name":"梧州市","provinceCode":"45"},{"code":"4505","name":"北海市","provinceCode":"45"},{"code":"4506","name":"防城港市","provinceCode":"45"},{"code":"4507","name":"钦州市","provinceCode":"45"},{"code":"4508","name":"贵港市","provinceCode":"45"},{"code":"4509","name":"玉林市","provinceCode":"45"},{"code":"4510","name":"百色市","provinceCode":"45"},{"code":"4511","name":"贺州市","provinceCode":"45"},{"code":"4512","name":"河池市","provinceCode":"45"},{"code":"4513","name":"来宾市","provinceCode":"45"},{"code":"4514","name":"崇左市","provinceCode":"45"},{"code":"4601","name":"海口市","provinceCode":"46"},{"code":"4602","name":"三亚市","provinceCode":"46"},{"code":"4603","name":"三沙市","provinceCode":"46"},{"code":"4604","name":"儋州市","provinceCode":"46"},{"code":"5002","name":"县","provinceCode":"50"},{"code":"5101","name":"成都市","provinceCode":"51"},{"code":"5103","name":"自贡市","provinceCode":"51"},{"code":"5104","name":"攀枝花市","provinceCode":"51"},{"code":"5105","name":"泸州市","provinceCode":"51"},{"code":"5106","name":"德阳市","provinceCode":"51"},{"code":"5107","name":"绵阳市","provinceCode":"51"},{"code":"5108","name":"广元市","provinceCode":"51"},{"code":"5109","name":"遂宁市","provinceCode":"51"},{"code":"5110","name":"内江市","provinceCode":"51"},{"code":"5111","name":"乐山市","provinceCode":"51"},{"code":"5113","name":"南充市","provinceCode":"51"},{"code":"5114","name":"眉山市","provinceCode":"51"},{"code":"5115","name":"宜宾市","provinceCode":"51"},{"code":"5116","name":"广安市","provinceCode":"51"},{"code":"5117","name":"达州市","provinceCode":"51"},{"code":"5118","name":"雅安市","provinceCode":"51"},{"code":"5119","name":"巴中市","provinceCode":"51"},{"code":"5120","name":"资阳市","provinceCode":"51"},{"code":"5132","name":"阿坝藏族羌族自治州","provinceCode":"51"},{"code":"5133","name":"甘孜藏族自治州","provinceCode":"51"},{"code":"5134","name":"凉山彝族自治州","provinceCode":"51"},{"code":"5201","name":"贵阳市","provinceCode":"52"},{"code":"5202","name":"六盘水市","provinceCode":"52"},{"code":"5203","name":"遵义市","provinceCode":"52"},{"code":"5204","name":"安顺市","provinceCode":"52"},{"code":"5205","name":"毕节市","provinceCode":"52"},{"code":"5206","name":"铜仁市","provinceCode":"52"},{"code":"5223","name":"黔西南布依族苗族自治州","provinceCode":"52"},{"code":"5226","name":"黔东南苗族侗族自治州","provinceCode":"52"},{"code":"5227","name":"黔南布依族苗族自治州","provinceCode":"52"},{"code":"5301","name":"昆明市","provinceCode":"53"},{"code":"5303","name":"曲靖市","provinceCode":"53"},{"code":"5304","name":"玉溪市","provinceCode":"53"},{"code":"5305","name":"保山市","provinceCode":"53"},{"code":"5306","name":"昭通市","provinceCode":"53"},{"code":"5307","name":"丽江市","provinceCode":"53"},{"code":"5308","name":"普洱市","provinceCode":"53"},{"code":"5309","name":"临沧市","provinceCode":"53"},{"code":"5323","name":"楚雄彝族自治州","provinceCode":"53"},{"code":"5325","name":"红河哈尼族彝族自治州","provinceCode":"53"},{"code":"5326","name":"文山壮族苗族自治州","provinceCode":"53"},{"code":"5328","name":"西双版纳傣族自治州","provinceCode":"53"},{"code":"5329","name":"大理白族自治州","provinceCode":"53"},{"code":"5331","name":"德宏傣族景颇族自治州","provinceCode":"53"},{"code":"5333","name":"怒江傈僳族自治州","provinceCode":"53"},{"code":"5334","name":"迪庆藏族自治州","provinceCode":"53"},{"code":"5401","name":"拉萨市","provinceCode":"54"},{"code":"5402","name":"日喀则市","provinceCode":"54"},{"code":"5403","name":"昌都市","provinceCode":"54"},{"code":"5404","name":"林芝市","provinceCode":"54"},{"code":"5405","name":"山南市","provinceCode":"54"},{"code":"5424","name":"那曲地区","provinceCode":"54"},{"code":"5425","name":"阿里地区","provinceCode":"54"},{"code":"6101","name":"西安市","provinceCode":"61"},{"code":"6102","name":"铜川市","provinceCode":"61"},{"code":"6103","name":"宝鸡市","provinceCode":"61"},{"code":"6104","name":"咸阳市","provinceCode":"61"},{"code":"6105","name":"渭南市","provinceCode":"61"},{"code":"6106","name":"延安市","provinceCode":"61"},{"code":"6107","name":"汉中市","provinceCode":"61"},{"code":"6108","name":"榆林市","provinceCode":"61"},{"code":"6109","name":"安康市","provinceCode":"61"},{"code":"6110","name":"商洛市","provinceCode":"61"},{"code":"6201","name":"兰州市","provinceCode":"62"},{"code":"6202","name":"嘉峪关市","provinceCode":"62"},{"code":"6203","name":"金昌市","provinceCode":"62"},{"code":"6204","name":"白银市","provinceCode":"62"},{"code":"6205","name":"天水市","provinceCode":"62"},{"code":"6206","name":"武威市","provinceCode":"62"},{"code":"6207","name":"张掖市","provinceCode":"62"},{"code":"6208","name":"平凉市","provinceCode":"62"},{"code":"6209","name":"酒泉市","provinceCode":"62"},{"code":"6210","name":"庆阳市","provinceCode":"62"},{"code":"6211","name":"定西市","provinceCode":"62"},{"code":"6212","name":"陇南市","provinceCode":"62"},{"code":"6229","name":"临夏回族自治州","provinceCode":"62"},{"code":"6230","name":"甘南藏族自治州","provinceCode":"62"},{"code":"6301","name":"西宁市","provinceCode":"63"},{"code":"6302","name":"海东市","provinceCode":"63"},{"code":"6322","name":"海北藏族自治州","provinceCode":"63"},{"code":"6323","name":"黄南藏族自治州","provinceCode":"63"},{"code":"6325","name":"海南藏族自治州","provinceCode":"63"},{"code":"6326","name":"果洛藏族自治州","provinceCode":"63"},{"code":"6327","name":"玉树藏族自治州","provinceCode":"63"},{"code":"6328","name":"海西蒙古族藏族自治州","provinceCode":"63"},{"code":"6401","name":"银川市","provinceCode":"64"},{"code":"6402","name":"石嘴山市","provinceCode":"64"},{"code":"6403","name":"吴忠市","provinceCode":"64"},{"code":"6404","name":"固原市","provinceCode":"64"},{"code":"6405","name":"中卫市","provinceCode":"64"},{"code":"6501","name":"乌鲁木齐市","provinceCode":"65"},{"code":"6502","name":"克拉玛依市","provinceCode":"65"},{"code":"6504","name":"吐鲁番市","provinceCode":"65"},{"code":"6505","name":"哈密市","provinceCode":"65"},{"code":"6523","name":"昌吉回族自治州","provinceCode":"65"},{"code":"6527","name":"博尔塔拉蒙古自治州","provinceCode":"65"},{"code":"6528","name":"巴音郭楞蒙古自治州","provinceCode":"65"},{"code":"6529","name":"阿克苏地区","provinceCode":"65"},{"code":"6530","name":"克孜勒苏柯尔克孜自治州","provinceCode":"65"},{"code":"6531","name":"喀什地区","provinceCode":"65"},{"code":"6532","name":"和田地区","provinceCode":"65"},{"code":"6540","name":"伊犁哈萨克自治州","provinceCode":"65"},{"code":"6542","name":"塔城地区","provinceCode":"65"},{"code":"6543","name":"阿勒泰地区","provinceCode":"65"}] 2 | -------------------------------------------------------------------------------- /doc/Flutter版携程AppDemo接口文档.md: -------------------------------------------------------------------------------- 1 | - [接口地址](#接口地址) 2 | - [接口字段](#接口字段) 3 | - [HomeModel](#HomeModel) 4 | - [CommonModel](#CommonModel) 5 | - [GridNavModel](#GridNavModel) 6 | - [SalesBoxModel](#SalesBoxModel) 7 | - [ConfigModel](#ConfigModel) 8 | 9 | 10 | ## 接口地址 11 | 12 | [http://www.devio.org/io/flutter_app/json/home_page.json](http://www.devio.org/io/flutter_app/json/home_page.json) 13 | 14 | ## 接口字段 15 | 16 | [JSON在线解析](https://www.json.cn/) 17 | 18 | ## HomeModel 19 | 20 | ![home_page](http://www.devio.org/io/flutter_app/img/blog/home_page.png) 21 | 22 | 字段 | 类型 | 备注 23 | | -------- | -------- | -------- | 24 | config | Object | NonNull 25 | bannerList | Array | NonNull 26 | localNavList | Array | NonNull 27 | gridNav | Object | NonNull 28 | subNavList | Array | NonNull 29 | salesBox | Object | NonNull 30 | 31 | ## CommonModel 32 | 33 | ![common-model](http://www.devio.org/io/flutter_app/img/blog/common-model.png) 34 | 35 | 字段 | 类型 | 备注 36 | | -------- | -------- | -------- | 37 | icon | String | Nullable 38 | title | String | Nullable 39 | url | String | NonNull 40 | statusBarColor | String | Nullable 41 | hideAppBar | bool | Nullable 42 | 43 | ## GridNavModel 44 | 45 | ![grid-nav](http://www.devio.org/io/flutter_app/img/blog/grid-nav.png) 46 | 47 | 字段 | 类型 | 备注 48 | | -------- | -------- | -------- | 49 | hotel | Object | NonNull 50 | flight | Object | NonNull 51 | travel | Object | NonNull 52 | 53 | ## SalesBoxModel 54 | 55 | ![sales-box](http://www.devio.org/io/flutter_app/img/blog/sales-box.png) 56 | 57 | 字段 | 类型 | 备注 58 | | -------- | -------- | -------- | 59 | icon | String | NonNull 60 | moreUrl | String | NonNull 61 | bigCard1 | Object | NonNull 62 | bigCard2 | Object | NonNull 63 | smallCard1 | Object | NonNull 64 | smallCard2 | Object | NonNull 65 | smallCard3 | Object | NonNull 66 | smallCard4 | Object | NonNull 67 | 68 | ## ConfigModel 69 | 70 | ![config](http://www.devio.org/io/flutter_app/img/blog/config.png) 71 | 72 | 字段 | 类型 | 备注 73 | | -------- | -------- | -------- | 74 | searchUrl | String | NonNull 75 | 76 | 77 | ## 搜索页接口 78 | https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword=1 79 | 80 | ## 旅拍接口 81 | 旅拍类别 82 | [http://www.devio.org/io/flutter_app/json/travel_page.json](http://www.devio.org/io/flutter_app/json/travel_page.json) 83 | 旅拍页地址 84 | https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5 85 | ``` 86 | "params": { 87 | "districtId": -1, 88 | "groupChannelCode": "tourphoto_global1", 89 | "type": null, 90 | "lat": -180, 91 | "lon": -180, 92 | "locatedDistrictId": 2, 93 | "pagePara": { 94 | "pageIndex": 1, 95 | "pageSize": 10, 96 | "sortType": 9, 97 | "sortDirection": 0 98 | }, 99 | "imageCutType": 1, 100 | "head": { 101 | "cid": "09031014111431397988", 102 | "ctok": "", 103 | "cver": "1.0", 104 | "lang": "01", 105 | "sid": "8888", 106 | "syscode": "09", 107 | "auth": null, 108 | "extension": [ 109 | { 110 | "name": "protocal", 111 | "value": "https" 112 | } 113 | ] 114 | }, 115 | "contentType": "json" 116 | } 117 | ``` -------------------------------------------------------------------------------- /images/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/images/launch_image.png -------------------------------------------------------------------------------- /images/君伟说.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/images/君伟说.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /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/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 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_webview_plugin (0.0.1): 4 | - Flutter 5 | - FMDB (2.7.5): 6 | - FMDB/standard (= 2.7.5) 7 | - FMDB/standard (2.7.5) 8 | - package_info (0.0.1): 9 | - Flutter 10 | - path_provider (0.0.1): 11 | - Flutter 12 | - path_provider_macos (0.0.1): 13 | - Flutter 14 | - sqflite (0.0.1): 15 | - Flutter 16 | - FMDB (~> 2.7.2) 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `Flutter`) 20 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 21 | - package_info (from `.symlinks/plugins/package_info/ios`) 22 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 23 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 24 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 25 | 26 | SPEC REPOS: 27 | https://github.com/cocoapods/specs.git: 28 | - FMDB 29 | 30 | EXTERNAL SOURCES: 31 | Flutter: 32 | :path: Flutter 33 | flutter_webview_plugin: 34 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 35 | package_info: 36 | :path: ".symlinks/plugins/package_info/ios" 37 | path_provider: 38 | :path: ".symlinks/plugins/path_provider/ios" 39 | path_provider_macos: 40 | :path: ".symlinks/plugins/path_provider_macos/ios" 41 | sqflite: 42 | :path: ".symlinks/plugins/sqflite/ios" 43 | 44 | SPEC CHECKSUMS: 45 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 46 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 47 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 48 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 49 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 50 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 51 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 52 | 53 | PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 54 | 55 | COCOAPODS: 1.6.1 56 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/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/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayne214/flutter_wtrip/4e7017b1e96a35a6c8e42ea84632d2176d0d9b13/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_wtrip 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | NSAllowsArbitraryLoadsInWebContent 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/dao/home_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wtrip/model/home_model.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:async'; // 异步编程 Future 4 | import 'dart:convert'; // http.Response 转换 5 | import 'package:dio/dio.dart'; 6 | const HOME_URL = 'http://www.devio.org/io/flutter_app/json/home_page.json'; 7 | class HomeDao{ 8 | static Future fetch() async{ 9 | // final response = await http.get(HOME_URL); 10 | // if(response.statusCode == 200) { 11 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码 12 | // var result = json.decode(utf8decoder.convert(response.bodyBytes)); 13 | // return HomeModel.fromJson(result); // 转换成HomeModel 14 | // }else { 15 | // throw Exception('Failed to load home_page json'); 16 | // } 17 | 18 | Response response = await Dio().get(HOME_URL); 19 | if(response.statusCode == 200) { 20 | return HomeModel.fromJson(response.data); // 转换成HomeModel 21 | } else { 22 | throw Exception('Failed to load home_page json'); 23 | } 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /lib/dao/search_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wtrip/model/search_model.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:async'; // 异步编程 Future 4 | import 'dart:convert'; // http.Response 转换 5 | import 'package:dio/dio.dart'; 6 | 7 | const SEARCH_URL = 8 | 'https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword='; 9 | 10 | class SearchDao { 11 | static Future fetch(String text) async { 12 | Response response = await Dio().get(SEARCH_URL+text); 13 | print(response.toString()); 14 | if (response.statusCode == 200) { 15 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码 16 | // var result = json.decode(utf8decoder.convert(response.bodyBytes)); 17 | // 只有当当前输入的内容和服务器返回的内容一致时才渲染 18 | SearchModel model = SearchModel.fromJson(response.data); 19 | model.keyword = text; 20 | return model; // 转换成HomeModel 21 | } else { 22 | throw Exception('Failed to load search_page json'); 23 | } 24 | 25 | 26 | 27 | 28 | // final response = await http.get(SEARCH_URL + text); 29 | // if (response.statusCode == 200) { 30 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码 31 | // var result = json.decode(utf8decoder.convert(response.bodyBytes)); 32 | // // 只有当当前输入的内容和服务器返回的内容一致时才渲染 33 | // SearchModel model = SearchModel.fromJson(result); 34 | // model.keyword = text; 35 | // return model; // 转换成HomeModel 36 | // } else { 37 | // throw Exception('Failed to load search_page json'); 38 | // } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/dao/travel_page_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wtrip/model/travel_page_model.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:async'; // 异步编程 Future 4 | import 'dart:convert'; // http.Response 转换 5 | import 'package:dio/dio.dart'; 6 | 7 | const TRAVEL_PAGE_DEFAULT_URL = 8 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5'; 9 | // 固定参数,如果要被修改,不要定义成const 10 | var Params = { 11 | "districtId": -1, 12 | "groupChannelCode": "tourphoto_global1", 13 | "type": null, 14 | "lat": -180, 15 | "lon": -180, 16 | "locatedDistrictId": 2, 17 | "pagePara": { 18 | "pageIndex": 1, 19 | "pageSize": 10, 20 | "sortType": 9, 21 | "sortDirection": 0 22 | }, 23 | "imageCutType": 1, 24 | "head": {}, 25 | "contentType": "json" 26 | }; 27 | 28 | class TravelPageDao { 29 | static Future fetch( 30 | String url, String groupChannelCode, int pageIndex, int pageSize) async { 31 | // 可变参数 32 | Map paramsMap = Params['pagePara']; 33 | paramsMap['pageIndex'] = pageIndex; 34 | paramsMap['pageSize'] = pageSize; 35 | Params['groupChannelCode'] = groupChannelCode; 36 | // var response = await http.post(TRAVEL_PAGE_DEFAULT_URL, body: jsonEncode(Params)); 37 | // if (response.statusCode == 200) { 38 | // Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码 39 | // var result = json.decode(utf8decoder.convert(response.bodyBytes)); 40 | // return TravelPageModel.fromJson(result); // 转换成HomeModel 41 | // } else { 42 | // throw Exception('Failed to load travel page json'); 43 | // } 44 | Response response = await Dio().post(TRAVEL_PAGE_DEFAULT_URL, data: Params); 45 | if(response.statusCode == 200) { 46 | return TravelPageModel.fromJson(response.data); 47 | }else{ 48 | throw Exception('Failed to load travel page json'); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/dao/travel_tab_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wtrip/model/travel_tab_model.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:async'; // 异步编程 Future 4 | import 'dart:convert'; // http.Response 转换 5 | import 'package:dio/dio.dart'; 6 | 7 | const TRAVEL_TAB_URL = 8 | 'http://www.devio.org/io/flutter_app/json/travel_page.json'; 9 | 10 | class TravelTabDao { 11 | static Future fetch() async { 12 | final response = await http.get(TRAVEL_TAB_URL); 13 | if (response.statusCode == 200) { 14 | Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码 15 | var result = json.decode(utf8decoder.convert(response.bodyBytes)); 16 | return TravelTabModel.fromJson(result); // 转换成HomeModel 17 | } else { 18 | throw Exception('Failed to load home_page json'); 19 | } 20 | 21 | // Response response = await Dio().get(TRAVEL_TAB_URL); 22 | // if(response.statusCode == 200) { 23 | // return TravelTabModel.fromJson(response.data); 24 | // } else { 25 | // throw Exception('Failed to load TravelTabDao json'); 26 | // } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'navigator/tab_navigator.dart'; 4 | 5 | import 'package:provider/provider.dart'; 6 | 7 | void main() => runApp(MyApp()); 8 | 9 | class MyApp extends StatelessWidget { 10 | // This widget is the root of your application. 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: 'Flutter Demo', 15 | theme: ThemeData( 16 | // This is the theme of your application. 17 | // 18 | // Try running your application with "flutter run". You'll see the 19 | // application has a blue toolbar. Then, without quitting the app, try 20 | // changing the primarySwatch below to Colors.green and then invoke 21 | // "hot reload" (press "r" in the console where you ran "flutter run", 22 | // or simply save your changes to "hot reload" in a Flutter IDE). 23 | // Notice that the counter didn't reset back to zero; the application 24 | // is not restarted. 25 | primarySwatch: Colors.blue, 26 | ), 27 | home: TabNavigator(), 28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/model/common_model.dart: -------------------------------------------------------------------------------- 1 | class CommonModel { 2 | final String icon; 3 | final String title; 4 | final String url; 5 | final String statusBarColor; 6 | final bool hideAppBar; 7 | 8 | CommonModel( 9 | {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar}); 10 | 11 | factory CommonModel.fromJson(Map json) { 12 | return CommonModel( 13 | icon: json['icon'], 14 | title: json['title'], 15 | url: json['url'], 16 | statusBarColor: json['statusBarColor'], 17 | hideAppBar: json['hideAppBar'], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/config_model.dart: -------------------------------------------------------------------------------- 1 | class ConfigModel{ 2 | final String searchUrl; 3 | 4 | ConfigModel({this.searchUrl}); 5 | 6 | factory ConfigModel.fromJson(Map json) { 7 | return ConfigModel( 8 | searchUrl: json['searchUrl'] 9 | ); 10 | } 11 | 12 | Map toJson() { 13 | return {searchUrl: searchUrl}; 14 | } 15 | } -------------------------------------------------------------------------------- /lib/model/gridNav_model.dart: -------------------------------------------------------------------------------- 1 | import 'common_model.dart'; 2 | 3 | class GridNavModel { 4 | final GridNavItem hotel; 5 | final GridNavItem flight; 6 | final GridNavItem travel; 7 | 8 | GridNavModel({this.hotel, this.flight, this.travel}); 9 | 10 | factory GridNavModel.fromJson(Map json) { 11 | return json !=null? GridNavModel( 12 | hotel: GridNavItem.fromJson(json['hotel']), 13 | flight: GridNavItem.fromJson(json['flight']), 14 | travel: GridNavItem.fromJson(json['travel']), 15 | ) : null; 16 | } 17 | } 18 | 19 | class GridNavItem { 20 | final String startColor; 21 | final String endColor; 22 | final CommonModel mainItem; 23 | final CommonModel item1; 24 | final CommonModel item2; 25 | final CommonModel item3; 26 | final CommonModel item4; 27 | 28 | GridNavItem( 29 | {this.startColor, 30 | this.endColor, 31 | this.mainItem, 32 | this.item1, 33 | this.item2, 34 | this.item3, 35 | this.item4}); 36 | 37 | factory GridNavItem.fromJson(Map json) { 38 | return GridNavItem( 39 | startColor: json['startColor'], 40 | endColor: json['endColor'], 41 | mainItem: CommonModel.fromJson(json['mainItem']), 42 | item1: CommonModel.fromJson(json['item1']), 43 | item2: CommonModel.fromJson(json['item2']), 44 | item3: CommonModel.fromJson(json['item3']), 45 | item4: CommonModel.fromJson(json['item4']), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/model/home_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wtrip/model/sales_box_model.dart'; 2 | 3 | import 'common_model.dart'; 4 | import 'config_model.dart'; 5 | import 'gridNav_model.dart'; 6 | 7 | class HomeModel { 8 | final ConfigModel config; 9 | final List bannerList; 10 | final List localNavList; 11 | final List subNavList; 12 | final GridNavModel gridNav; 13 | final SalesBoxModel salesBox; 14 | 15 | HomeModel( 16 | {this.config, 17 | this.bannerList, 18 | this.localNavList, 19 | this.subNavList, 20 | this.gridNav, 21 | this.salesBox}); 22 | 23 | 24 | factory HomeModel.fromJson(Map json) { 25 | var localNavListJson = json['localNavList'] as List; 26 | List localNavList = localNavListJson.map((i)=> CommonModel.fromJson(i)).toList(); 27 | 28 | var bannerListJson = json['bannerList'] as List; 29 | List bannerList = bannerListJson.map((i)=> CommonModel.fromJson(i)).toList(); 30 | 31 | var subNavListJson = json['subNavList'] as List; 32 | List subNavList = subNavListJson.map((i)=> CommonModel.fromJson(i)).toList(); 33 | 34 | 35 | return HomeModel( 36 | localNavList: localNavList, 37 | bannerList: bannerList, 38 | subNavList: subNavList, 39 | config: ConfigModel.fromJson(json['config']), 40 | gridNav: GridNavModel.fromJson(json['gridNav']), 41 | salesBox: SalesBoxModel.fromJson(json['salesBox']), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/model/sales_box_model.dart: -------------------------------------------------------------------------------- 1 | import 'common_model.dart'; 2 | 3 | class SalesBoxModel { 4 | final String icon; 5 | final String moreUrl; 6 | final CommonModel bigCard1; 7 | final CommonModel bigCard2; 8 | final CommonModel smallCard1; 9 | final CommonModel smallCard2; 10 | final CommonModel smallCard3; 11 | final CommonModel smallCard4; 12 | 13 | SalesBoxModel({ 14 | this.icon, 15 | this.moreUrl, 16 | this.bigCard1, 17 | this.bigCard2, 18 | this.smallCard1, 19 | this.smallCard2, 20 | this.smallCard3, 21 | this.smallCard4, 22 | }); 23 | 24 | factory SalesBoxModel.fromJson(Mapjson) { 25 | return json !=null? 26 | SalesBoxModel( 27 | icon: json['icon'], 28 | moreUrl: json['moreUrl'], 29 | bigCard1: CommonModel.fromJson(json['bigCard1']), 30 | bigCard2: CommonModel.fromJson(json['bigCard2']), 31 | smallCard1: CommonModel.fromJson(json['smallCard1']), 32 | smallCard2: CommonModel.fromJson(json['smallCard2']), 33 | smallCard3: CommonModel.fromJson(json['smallCard3']), 34 | smallCard4: CommonModel.fromJson(json['smallCard4']), 35 | ): null; 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /lib/model/search_model.dart: -------------------------------------------------------------------------------- 1 | 2 | ///搜索模型 3 | class SearchModel { 4 | final List data; 5 | String keyword; 6 | 7 | SearchModel({this.data}); 8 | 9 | factory SearchModel.fromJson(Map json) { 10 | var dataJson = json['data'] as List; 11 | List data = dataJson.map((i) => SearchItem.fromJson(i)).toList(); 12 | 13 | return SearchModel(data: data); 14 | } 15 | } 16 | 17 | class SearchItem { 18 | final String word; 19 | final String type; 20 | final String price; 21 | final String star; 22 | final String zonename; 23 | final String districtname; 24 | final String url; 25 | 26 | SearchItem( 27 | {this.word, 28 | this.type, 29 | this.price, 30 | this.star, 31 | this.zonename, 32 | this.districtname, 33 | this.url}); 34 | 35 | factory SearchItem.fromJson(Map json) { 36 | return SearchItem( 37 | word: json['word'], 38 | type: json['type'], 39 | price: json['price'], 40 | zonename: json['zonename'], 41 | districtname: json['districtname'], 42 | url: json['url'], 43 | star: json['star'], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/model/travel_page_model.dart: -------------------------------------------------------------------------------- 1 | ///旅拍页modal 2 | class TravelPageModel { 3 | int totalCount; 4 | List resultList; 5 | 6 | TravelPageModel({this.totalCount, this.resultList}); 7 | 8 | TravelPageModel.fromJson(Map json) { 9 | totalCount = json['totalCount']; 10 | if (json['resultList'] != null) { 11 | resultList = new List(); 12 | json['resultList'].forEach((v) { 13 | resultList.add(new TravelPageItem.fromJson(v)); 14 | }); 15 | } 16 | } 17 | 18 | Map toJson() { 19 | final Map data = new Map(); 20 | data['totalCount'] = this.totalCount; 21 | if (this.resultList != null) { 22 | data['resultList'] = this.resultList.map((v) => v.toJson()).toList(); 23 | } 24 | return data; 25 | } 26 | } 27 | 28 | class TravelPageItem { 29 | int type; 30 | Article article; 31 | 32 | TravelPageItem({this.type, this.article}); 33 | 34 | TravelPageItem.fromJson(Map json) { 35 | type = json['type']; 36 | article = 37 | json['article'] != null ? new Article.fromJson(json['article']) : null; 38 | } 39 | 40 | Map toJson() { 41 | final Map data = new Map(); 42 | data['type'] = this.type; 43 | if (this.article != null) { 44 | data['article'] = this.article.toJson(); 45 | } 46 | return data; 47 | } 48 | } 49 | 50 | class Article { 51 | int articleId; 52 | int productType; 53 | int sourceType; 54 | String articleTitle; 55 | Author author; 56 | List images; 57 | bool hasVideo; 58 | int readCount; 59 | int likeCount; 60 | int commentCount; 61 | int shareCount; 62 | List urls; 63 | List topics; 64 | List pois; 65 | String publishTime; 66 | String publishTimeDisplay; 67 | String shootTime; 68 | String shootTimeDisplay; 69 | int level; 70 | String distanceText; 71 | bool isLike; 72 | int imageCounts; 73 | bool isCollected; 74 | int collectCount; 75 | int articleStatus; 76 | String poiName; 77 | ShareInfo shareInfo; 78 | String currentDate; 79 | int sourceId; 80 | 81 | Article( 82 | {this.articleId, 83 | this.productType, 84 | this.sourceType, 85 | this.articleTitle, 86 | this.author, 87 | this.images, 88 | this.hasVideo, 89 | this.readCount, 90 | this.likeCount, 91 | this.commentCount, 92 | this.shareCount, 93 | this.urls, 94 | this.topics, 95 | this.pois, 96 | this.publishTime, 97 | this.publishTimeDisplay, 98 | this.shootTime, 99 | this.shootTimeDisplay, 100 | this.level, 101 | this.distanceText, 102 | this.isLike, 103 | this.imageCounts, 104 | this.isCollected, 105 | this.collectCount, 106 | this.articleStatus, 107 | this.poiName, 108 | this.shareInfo, 109 | this.currentDate, 110 | this.sourceId}); 111 | 112 | Article.fromJson(Map json) { 113 | articleId = json['articleId']; 114 | productType = json['productType']; 115 | sourceType = json['sourceType']; 116 | articleTitle = json['articleTitle']; 117 | author = 118 | json['author'] != null ? new Author.fromJson(json['author']) : null; 119 | if (json['images'] != null) { 120 | images = new List(); 121 | json['images'].forEach((v) { 122 | images.add(new Images.fromJson(v)); 123 | }); 124 | } 125 | hasVideo = json['hasVideo']; 126 | readCount = json['readCount']; 127 | likeCount = json['likeCount']; 128 | commentCount = json['commentCount']; 129 | shareCount = json['shareCount']; 130 | if (json['urls'] != null) { 131 | urls = new List(); 132 | json['urls'].forEach((v) { 133 | urls.add(new Urls.fromJson(v)); 134 | }); 135 | } 136 | if (json['topics'] != null) { 137 | topics = new List(); 138 | json['topics'].forEach((v) { 139 | topics.add(new Topics.fromJson(v)); 140 | }); 141 | } 142 | if (json['pois'] != null) { 143 | pois = new List(); 144 | json['pois'].forEach((v) { 145 | pois.add(new Pois.fromJson(v)); 146 | }); 147 | } 148 | publishTime = json['publishTime']; 149 | publishTimeDisplay = json['publishTimeDisplay']; 150 | shootTime = json['shootTime']; 151 | shootTimeDisplay = json['shootTimeDisplay']; 152 | level = json['level']; 153 | distanceText = json['distanceText']; 154 | isLike = json['isLike']; 155 | imageCounts = json['imageCounts']; 156 | isCollected = json['isCollected']; 157 | collectCount = json['collectCount']; 158 | articleStatus = json['articleStatus']; 159 | poiName = json['poiName']; 160 | shareInfo = json['shareInfo'] != null 161 | ? new ShareInfo.fromJson(json['shareInfo']) 162 | : null; 163 | currentDate = json['currentDate']; 164 | sourceId = json['sourceId']; 165 | } 166 | 167 | Map toJson() { 168 | final Map data = new Map(); 169 | data['articleId'] = this.articleId; 170 | data['productType'] = this.productType; 171 | data['sourceType'] = this.sourceType; 172 | data['articleTitle'] = this.articleTitle; 173 | if (this.author != null) { 174 | data['author'] = this.author.toJson(); 175 | } 176 | if (this.images != null) { 177 | data['images'] = this.images.map((v) => v.toJson()).toList(); 178 | } 179 | data['hasVideo'] = this.hasVideo; 180 | data['readCount'] = this.readCount; 181 | data['likeCount'] = this.likeCount; 182 | data['commentCount'] = this.commentCount; 183 | data['shareCount'] = this.shareCount; 184 | if (this.urls != null) { 185 | data['urls'] = this.urls.map((v) => v.toJson()).toList(); 186 | } 187 | if (this.topics != null) { 188 | data['topics'] = this.topics.map((v) => v.toJson()).toList(); 189 | } 190 | if (this.pois != null) { 191 | data['pois'] = this.pois.map((v) => v.toJson()).toList(); 192 | } 193 | data['publishTime'] = this.publishTime; 194 | data['publishTimeDisplay'] = this.publishTimeDisplay; 195 | data['shootTime'] = this.shootTime; 196 | data['shootTimeDisplay'] = this.shootTimeDisplay; 197 | data['level'] = this.level; 198 | data['distanceText'] = this.distanceText; 199 | data['isLike'] = this.isLike; 200 | data['imageCounts'] = this.imageCounts; 201 | data['isCollected'] = this.isCollected; 202 | data['collectCount'] = this.collectCount; 203 | data['articleStatus'] = this.articleStatus; 204 | data['poiName'] = this.poiName; 205 | if (this.shareInfo != null) { 206 | data['shareInfo'] = this.shareInfo.toJson(); 207 | } 208 | data['currentDate'] = this.currentDate; 209 | data['sourceId'] = this.sourceId; 210 | return data; 211 | } 212 | } 213 | 214 | class Author { 215 | int authorId; 216 | String nickName; 217 | String clientAuth; 218 | String jumpUrl; 219 | CoverImage coverImage; 220 | int identityType; 221 | String identityTypeName; 222 | String tag; 223 | int followCount; 224 | 225 | Author( 226 | {this.authorId, 227 | this.nickName, 228 | this.clientAuth, 229 | this.jumpUrl, 230 | this.coverImage, 231 | this.identityType, 232 | this.identityTypeName, 233 | this.tag, 234 | this.followCount}); 235 | 236 | Author.fromJson(Map json) { 237 | authorId = json['authorId']; 238 | nickName = json['nickName']; 239 | clientAuth = json['clientAuth']; 240 | jumpUrl = json['jumpUrl']; 241 | coverImage = json['coverImage'] != null 242 | ? new CoverImage.fromJson(json['coverImage']) 243 | : null; 244 | identityType = json['identityType']; 245 | identityTypeName = json['identityTypeName']; 246 | tag = json['tag']; 247 | followCount = json['followCount']; 248 | } 249 | 250 | Map toJson() { 251 | final Map data = new Map(); 252 | data['authorId'] = this.authorId; 253 | data['nickName'] = this.nickName; 254 | data['clientAuth'] = this.clientAuth; 255 | data['jumpUrl'] = this.jumpUrl; 256 | if (this.coverImage != null) { 257 | data['coverImage'] = this.coverImage.toJson(); 258 | } 259 | data['identityType'] = this.identityType; 260 | data['identityTypeName'] = this.identityTypeName; 261 | data['tag'] = this.tag; 262 | data['followCount'] = this.followCount; 263 | return data; 264 | } 265 | } 266 | 267 | class CoverImage { 268 | String dynamicUrl; 269 | String originalUrl; 270 | 271 | CoverImage({this.dynamicUrl, this.originalUrl}); 272 | 273 | CoverImage.fromJson(Map json) { 274 | dynamicUrl = json['dynamicUrl']; 275 | originalUrl = json['originalUrl']; 276 | } 277 | 278 | Map toJson() { 279 | final Map data = new Map(); 280 | data['dynamicUrl'] = this.dynamicUrl; 281 | data['originalUrl'] = this.originalUrl; 282 | return data; 283 | } 284 | } 285 | 286 | class Images { 287 | int imageId; 288 | String dynamicUrl; 289 | String originalUrl; 290 | double width; 291 | double height; 292 | int mediaType; 293 | double lat; 294 | double lon; 295 | bool isWaterMarked; 296 | 297 | Images( 298 | {this.imageId, 299 | this.dynamicUrl, 300 | this.originalUrl, 301 | this.width, 302 | this.height, 303 | this.mediaType, 304 | this.lat, 305 | this.lon, 306 | this.isWaterMarked}); 307 | 308 | Images.fromJson(Map json) { 309 | imageId = json['imageId']; 310 | dynamicUrl = json['dynamicUrl']; 311 | originalUrl = json['originalUrl']; 312 | width = json['width']; 313 | height = json['height']; 314 | mediaType = json['mediaType']; 315 | lat = json['lat']; 316 | lon = json['lon']; 317 | isWaterMarked = json['isWaterMarked']; 318 | } 319 | 320 | Map toJson() { 321 | final Map data = new Map(); 322 | data['imageId'] = this.imageId; 323 | data['dynamicUrl'] = this.dynamicUrl; 324 | data['originalUrl'] = this.originalUrl; 325 | data['width'] = this.width; 326 | data['height'] = this.height; 327 | data['mediaType'] = this.mediaType; 328 | data['lat'] = this.lat; 329 | data['lon'] = this.lon; 330 | data['isWaterMarked'] = this.isWaterMarked; 331 | return data; 332 | } 333 | } 334 | 335 | class Urls { 336 | String version; 337 | String appUrl; 338 | String h5Url; 339 | String wxUrl; 340 | 341 | Urls({this.version, this.appUrl, this.h5Url, this.wxUrl}); 342 | 343 | Urls.fromJson(Map json) { 344 | version = json['version']; 345 | appUrl = json['appUrl']; 346 | h5Url = json['h5Url']; 347 | wxUrl = json['wxUrl']; 348 | } 349 | 350 | Map toJson() { 351 | final Map data = new Map(); 352 | data['version'] = this.version; 353 | data['appUrl'] = this.appUrl; 354 | data['h5Url'] = this.h5Url; 355 | data['wxUrl'] = this.wxUrl; 356 | return data; 357 | } 358 | } 359 | 360 | class Topics { 361 | int topicId; 362 | String topicName; 363 | int level; 364 | 365 | Topics({this.topicId, this.topicName, this.level}); 366 | 367 | Topics.fromJson(Map json) { 368 | topicId = json['topicId']; 369 | topicName = json['topicName']; 370 | level = json['level']; 371 | } 372 | 373 | Map toJson() { 374 | final Map data = new Map(); 375 | data['topicId'] = this.topicId; 376 | data['topicName'] = this.topicName; 377 | data['level'] = this.level; 378 | return data; 379 | } 380 | } 381 | 382 | class Pois { 383 | int poiType; 384 | int poiId; 385 | String poiName; 386 | int businessId; 387 | int districtId; 388 | PoiExt poiExt; 389 | int source; 390 | int isMain; 391 | bool isInChina; 392 | String countryName; 393 | 394 | Pois( 395 | {this.poiType, 396 | this.poiId, 397 | this.poiName, 398 | this.businessId, 399 | this.districtId, 400 | this.poiExt, 401 | this.source, 402 | this.isMain, 403 | this.isInChina, 404 | this.countryName}); 405 | 406 | Pois.fromJson(Map json) { 407 | poiType = json['poiType']; 408 | poiId = json['poiId']; 409 | poiName = json['poiName']; 410 | businessId = json['businessId']; 411 | districtId = json['districtId']; 412 | poiExt = 413 | json['poiExt'] != null ? new PoiExt.fromJson(json['poiExt']) : null; 414 | source = json['source']; 415 | isMain = json['isMain']; 416 | isInChina = json['isInChina']; 417 | countryName = json['countryName']; 418 | } 419 | 420 | Map toJson() { 421 | final Map data = new Map(); 422 | data['poiType'] = this.poiType; 423 | data['poiId'] = this.poiId; 424 | data['poiName'] = this.poiName; 425 | data['businessId'] = this.businessId; 426 | data['districtId'] = this.districtId; 427 | if (this.poiExt != null) { 428 | data['poiExt'] = this.poiExt.toJson(); 429 | } 430 | data['source'] = this.source; 431 | data['isMain'] = this.isMain; 432 | data['isInChina'] = this.isInChina; 433 | data['countryName'] = this.countryName; 434 | return data; 435 | } 436 | } 437 | 438 | class PoiExt { 439 | String h5Url; 440 | String appUrl; 441 | 442 | PoiExt({this.h5Url, this.appUrl}); 443 | 444 | PoiExt.fromJson(Map json) { 445 | h5Url = json['h5Url']; 446 | appUrl = json['appUrl']; 447 | } 448 | 449 | Map toJson() { 450 | final Map data = new Map(); 451 | data['h5Url'] = this.h5Url; 452 | data['appUrl'] = this.appUrl; 453 | return data; 454 | } 455 | } 456 | 457 | class ShareInfo { 458 | String imageUrl; 459 | String shareTitle; 460 | String shareContent; 461 | String platForm; 462 | String token; 463 | 464 | ShareInfo( 465 | {this.imageUrl, 466 | this.shareTitle, 467 | this.shareContent, 468 | this.platForm, 469 | this.token}); 470 | 471 | ShareInfo.fromJson(Map json) { 472 | imageUrl = json['imageUrl']; 473 | shareTitle = json['shareTitle']; 474 | shareContent = json['shareContent']; 475 | platForm = json['platForm']; 476 | token = json['token']; 477 | } 478 | 479 | Map toJson() { 480 | final Map data = new Map(); 481 | data['imageUrl'] = this.imageUrl; 482 | data['shareTitle'] = this.shareTitle; 483 | data['shareContent'] = this.shareContent; 484 | data['platForm'] = this.platForm; 485 | data['token'] = this.token; 486 | return data; 487 | } 488 | } -------------------------------------------------------------------------------- /lib/model/travel_tab_model.dart: -------------------------------------------------------------------------------- 1 | 2 | /// 旅拍模块顶部tab model 3 | class TravelTabModel { 4 | String url; 5 | List tabs; 6 | 7 | TravelTabModel({this.url, this.tabs}); 8 | 9 | TravelTabModel.fromJson(Map json) { 10 | url = json['url']; 11 | if (json['tabs'] != null) { 12 | tabs = new List(); 13 | json['tabs'].forEach((v) { 14 | tabs.add(new TravelTab.fromJson(v)); 15 | }); 16 | } 17 | } 18 | 19 | Map toJson() { 20 | final Map data = new Map(); 21 | data['url'] = this.url; 22 | if (this.tabs != null) { 23 | data['tabs'] = this.tabs.map((v) => v.toJson()).toList(); 24 | } 25 | return data; 26 | } 27 | } 28 | 29 | 30 | class TravelTab { 31 | String labelName; 32 | String groupChannelCode; 33 | 34 | TravelTab({this.labelName, this.groupChannelCode}); 35 | 36 | TravelTab.fromJson(Map json) { 37 | labelName = json['labelName']; 38 | groupChannelCode = json['groupChannelCode']; 39 | } 40 | 41 | Map toJson() { 42 | final Map data = new Map(); 43 | data['labelName'] = this.labelName; 44 | data['groupChannelCode'] = this.groupChannelCode; 45 | return data; 46 | } 47 | } -------------------------------------------------------------------------------- /lib/navigator/tab_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/pages/home_page.dart'; 3 | import 'package:flutter_wtrip/pages/my_page.dart'; 4 | import 'package:flutter_wtrip/pages/search_page.dart'; 5 | import 'package:flutter_wtrip/pages/travel_page.dart'; 6 | 7 | class TabNavigator extends StatefulWidget { 8 | @override 9 | _TabNavigatorState createState() => _TabNavigatorState(); 10 | } 11 | 12 | class _TabNavigatorState extends State { 13 | final _defaultColor = Colors.grey; 14 | final _activeColor = Colors.blue; 15 | int _currentIndex = 0; 16 | 17 | final PageController _controller = PageController( 18 | initialPage: 0, 19 | ); 20 | 21 | void _onItemTapped(int value) { 22 | _controller.jumpToPage(value); 23 | setState(() { 24 | _currentIndex = value; 25 | }); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | // TODO: implement build 31 | return Scaffold( 32 | body: PageView( 33 | physics: NeverScrollableScrollPhysics(), // 禁止导航页滑动 34 | controller: _controller, 35 | children: [ 36 | HomePage(), 37 | SearchPage(hideLeft: true,), 38 | TravelPage(), 39 | MyPage(), 40 | ], 41 | ), 42 | bottomNavigationBar: BottomNavigationBar( 43 | items: [ 44 | _tabBarItem(Icons.home, '首页', 0), 45 | _tabBarItem(Icons.search, '搜索', 1), 46 | _tabBarItem(Icons.camera_alt, '旅拍', 2), 47 | _tabBarItem(Icons.account_circle, "我的", 3), 48 | ], 49 | currentIndex: _currentIndex, 50 | // selectedItemColor: _activeColor, 51 | onTap: _onItemTapped, 52 | type: BottomNavigationBarType.fixed, // 固定显示 53 | ), 54 | ); 55 | } 56 | 57 | _tabBarItem(IconData icon, String title, int index) { 58 | return BottomNavigationBarItem( 59 | icon: Icon( 60 | icon, 61 | color: _defaultColor, 62 | ), 63 | activeIcon: Icon( 64 | icon, 65 | color: _activeColor, 66 | ), 67 | title: Text(title, 68 | style: TextStyle( 69 | color: 70 | _currentIndex != index ? _defaultColor : _activeColor))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/city_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_wtrip/widgets/loading_container.dart'; 5 | import 'package:azlistview/azlistview.dart'; 6 | import 'package:lpinyin/lpinyin.dart'; 7 | 8 | class CityPage extends StatefulWidget { 9 | @override 10 | _CityPageState createState() => _CityPageState(); 11 | } 12 | 13 | class _CityPageState extends State { 14 | 15 | List _cityList = List(); 16 | int _suspensionHeight = 40; 17 | int _itemHeight = 50; 18 | String _suspensionTag = ""; 19 | 20 | 21 | @override 22 | void initState() { 23 | // TODO: implement initState 24 | _loadCityData(); 25 | super.initState(); 26 | } 27 | 28 | void _loadCityData() async { 29 | try { 30 | var value = await rootBundle.loadString('assets/data/cities.json'); 31 | List list = json.decode(value); 32 | list.forEach((value){ 33 | _cityList.add(CityInfo(name: value['name'])); 34 | }); 35 | _handleList(_cityList); 36 | setState(() { 37 | _cityList = _cityList; 38 | }); 39 | 40 | // print(_cityList); 41 | } catch (e) { 42 | print(e); 43 | } 44 | } 45 | 46 | //排序 47 | void _handleList(List list) { 48 | if (list == null || list.isEmpty) return; 49 | for (int i = 0, length = list.length; i < length; i++) { 50 | String pinyin = PinyinHelper.getPinyinE(list[i].name); 51 | String tag = pinyin.substring(0, 1).toUpperCase(); 52 | list[i].namePinyin = pinyin; 53 | if (RegExp("[A-Z]").hasMatch(tag)) { 54 | list[i].tagIndex = tag; 55 | } else { 56 | list[i].tagIndex = "#"; 57 | } 58 | } 59 | //根据A-Z排序 60 | SuspensionUtil.sortListBySuspensionTag(_cityList); 61 | } 62 | 63 | //tag更改 64 | void _onSusTagChanged(String tag) { 65 | setState(() { 66 | _suspensionTag = tag; 67 | }); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | // TODO: implement build 73 | return MaterialApp( 74 | theme: ThemeData( 75 | primarySwatch: Colors.blue 76 | ), 77 | home: Scaffold( 78 | appBar: AppBar( 79 | title: Text('选择城市'), 80 | leading: GestureDetector( 81 | onTap: (){ 82 | Navigator.pop(context); 83 | }, 84 | child: Icon(Icons.arrow_back), 85 | ), 86 | ), 87 | body: Column( 88 | children: [ 89 | ListTile( 90 | title: Text('定位城市'), 91 | trailing: Row( 92 | mainAxisSize: MainAxisSize.min, 93 | children: [ 94 | Icon(Icons.place, size: 20.0,), 95 | Text('北京市') 96 | ], 97 | ), 98 | ), 99 | Divider(height: .0,), 100 | Expanded( 101 | flex: 1, 102 | child: AzListView( 103 | data: _cityList, 104 | itemBuilder: (context, model) => _buildListItem(model), 105 | suspensionWidget: _buildSusWidget(_suspensionTag), 106 | isUseRealIndex: true, 107 | itemHeight: _itemHeight, 108 | onSusTagChanged: _onSusTagChanged, 109 | header: AzListViewHeader( 110 | tag: '*', 111 | height: 140, 112 | builder: (context){ 113 | return _buildHeader(); 114 | }), 115 | indexHintBuilder: (context,hint){ 116 | return Container( 117 | alignment: Alignment.center, 118 | width: 80.0, 119 | height: 80.0, 120 | decoration: BoxDecoration( 121 | color: Colors.black54, shape: BoxShape.circle 122 | ), 123 | child: Text(hint, 124 | style: TextStyle(color: Colors.white, fontSize: 30.0)), 125 | ); 126 | }, 127 | ), 128 | ) 129 | ], 130 | ), 131 | ), 132 | ); 133 | } 134 | 135 | Widget _buildHeader(){ 136 | List hotCityList = List(); 137 | hotCityList.addAll([ 138 | CityInfo(name: "北京市"), 139 | CityInfo(name: "上海市"), 140 | CityInfo(name: "广州市"), 141 | CityInfo(name: "深圳市"), 142 | CityInfo(name: "杭州市"), 143 | CityInfo(name: "成都市"), 144 | ]); 145 | return Padding( 146 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 147 | child: Wrap( 148 | alignment: WrapAlignment.center, 149 | runAlignment: WrapAlignment.center, 150 | spacing: 10.0, 151 | children: hotCityList.map((e) { 152 | return OutlineButton( 153 | borderSide: BorderSide(color: Colors.grey[300], width: .5), 154 | child: Text(e.name), 155 | onPressed: () { 156 | print("OnItemClick: $e"); 157 | Navigator.pop(context, e.name); 158 | }, 159 | ); 160 | }).toList(), 161 | ), 162 | ); 163 | } 164 | 165 | Widget _buildSusWidget(String susTag){ 166 | return Container( 167 | height: _suspensionHeight.toDouble(), 168 | padding: const EdgeInsets.only(left: 15.0), 169 | color: Color(0xfff3f4f5), 170 | alignment: Alignment.centerLeft, 171 | child: Text( 172 | '$susTag', 173 | softWrap: false, 174 | style: TextStyle( 175 | fontSize: 14.0, 176 | color: Color(0xff999999) 177 | ), 178 | ), 179 | ); 180 | } 181 | 182 | // 自定义item 183 | Widget _buildListItem(CityInfo model){ 184 | String susTag = model.getSuspensionTag(); 185 | return Column( 186 | children: [ 187 | Offstage( 188 | offstage: model.isShowSuspension != true, 189 | child: _buildSusWidget(susTag), 190 | ), 191 | SizedBox( 192 | height: _itemHeight.toDouble(), 193 | child: ListTile( 194 | title: Text(model.name), 195 | onTap: (){ 196 | print('onItemClick:$model'); 197 | Navigator.pop(context, model.name); 198 | }, 199 | ), 200 | ) 201 | ], 202 | ); 203 | } 204 | } 205 | 206 | class CityInfo extends ISuspensionBean { 207 | String name; 208 | String tagIndex; 209 | String namePinyin; 210 | 211 | CityInfo({ 212 | this.name, 213 | this.tagIndex, 214 | this.namePinyin, 215 | }); 216 | 217 | CityInfo.fromJson(Map json) 218 | : name = json['name'] == null ? "" : json['name']; 219 | 220 | Map toJson() => { 221 | 'name': name, 222 | 'tagIndex': tagIndex, 223 | 'namePinyin': namePinyin, 224 | 'isShowSuspension': isShowSuspension 225 | }; 226 | 227 | @override 228 | String getSuspensionTag() => tagIndex; 229 | 230 | @override 231 | String toString() => "CityBean {" + " \"name\":\"" + name + "\"" + '}'; 232 | } 233 | -------------------------------------------------------------------------------- /lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_swiper/flutter_swiper.dart'; 3 | import 'package:flutter_wtrip/dao/home_dao.dart'; 4 | import 'package:flutter_wtrip/model/common_model.dart'; 5 | import 'package:flutter_wtrip/model/gridNav_model.dart'; 6 | 7 | import 'package:flutter_wtrip/model/home_model.dart'; 8 | import 'package:flutter_wtrip/model/sales_box_model.dart'; 9 | import 'package:flutter_wtrip/pages/search_page.dart'; 10 | import 'package:flutter_wtrip/widgets/grid_nav.dart'; 11 | import 'package:flutter_wtrip/widgets/loading_container.dart'; 12 | import 'package:flutter_wtrip/widgets/local_nav.dart'; 13 | import 'package:flutter_wtrip/widgets/sales_box.dart'; 14 | import 'package:flutter_wtrip/widgets/search_bar.dart'; 15 | import 'package:flutter_wtrip/widgets/sub_nav.dart'; 16 | import 'package:flutter_wtrip/widgets/webview.dart'; 17 | import 'package:flutter_wtrip/pages/city_page.dart'; 18 | import 'package:flutter_wtrip/util/navigator_util.dart'; 19 | import 'package:flutter_wtrip/widgets/cached_image.dart'; 20 | import 'package:flutter_wtrip/pages/speak_page.dart'; 21 | 22 | const APPBAR_SCROLL_OFFSET = 100; 23 | const SEARCH_BAR_DEFAULT_TEXT = '网红打卡地 景点 酒店 美食'; 24 | 25 | class HomePage extends StatefulWidget { 26 | @override 27 | _HomePageState createState() => _HomePageState(); 28 | } 29 | 30 | class _HomePageState extends State 31 | with AutomaticKeepAliveClientMixin { 32 | double appBarAlpha = 0; 33 | 34 | String resultString = ''; 35 | 36 | List localNavList = []; 37 | List bannerList = []; 38 | List subNavList = []; 39 | 40 | GridNavModel gridNavModel; 41 | SalesBoxModel salesBoxModel; 42 | 43 | // 加载状态 44 | bool isLoading = true; 45 | 46 | String city = '北京市'; 47 | 48 | @override 49 | void initState() { 50 | super.initState(); 51 | _handleRefresh(); 52 | } 53 | 54 | //缓存页面 55 | @override 56 | bool get wantKeepAlive => true; 57 | 58 | Future _handleRefresh() async { 59 | // HomeDao.fetch().then((result){ 60 | // setState(() { 61 | // resultString = json.encode(result); 62 | // }); 63 | // }).catchError((e){ 64 | // setState(() { 65 | // resultString = json.encode(e); 66 | // }); 67 | // }); 68 | try { 69 | HomeModel model = await HomeDao.fetch(); 70 | setState(() { 71 | localNavList = model.localNavList; 72 | gridNavModel = model.gridNav; 73 | subNavList = model.subNavList; 74 | salesBoxModel = model.salesBox; 75 | bannerList = model.bannerList; 76 | isLoading = false; 77 | }); 78 | 79 | // print(model.config.searchUrl); 80 | } catch (e) { 81 | print(e); 82 | setState(() { 83 | isLoading = false; 84 | }); 85 | } 86 | 87 | return null; 88 | } 89 | 90 | //判断滚动改变透明度 91 | _onScroll(offset) { 92 | double alpha = offset / APPBAR_SCROLL_OFFSET; 93 | if (alpha < 0) { 94 | alpha = 0; 95 | } else if (alpha > 1) { 96 | alpha = 1; 97 | } 98 | 99 | setState(() { 100 | appBarAlpha = alpha; 101 | }); 102 | } 103 | 104 | // 跳转至城市页面 105 | _jumpToCity() async { 106 | String result = await NavigatorUtil.push(context, CityPage()); 107 | 108 | if (result != null) { 109 | setState(() { 110 | city = result; 111 | }); 112 | } 113 | } 114 | 115 | // 跳转搜索页面 116 | _jumpToSearch() { 117 | NavigatorUtil.push(context, SearchPage(hint: SEARCH_BAR_DEFAULT_TEXT)); 118 | } 119 | 120 | // 跳转语音页面 121 | _jumpToSpeak() { 122 | NavigatorUtil.push(context, SpeakPage()); 123 | } 124 | 125 | @override 126 | Widget build(BuildContext context) { 127 | // TODO: implement build 128 | return Scaffold( 129 | backgroundColor: Color(0xfff2f2f2), 130 | body: LoadingContainer( 131 | isLoading: isLoading, 132 | child: Stack( 133 | children: [ 134 | MediaQuery.removePadding( 135 | // 去除手机顶部的padding 136 | removeTop: true, 137 | context: context, 138 | child: RefreshIndicator( 139 | onRefresh: _handleRefresh, 140 | child: NotificationListener( 141 | onNotification: (scrollNotification) { 142 | if (scrollNotification is ScrollUpdateNotification && 143 | scrollNotification.depth == 0) { 144 | // 滑动滚动是第0个元素,也就是列表滚动的时候才监听 145 | _onScroll(scrollNotification.metrics.pixels); 146 | } 147 | return true; 148 | }, 149 | child: _listView, 150 | ), 151 | )), 152 | _appBar, 153 | ], 154 | )), 155 | ); 156 | } 157 | 158 | // 顶部搜索框 159 | Widget get _appBar { 160 | return Column( 161 | children: [ 162 | Container( 163 | // 装饰器 164 | decoration: BoxDecoration( 165 | // 线性渐变 166 | gradient: LinearGradient( 167 | colors: [Color(0x66000000), Colors.transparent], 168 | begin: Alignment.topCenter, 169 | end: Alignment.bottomCenter, 170 | )), 171 | child: Container( 172 | padding: EdgeInsets.fromLTRB(0, 20, 0, 0), 173 | height: 80, 174 | decoration: BoxDecoration( 175 | color: Color.fromARGB((appBarAlpha * 255).toInt(), 255, 255, 255), 176 | ), 177 | child: SearchBar( 178 | searchBarType: appBarAlpha > 0.2 179 | ? SearchBarType.homeLight 180 | : SearchBarType.home, 181 | defaultText: SEARCH_BAR_DEFAULT_TEXT, 182 | inputBoxClick: _jumpToSearch, 183 | speakClick: _jumpToSpeak, 184 | leftButtonClick: _jumpToCity, 185 | city: city, 186 | ), 187 | ), 188 | ), 189 | // 底部阴影 190 | Container( 191 | height: appBarAlpha > 0.2 ? 0.5 : 0, 192 | decoration: BoxDecoration( 193 | boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 0.5)]), 194 | ) 195 | ], 196 | ); 197 | } 198 | 199 | // banner轮播图 200 | Widget get _banner { 201 | return Container( 202 | height: 160, 203 | child: Swiper( 204 | itemCount: bannerList.length, 205 | autoplay: true, // 自动播放 206 | itemBuilder: (BuildContext context, int index) { 207 | return GestureDetector( 208 | // 点击事件 209 | onTap: () { 210 | NavigatorUtil.push( 211 | context, 212 | WebView( 213 | url: bannerList[index].url, 214 | statusBarColor: bannerList[index].statusBarColor, 215 | hideAppBar: bannerList[index].hideAppBar, 216 | )); 217 | }, 218 | child: CachedImage( 219 | imageUrl: bannerList[index].icon, 220 | fit: BoxFit.fill, 221 | ), 222 | ); 223 | }, 224 | pagination: SwiperPagination(), // 指示器 225 | ), 226 | ); 227 | } 228 | 229 | Widget get _listView { 230 | return ListView( 231 | physics: const AlwaysScrollableScrollPhysics(), 232 | children: [ 233 | _banner, 234 | Padding( 235 | child: LocalNav( 236 | localNavList: localNavList, 237 | ), 238 | padding: EdgeInsets.fromLTRB(7, 4, 7, 4), 239 | ), 240 | Padding( 241 | child: GridNav( 242 | gridNavModel: gridNavModel, 243 | ), 244 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4), 245 | ), 246 | Padding( 247 | child: SubNav( 248 | subNavList: subNavList, 249 | ), 250 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4), 251 | ), 252 | Padding( 253 | child: SalesBox( 254 | salesBox: salesBoxModel, 255 | ), 256 | padding: EdgeInsets.fromLTRB(7, 0, 7, 4), 257 | ), 258 | ], 259 | ); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /lib/pages/my_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/widgets/webview.dart'; 3 | 4 | class MyPage extends StatefulWidget{ 5 | @override 6 | _MyPageState createState() => _MyPageState(); 7 | } 8 | 9 | class _MyPageState extends State{ 10 | @override 11 | Widget build(BuildContext context) { 12 | // TODO: implement build 13 | return Scaffold( 14 | body: WebView( 15 | url: 'https://m.ctrip.com/webapp/myctrip/', 16 | hideAppBar: true, 17 | backForbid: true, 18 | statusBarColor: '4c5bca', 19 | ), 20 | ); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /lib/pages/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/dao/search_dao.dart'; 3 | import 'package:flutter_wtrip/model/search_model.dart'; 4 | import 'package:flutter_wtrip/widgets/search_bar.dart'; 5 | import 'package:flutter_wtrip/widgets/webview.dart'; 6 | 7 | const URL = 8 | 'https://m.ctrip.com/restapi/h5api/searchapp/search?source=mobileweb&action=autocomplete&contentType=json&keyword='; 9 | 10 | class SearchPage extends StatefulWidget { 11 | final bool hideLeft; 12 | final String searchUrl; 13 | final String keyWord; 14 | final String hint; 15 | 16 | const SearchPage( 17 | {Key key, this.hideLeft, this.searchUrl = URL, this.keyWord, this.hint}) 18 | : super(key: key); 19 | 20 | @override 21 | _SearchPageState createState() => _SearchPageState(); 22 | } 23 | 24 | class _SearchPageState extends State { 25 | SearchModel searchModel; 26 | String keyWord; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | // TODO: implement build 31 | return Scaffold( 32 | body: Column( 33 | children: [ 34 | _appBar(context), 35 | MediaQuery.removePadding( 36 | removeTop: true, 37 | context: context, 38 | child: Expanded( 39 | flex: 1, 40 | child: ListView.builder( 41 | itemCount: searchModel?.data?.length ?? 0, 42 | itemBuilder: (BuildContext context, int position) { 43 | return _item(position); 44 | }))) 45 | ], 46 | ), 47 | ); 48 | } 49 | 50 | // 搜索结果item 51 | _item(int pos) { 52 | if (searchModel == null || searchModel.data == null) return null; 53 | SearchItem item = searchModel.data[pos]; 54 | return GestureDetector( 55 | onTap: () { 56 | Navigator.push( 57 | context, 58 | MaterialPageRoute( 59 | builder: (context) => (WebView( 60 | url: item.url, 61 | title: '详情', 62 | )))); 63 | }, 64 | child: Container( 65 | padding: EdgeInsets.all(10), 66 | decoration: BoxDecoration( 67 | border: Border(bottom: BorderSide(color: Colors.grey, width: 0.3))), 68 | child: Row( 69 | children: [ 70 | Column( 71 | children: [ 72 | Container( 73 | width: 300, 74 | child: _title(item), 75 | ), 76 | Container( 77 | margin: EdgeInsets.only(top: 5), 78 | width: 300, 79 | child: _subTitle(item), 80 | ) 81 | ], 82 | ) 83 | ], 84 | ), 85 | ), 86 | ); 87 | } 88 | // 主标题 89 | _title(SearchItem item) { 90 | if (item == null) return null; 91 | List spans = []; 92 | spans.addAll(_keywordTextSpans(item.word, searchModel.keyword)); 93 | spans.add(TextSpan( 94 | text: ' ' + (item.districtname??'') + ' ' +(item.zonename??''), 95 | style: TextStyle( 96 | color: Colors.grey, 97 | fontSize: 16, 98 | ))); 99 | 100 | return RichText( 101 | text: TextSpan( 102 | children: spans, 103 | ), 104 | ); 105 | } 106 | // 副标题 107 | _subTitle(SearchItem item) { 108 | if (item == null) return null; 109 | return RichText( 110 | text: TextSpan( 111 | children: [ 112 | TextSpan( 113 | text: item.price??"", 114 | style: TextStyle(color: Colors.orange, fontSize: 16), 115 | ), 116 | TextSpan( 117 | text: " " + (item.star??""), 118 | style: TextStyle(color: Colors.orange, fontSize: 16), 119 | ), 120 | ] 121 | ), 122 | ); 123 | } 124 | // 富文本文字高亮 125 | _keywordTextSpans(String word, String keyword) { 126 | if (word == null || word.length == 0) return null; 127 | List spans = []; 128 | List arr = word.split(keyword); 129 | for (int i = 0; i < arr.length; i++) { 130 | if ((i + 1) % 2 == 0) { 131 | spans.add(TextSpan( 132 | text: keyword, 133 | style: TextStyle(color: Colors.orange, fontSize: 16))); 134 | } 135 | String val = arr[i]; 136 | if (val != null && val.length > 0) { 137 | spans.add(TextSpan( 138 | text: val, style: TextStyle(color: Colors.black87, fontSize: 16))); 139 | } 140 | } 141 | return spans; 142 | } 143 | 144 | _appBar(BuildContext context) { 145 | return Column( 146 | children: [ 147 | Container( 148 | decoration: BoxDecoration( 149 | gradient: LinearGradient( 150 | colors: [Color(0x66000000), Colors.transparent], 151 | begin: Alignment.topCenter, 152 | end: Alignment.bottomCenter, 153 | )), 154 | child: Container( 155 | padding: EdgeInsets.only(top: 20), 156 | height: 80, 157 | decoration: BoxDecoration(color: Colors.white), 158 | child: SearchBar( 159 | hideLeft: widget.hideLeft, 160 | hint: widget.hint, 161 | defaultText: widget.keyWord, 162 | leftButtonClick: () { 163 | Navigator.pop(context); 164 | }, 165 | onChange: _onChangeText, 166 | searchBarType: SearchBarType.normal, 167 | ), 168 | ), 169 | ) 170 | ], 171 | ); 172 | } 173 | 174 | _onChangeText(String text) async{ 175 | keyWord = text; 176 | if (text.length == 0) { 177 | setState(() { 178 | searchModel = null; 179 | }); 180 | return; 181 | } 182 | 183 | try{ 184 | SearchModel model = await SearchDao.fetch(text); 185 | if (model.keyword == keyWord) { 186 | setState(() { 187 | searchModel = model; 188 | }); 189 | } 190 | }catch(e){ 191 | print(e); 192 | } 193 | 194 | // SearchDao.fetch(text).then((SearchModel modal) { 195 | // if (modal.keyword == keyWord) { 196 | // setState(() { 197 | // searchModel = modal; 198 | // }); 199 | // } 200 | // }).catchError((e) { 201 | // print(e); 202 | // }); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/pages/speak_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/pages/search_page.dart'; 3 | import 'package:flutter_wtrip/util/navigator_util.dart'; 4 | import 'package:flutter_wtrip/plugin/asr_manager.dart'; 5 | 6 | class SpeakPage extends StatefulWidget { 7 | @override 8 | _SpeakPageState createState() => _SpeakPageState(); 9 | } 10 | 11 | class _SpeakPageState extends State 12 | with SingleTickerProviderStateMixin { 13 | String speakTips = '长按说话'; 14 | String speakResult = ''; 15 | 16 | Animation animation; 17 | AnimationController controller; 18 | 19 | @override 20 | void initState() { 21 | controller = AnimationController( 22 | vsync: this, duration: Duration(milliseconds: 1000)); 23 | animation = CurvedAnimation(parent: controller, curve: Curves.easeIn) 24 | ..addStatusListener((status) { 25 | if (status == AnimationStatus.completed) { 26 | controller.reverse(); 27 | } else if (status == AnimationStatus.dismissed) { 28 | controller.forward(); 29 | } 30 | }); 31 | super.initState(); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | // TODO: implement dispose 37 | controller.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | // 开始录音 42 | void _speakStart() { 43 | controller.forward(); 44 | setState(() { 45 | speakTips = '识别中...'; 46 | }); 47 | 48 | AsrManager.start().then((text) { 49 | print('---识别的文字:'+text); 50 | if (text != null && text.length > 0) { 51 | setState(() { 52 | speakResult = text; 53 | }); 54 | Navigator.pop(context); 55 | NavigatorUtil.push( 56 | context, 57 | SearchPage( 58 | keyWord: speakResult, 59 | )); 60 | } 61 | }).catchError((e) { 62 | print('-------' + e.toString()); 63 | }); 64 | } 65 | 66 | // 停止录音 67 | void _speakStop() { 68 | setState(() { 69 | speakTips = '长按说话'; 70 | }); 71 | 72 | controller.reset(); 73 | controller.stop(); 74 | AsrManager.stop(); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return Scaffold( 80 | body: Container( 81 | padding: EdgeInsets.all(30), 82 | child: Center( 83 | child: Column( 84 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 85 | children: [ 86 | _topItem, 87 | _bottomItem, 88 | ], 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | 95 | Widget get _topItem { 96 | return Column( 97 | children: [ 98 | Padding( 99 | padding: EdgeInsets.fromLTRB(0, 30, 0, 30), 100 | child: Text( 101 | '你可以这样说', 102 | style: TextStyle(fontSize: 16, color: Colors.black54), 103 | ), 104 | ), 105 | Text( 106 | '故宫门票\n北京一日游\n迪士尼乐园', 107 | textAlign: TextAlign.center, 108 | style: TextStyle(fontSize: 15, color: Colors.grey), 109 | ), 110 | Padding( 111 | padding: EdgeInsets.all(20), 112 | child: Text( 113 | speakResult, 114 | style: TextStyle(color: Colors.blue), 115 | ), 116 | ) 117 | ], 118 | ); 119 | } 120 | 121 | Widget get _bottomItem { 122 | return FractionallySizedBox( 123 | child: Stack( 124 | children: [ 125 | GestureDetector( 126 | onTapDown: (e) { 127 | _speakStart(); 128 | }, 129 | onTapUp: (e) { 130 | _speakStop(); 131 | }, 132 | onTapCancel: () { 133 | _speakStop(); 134 | }, 135 | child: Center( 136 | child: Column( 137 | children: [ 138 | Padding( 139 | padding: EdgeInsets.all(10), 140 | child: Text( 141 | speakTips, 142 | style: TextStyle(color: Colors.blue, fontSize: 12), 143 | ), 144 | ), 145 | Stack( 146 | children: [ 147 | Container( 148 | height: MIC_SIZE, 149 | width: MIC_SIZE, 150 | ), 151 | Center( 152 | child: AnimatedMic( 153 | animation: animation, 154 | ), 155 | ) 156 | ], 157 | ) 158 | ], 159 | ), 160 | ), 161 | ), 162 | Positioned( 163 | right: 0, 164 | bottom: 20, 165 | child: GestureDetector( 166 | onTap: () { 167 | Navigator.pop(context); 168 | }, 169 | child: Icon( 170 | Icons.close, 171 | size: 30, 172 | color: Colors.grey, 173 | ), 174 | ), 175 | ), 176 | // Text( 177 | // '百度AI语音平台提供技术支持', 178 | // style: TextStyle( 179 | // fontSize: 12, 180 | // color: Color.fromARGB(255, 200, 200, 200) 181 | // ), 182 | // ) 183 | ], 184 | ), 185 | ); 186 | } 187 | } 188 | 189 | const double MIC_SIZE = 80; 190 | 191 | class AnimatedMic extends AnimatedWidget { 192 | static final _operatyTween = Tween(begin: 1, end: 0.5); 193 | static final _sizeTween = Tween(begin: MIC_SIZE, end: MIC_SIZE - 20); 194 | 195 | AnimatedMic({Key key, Animation animation}) 196 | : super(key: key, listenable: animation); 197 | 198 | @override 199 | Widget build(BuildContext context) { 200 | final Animation animation = listenable; 201 | 202 | return Opacity( 203 | opacity: _operatyTween.evaluate(animation), 204 | child: Container( 205 | height: _sizeTween.evaluate(animation), 206 | width: _sizeTween.evaluate(animation), 207 | decoration: BoxDecoration( 208 | color: Colors.blue, 209 | borderRadius: BorderRadius.circular(MIC_SIZE / 2)), 210 | child: Icon( 211 | Icons.mic, 212 | color: Colors.white, 213 | size: 30, 214 | ), 215 | ), 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/pages/travel_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/dao/travel_tab_dao.dart'; 3 | import 'package:flutter_wtrip/model/travel_tab_model.dart'; 4 | import 'package:flutter_wtrip/pages/travel_tab_page.dart'; 5 | 6 | const DEFAULT_URL = 7 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5'; 8 | const PAGE_SIZE = 10; 9 | 10 | class TravelPage extends StatefulWidget { 11 | @override 12 | _TravelPageState createState() => _TravelPageState(); 13 | } 14 | 15 | class _TravelPageState extends State with TickerProviderStateMixin { 16 | TabController _tabController; 17 | TravelTabModel travelTabModel; 18 | List tabs = []; 19 | 20 | @override 21 | void initState() { 22 | _tabController = TabController(length: 0, vsync: this); 23 | TravelTabDao.fetch().then((TravelTabModel model) { 24 | // 重新设置coller 修复标签不显示 25 | _tabController = TabController(length: model.tabs.length, vsync: this); 26 | setState(() { 27 | tabs = model.tabs; 28 | travelTabModel = model; 29 | }); 30 | }).catchError((e) { 31 | print(e); 32 | }); 33 | 34 | super.initState(); 35 | } 36 | 37 | @override 38 | void dispose() { 39 | _tabController.dispose(); 40 | super.dispose(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | // TODO: implement build 46 | return Scaffold( 47 | body: Column( 48 | children: [ 49 | Container( 50 | color: Colors.white, 51 | padding: EdgeInsets.only(top: 30), 52 | child: TabBar( 53 | controller: _tabController, 54 | isScrollable: true, 55 | labelColor: Colors.black, 56 | labelPadding: EdgeInsets.fromLTRB(20, 0, 20, 5), 57 | indicatorSize: TabBarIndicatorSize.label, 58 | indicator: UnderlineTabIndicator( 59 | borderSide: BorderSide( 60 | color: Color(0xff2fcfbb), 61 | width: 3, 62 | ), 63 | insets: EdgeInsets.only(bottom: 10)), 64 | tabs: tabs.map((TravelTab tab) { 65 | return Tab( 66 | text: tab.labelName, 67 | ); 68 | }).toList(), // 将map装换成list 69 | ), 70 | ), 71 | Flexible( 72 | child: TabBarView( 73 | controller: _tabController, 74 | children: tabs.map((TravelTab tab) { 75 | return TravelTabPage(travelUrl: travelTabModel.url, groupChannelCode: tab.groupChannelCode,); 76 | }).toList())) 77 | ], 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/pages/travel_tab_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/dao/travel_page_dao.dart'; 3 | import 'package:flutter_wtrip/model/travel_page_model.dart'; 4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 5 | import 'package:flutter_wtrip/widgets/loading_container.dart'; 6 | import 'package:flutter_wtrip/widgets/webview.dart'; 7 | 8 | const DEFAULT_URL = 9 | 'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5'; 10 | const PAGE_SIZE = 10; 11 | 12 | class TravelTabPage extends StatefulWidget { 13 | final String groupChannelCode; 14 | final String travelUrl; 15 | 16 | const TravelTabPage({Key key, this.groupChannelCode, this.travelUrl}) 17 | : super(key: key); 18 | 19 | @override 20 | _TravelTabPageState createState() => _TravelTabPageState(); 21 | } 22 | 23 | /// with AutomaticKeepAliveClientMixin 加载过的页面重绘 24 | class _TravelTabPageState extends State 25 | with AutomaticKeepAliveClientMixin { 26 | List travelPageItems = []; 27 | int pageIndex = 1; 28 | bool _isLoading = true; 29 | bool showToTopBtn = false; 30 | 31 | ScrollController _scrollController = ScrollController(); 32 | 33 | @override 34 | void initState() { 35 | _loadData(); 36 | _scrollController.addListener(() { 37 | if (_scrollController.offset < 1000 && showToTopBtn) { 38 | setState(() { 39 | showToTopBtn = false; 40 | }); 41 | } else if (_scrollController.offset >= 1000 && showToTopBtn == false) { 42 | setState(() { 43 | showToTopBtn = true; 44 | }); 45 | } 46 | 47 | if (_scrollController.position.pixels == 48 | _scrollController.position.maxScrollExtent) { 49 | _loadData(isLoadMore: true); 50 | } 51 | }); 52 | super.initState(); 53 | } 54 | 55 | // 刷新是一个异步方法 56 | Future _handleRefresh() async { 57 | _loadData(); 58 | 59 | return null; 60 | } 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | // TODO: implement build 65 | return Scaffold( 66 | body: LoadingContainer( 67 | isLoading: _isLoading, 68 | child: RefreshIndicator( 69 | child: MediaQuery.removePadding( 70 | removeTop: true, 71 | context: context, 72 | child: StaggeredGridView.countBuilder( 73 | controller: _scrollController, 74 | crossAxisCount: 4, 75 | itemCount: travelPageItems?.length ?? 0, 76 | itemBuilder: (BuildContext context, int index) => _TravelItem( 77 | index: index, 78 | item: travelPageItems[index], 79 | ), 80 | staggeredTileBuilder: (int index) => new StaggeredTile.fit(2), 81 | )), 82 | onRefresh: _handleRefresh), 83 | ), 84 | // 显示一个返回顶部按钮 85 | floatingActionButton: !showToTopBtn 86 | ? null 87 | : FloatingActionButton( 88 | child: Icon(Icons.arrow_upward), 89 | onPressed: () { 90 | _scrollController.animateTo(.0, 91 | duration: Duration(microseconds: 200), curve: Curves.ease); 92 | }, 93 | ), 94 | ); 95 | } 96 | 97 | /// Dart 可选参数 98 | void _loadData({isLoadMore = false}) async { 99 | if (isLoadMore) { 100 | pageIndex++; 101 | } else { 102 | pageIndex = 1; 103 | } 104 | 105 | try { 106 | TravelPageModel model = await TravelPageDao.fetch( 107 | widget.travelUrl ?? DEFAULT_URL, 108 | widget.groupChannelCode, 109 | pageIndex, 110 | PAGE_SIZE); 111 | _isLoading = false; 112 | 113 | setState(() { 114 | List items = _filterItems(model.resultList); 115 | if (travelPageItems != null) { 116 | travelPageItems.addAll(items); 117 | } else { 118 | travelPageItems = items; 119 | } 120 | }); 121 | } catch (e) { 122 | print(e); 123 | _isLoading = false; 124 | } 125 | } 126 | 127 | /// 过滤服务器返回结果, 移除article为空 128 | List _filterItems(List resultList) { 129 | if (resultList == null) { 130 | return []; 131 | } 132 | List filterItems = []; 133 | resultList.forEach((item) { 134 | if (item.article != null) { 135 | filterItems.add(item); 136 | } 137 | }); 138 | 139 | return filterItems; 140 | } 141 | 142 | @override 143 | // TODO: implement wantKeepAlive 144 | bool get wantKeepAlive => true; 145 | } 146 | 147 | class _TravelItem extends StatelessWidget { 148 | final int index; 149 | final TravelPageItem item; 150 | 151 | const _TravelItem({Key key, this.index, this.item}) : super(key: key); 152 | 153 | @override 154 | Widget build(BuildContext context) { 155 | // TODO: implement build 156 | return GestureDetector( 157 | onTap: () { 158 | Navigator.push( 159 | context, 160 | MaterialPageRoute( 161 | builder: (context) => (WebView( 162 | url: item.article.urls[0].h5Url, 163 | title: '旅拍详情', 164 | )))); 165 | }, 166 | child: Card( 167 | /// 裁切圆角 168 | child: PhysicalModel( 169 | color: Colors.transparent, 170 | clipBehavior: Clip.antiAlias, 171 | borderRadius: BorderRadius.circular(5), 172 | child: Column( 173 | crossAxisAlignment: CrossAxisAlignment.start, // 设置子widget未知 174 | children: [ 175 | _itemImage(), 176 | Container( 177 | padding: EdgeInsets.all(4), 178 | child: Text( 179 | item.article.articleTitle, 180 | maxLines: 2, 181 | overflow: TextOverflow.ellipsis, 182 | style: TextStyle(color: Colors.black87, fontSize: 14), 183 | ), 184 | ), 185 | _infoText(), 186 | ], 187 | ), 188 | ), 189 | ), 190 | ); 191 | } 192 | 193 | _infoText() { 194 | return Container( 195 | padding: EdgeInsets.fromLTRB(6, 0, 6, 10), 196 | child: Row( 197 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 198 | children: [ 199 | Row( 200 | children: [ 201 | PhysicalModel( 202 | color: Colors.transparent, 203 | clipBehavior: Clip.antiAlias, 204 | borderRadius: BorderRadius.circular(12), 205 | child: Image.network( 206 | item.article.author?.coverImage?.dynamicUrl, 207 | width: 24, 208 | height: 24, 209 | ), 210 | ), 211 | Container( 212 | padding: EdgeInsets.all(5), 213 | width: 90, 214 | child: Text( 215 | item.article.articleTitle, 216 | maxLines: 1, 217 | overflow: TextOverflow.ellipsis, 218 | style: TextStyle( 219 | color: Colors.black87, 220 | fontSize: 12, 221 | ), 222 | ), 223 | ) 224 | ], 225 | ), 226 | Row( 227 | children: [ 228 | Icon( 229 | Icons.thumb_up, 230 | size: 14, 231 | color: Colors.grey, 232 | ), 233 | Padding( 234 | padding: EdgeInsets.only(left: 3), 235 | child: Text(item.article.likeCount.toString()), 236 | ) 237 | ], 238 | ) 239 | ], 240 | ), 241 | ); 242 | } 243 | 244 | _itemImage() { 245 | return Stack( 246 | children: [ 247 | Image.network(item.article.images[0]?.dynamicUrl), 248 | Positioned( 249 | bottom: 8, 250 | left: 8, 251 | child: Container( 252 | padding: EdgeInsets.fromLTRB(5, 1, 5, 1), 253 | decoration: BoxDecoration( 254 | color: Colors.black54, borderRadius: BorderRadius.circular(10)), 255 | child: Row( 256 | children: [ 257 | Padding( 258 | padding: EdgeInsets.only(right: 3), 259 | child: Icon( 260 | Icons.location_on, 261 | color: Colors.white, 262 | size: 12, 263 | ), 264 | ), 265 | LimitedBox( 266 | // 显示带缩略号的文字 267 | maxWidth: 130, 268 | child: Text( 269 | _poiName(), 270 | maxLines: 1, 271 | overflow: TextOverflow.ellipsis, 272 | style: TextStyle(color: Colors.white, fontSize: 12), 273 | ), 274 | ) 275 | ], 276 | ), 277 | ), 278 | ) 279 | ], 280 | ); 281 | } 282 | 283 | _poiName() { 284 | return item.article.pois == null || item.article.pois.length == 0 285 | ? '未知' 286 | : item.article.pois[0]?.poiName ?? '未知'; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /lib/plugin/asr_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | // 百度ai语音flutter方法 4 | class AsrManager { 5 | static const MethodChannel _channel = const MethodChannel('asr_plugin'); 6 | 7 | // 开始录音 8 | static Future start({Map params}) async { 9 | return await _channel.invokeMethod('start', params ??{}); 10 | } 11 | // 停止录音 12 | static Future stop() async { 13 | return await _channel.invokeMethod('stop'); 14 | } 15 | // 取消录音 16 | static Future cancel() async { 17 | return await _channel.invokeMethod('cancel'); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/util/navigator_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | // 页面跳转工具类 4 | class NavigatorUtil { 5 | // 跳转页面 6 | static push(BuildContext context, Widget page) async { 7 | final result = await Navigator.push( 8 | context, MaterialPageRoute(builder: (context) => page)); 9 | return result; 10 | } 11 | // 使用命名路由进行跳转 12 | static pushNamed(BuildContext context, String routeName) async { 13 | final result = await Navigator.pushNamed(context, routeName); 14 | return result; 15 | } 16 | // 返回上一页 17 | static pop(BuildContext context) { 18 | Navigator.pop(context); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/cached_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CachedImage extends StatelessWidget { 5 | final AlignmentGeometry alignment; 6 | final BoxFit fit; 7 | final String imageUrl; 8 | final double width; 9 | final double height; 10 | final bool inSizedBox; 11 | 12 | const CachedImage( 13 | {Key key, 14 | @required this.imageUrl, 15 | this.alignment = Alignment.center, 16 | this.fit, 17 | this.width, 18 | this.height, 19 | this.inSizedBox = false}) 20 | : assert(imageUrl != null), 21 | super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return inSizedBox 26 | ? FractionallySizedBox( 27 | widthFactor: 1, 28 | child: CachedNetworkImage( 29 | imageUrl: imageUrl, 30 | alignment: alignment, 31 | fit: fit, 32 | width: width, 33 | height: height, 34 | ), 35 | ) 36 | : CachedNetworkImage( 37 | imageUrl: imageUrl, 38 | alignment: alignment, 39 | fit: fit, 40 | width: width, 41 | height: height, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/widgets/grid_nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/model/common_model.dart'; 3 | import 'package:flutter_wtrip/model/gridNav_model.dart'; 4 | import 'package:flutter_wtrip/widgets/webview.dart'; 5 | 6 | class GridNav extends StatelessWidget { 7 | final GridNavModel gridNavModel; 8 | 9 | const GridNav({Key key, this.gridNavModel}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | // TODO: implement build 14 | return PhysicalModel( 15 | color: Colors.transparent,// 透明 16 | borderRadius: BorderRadius.circular(6), // 圆角 17 | clipBehavior: Clip.antiAlias, // 裁切 18 | child: Column( 19 | children: _gridNavItems(context), 20 | ), 21 | ); 22 | } 23 | 24 | _gridNavItems(BuildContext context) { 25 | // 整个卡片 26 | List items = []; 27 | if (gridNavModel == null) return items; 28 | if (gridNavModel.hotel != null) { 29 | items.add(_gridNavItem(context,gridNavModel.hotel, true)); 30 | } 31 | if (gridNavModel.flight != null) { 32 | items.add(_gridNavItem(context,gridNavModel.flight, false)); 33 | } 34 | if (gridNavModel.travel != null) { 35 | items.add(_gridNavItem(context,gridNavModel.travel, false)); 36 | } 37 | 38 | return items; 39 | } 40 | 41 | _gridNavItem(BuildContext context, GridNavItem item, bool isFirst) { 42 | // 卡片的三块 43 | List items = []; 44 | items.add(_mainItem(context, item.mainItem)); 45 | items.add(_doubleItem(context, item.item1, item.item2)); 46 | items.add(_doubleItem(context, item.item3, item.item4)); 47 | 48 | List expandItems = []; 49 | items.forEach((item){ 50 | expandItems.add(Expanded(child: item, flex: 1,)); 51 | }); 52 | 53 | Color startColor = Color(int.parse('0xff'+item.startColor)); 54 | Color endColor = Color(int.parse('0xff'+item.endColor)); 55 | 56 | return Container( 57 | height: 88, 58 | margin: isFirst? null: EdgeInsets.only(top: 3), 59 | decoration: BoxDecoration( 60 | // 颜色渐变 61 | gradient: LinearGradient(colors: [startColor, endColor]) 62 | ), 63 | child: Row( 64 | children: expandItems, 65 | ), 66 | ); 67 | } 68 | // 最左边的卡片 69 | _mainItem(BuildContext context, CommonModel model) { 70 | return _wrapGesture(context, Stack( 71 | alignment: AlignmentDirectional.topCenter, 72 | children: [ 73 | Image.network( 74 | model.icon, 75 | fit: BoxFit.contain, 76 | width: 88, 77 | height: 121, 78 | alignment: AlignmentDirectional.bottomEnd, 79 | ), 80 | Container( 81 | margin: EdgeInsets.only(top: 11), 82 | child: Text( 83 | model.title, 84 | style: TextStyle(fontSize: 14, color: Colors.white), 85 | ), 86 | ) 87 | ], 88 | ), model); 89 | } 90 | // 小卡片竖直排列的两块 91 | _doubleItem(BuildContext context, CommonModel topModel, 92 | CommonModel bottomModel) { 93 | return Column( 94 | children: [ 95 | Expanded( 96 | child: _item(context, topModel, true), 97 | ), 98 | Expanded( 99 | child: _item(context, bottomModel, false), 100 | ) 101 | ], 102 | ); 103 | } 104 | // 单独的每个小卡片 105 | _item(BuildContext context, CommonModel item, bool isFirst) { 106 | BorderSide borderSide = BorderSide(width: 0.8, color: Colors.white); 107 | return FractionallySizedBox( 108 | // 撑满父布局 109 | widthFactor: 1, 110 | child: Container( 111 | decoration: BoxDecoration( 112 | border: Border( 113 | left: borderSide, 114 | bottom: isFirst ? borderSide : BorderSide.none, 115 | )), 116 | child: _wrapGesture(context, Center( 117 | child: Text( 118 | item.title, 119 | textAlign: TextAlign.center, 120 | style: TextStyle(fontSize: 14, color: Colors.white), 121 | ), 122 | ), item) 123 | ), 124 | ); 125 | } 126 | // 封装点击跳转逻辑 127 | _wrapGesture(BuildContext context, Widget widget, CommonModel model) { 128 | return GestureDetector( 129 | child: widget, 130 | onTap: (){ 131 | Navigator.push(context, MaterialPageRoute(builder: (context)=>( 132 | WebView(url: model.url, statusBarColor: model.statusBarColor,hideAppBar: model.hideAppBar, title: model.title,) 133 | ))); 134 | }, 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/widgets/loading_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | 4 | // 加载进度条组件 5 | class LoadingContainer extends StatelessWidget { 6 | final Widget child; 7 | final bool isLoading; 8 | final bool cover; 9 | 10 | const LoadingContainer( 11 | {Key key, 12 | @required this.isLoading, 13 | this.cover = false, 14 | @required this.child}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return !cover? !isLoading ? child : _loadingView : 20 | Stack( 21 | children: [ 22 | child, isLoading? _loadingView : null 23 | ], 24 | ); 25 | } 26 | 27 | Widget get _loadingView{ 28 | return Center( 29 | child: new Column( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | crossAxisAlignment: CrossAxisAlignment.center, 32 | children: [ 33 | // new CircularProgressIndicator(), 34 | new SpinKitPumpingHeart(color: Colors.blue,), 35 | new Padding(padding: const EdgeInsets.only(top: 20.0), 36 | child: new Text( 37 | '拼命加载中...', 38 | style: new TextStyle(fontSize: 12.0, color: Colors.lightBlue), 39 | ), 40 | ) 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/widgets/local_nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/model/common_model.dart'; 3 | import 'package:flutter_wtrip/widgets/webview.dart'; 4 | 5 | class LocalNav extends StatelessWidget { 6 | final List localNavList; 7 | 8 | const LocalNav({Key key, @required this.localNavList}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | // TODO: implement build 13 | return Container( 14 | height: 64, 15 | decoration: BoxDecoration( 16 | color: Colors.white, 17 | borderRadius: BorderRadius.all(Radius.circular(6))), 18 | child: Padding( 19 | padding: EdgeInsets.all(7), 20 | child: _items(context), 21 | ), 22 | ); 23 | } 24 | 25 | _items(BuildContext context) { 26 | if (localNavList.length == 0) return null; 27 | 28 | List items = []; 29 | localNavList.forEach((model) { 30 | items.add(_item(context, model)); 31 | }); 32 | 33 | return Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: items, 36 | ); 37 | } 38 | 39 | Widget _item(BuildContext context, CommonModel model) { 40 | return GestureDetector( 41 | onTap: (){ 42 | Navigator.push(context, MaterialPageRoute(builder: (context)=>( 43 | WebView(url: model.url, statusBarColor: model.statusBarColor,hideAppBar: model.hideAppBar,) 44 | ))); 45 | }, 46 | child: Column( 47 | children: [ 48 | Image.network( 49 | model.icon, 50 | width: 32, 51 | height: 32, 52 | ), 53 | Text( 54 | model.title, 55 | style: TextStyle(fontSize: 12), 56 | ) 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/widgets/sales_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/model/common_model.dart'; 3 | import 'package:flutter_wtrip/model/sales_box_model.dart'; 4 | import 'package:flutter_wtrip/widgets/webview.dart'; 5 | 6 | class SalesBox extends StatelessWidget { 7 | final SalesBoxModel salesBox; 8 | 9 | const SalesBox({Key key, @required this.salesBox}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | // TODO: implement build 14 | return Container( 15 | decoration: BoxDecoration( 16 | color: Colors.white, 17 | borderRadius: BorderRadius.all(Radius.circular(6))), 18 | child: _items(context), 19 | ); 20 | } 21 | 22 | _items(BuildContext context) { 23 | List items = []; 24 | 25 | items.add(_doubleItem( 26 | context, salesBox.bigCard1, salesBox.bigCard2, true, true, false)); 27 | items.add(_doubleItem( 28 | context, salesBox.smallCard1, salesBox.smallCard2, false, true, false)); 29 | items.add(_doubleItem( 30 | context, salesBox.smallCard3, salesBox.smallCard4, false, false, true)); 31 | 32 | return Column( 33 | children: [ 34 | Container( 35 | height: 44, 36 | margin: EdgeInsets.only(left: 10), 37 | decoration: BoxDecoration( 38 | border: Border( 39 | bottom: BorderSide(width: 1, color: Color(0xffff2f2f2)))), 40 | child: Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: [ 43 | Image.network( 44 | salesBox.icon, 45 | height: 15, 46 | fit: BoxFit.fill, 47 | ), 48 | Container( 49 | padding: EdgeInsets.fromLTRB(10, 1, 7, 1), 50 | margin: EdgeInsets.only(right: 7), 51 | decoration: BoxDecoration( 52 | borderRadius: BorderRadius.circular(12), 53 | gradient: LinearGradient( 54 | colors: [ 55 | Color(0xffff4e63), 56 | Color(0xffff6cc9), 57 | ], 58 | begin: Alignment.centerLeft, 59 | end: Alignment.centerRight)), 60 | child: GestureDetector( 61 | onTap: () {}, 62 | child: Text( 63 | '获取更多 >', 64 | style: TextStyle(color: Colors.white, fontSize: 12), 65 | ), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ), 71 | Row( 72 | mainAxisAlignment: MainAxisAlignment.center, 73 | children: items.sublist(0, 1), 74 | ), 75 | Row( 76 | mainAxisAlignment: MainAxisAlignment.center, 77 | children: items.sublist(1, 2), 78 | ), 79 | Row( 80 | mainAxisAlignment: MainAxisAlignment.center, 81 | children: items.sublist(2, 3), 82 | ), 83 | ], 84 | ); 85 | } 86 | 87 | Widget _doubleItem(BuildContext context, CommonModel leftCard, 88 | CommonModel rightCard, bool big, bool left, bool last) { 89 | return Row( 90 | mainAxisAlignment: MainAxisAlignment.spaceAround, 91 | children: [ 92 | _item(context, leftCard, big, true, last), 93 | _item(context, rightCard, big, false, last) 94 | ], 95 | ); 96 | } 97 | 98 | Widget _item( 99 | BuildContext context, CommonModel model, bool big, bool left, bool last) { 100 | BorderSide borderSide = BorderSide(width: 0.8, color: Color(0xffff2f2f2)); 101 | return GestureDetector( 102 | onTap: () { 103 | Navigator.push( 104 | context, 105 | MaterialPageRoute( 106 | builder: (context) => (WebView( 107 | url: model.url, 108 | statusBarColor: model.statusBarColor, 109 | hideAppBar: model.hideAppBar, 110 | )))); 111 | }, 112 | child: Container( 113 | decoration: BoxDecoration( 114 | border: Border( 115 | right: left? BorderSide.none: borderSide, 116 | bottom: last? BorderSide.none: borderSide 117 | ) 118 | ), 119 | child: Image.network( 120 | model.icon, 121 | fit: BoxFit.fill, 122 | width: MediaQuery.of(context).size.width / 2- 10, // 获取设备宽度 123 | height: big ? 129 : 80, 124 | ), 125 | )); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/widgets/search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum SearchBarType { home, normal, homeLight } 4 | 5 | class SearchBar extends StatefulWidget { 6 | final bool enabled; 7 | final bool hideLeft; 8 | final SearchBarType searchBarType; 9 | final String hint; 10 | final String defaultText; 11 | final String city; 12 | final void Function() leftButtonClick; 13 | final void Function() rightButtonClick; 14 | final void Function() speakClick; 15 | final void Function() inputBoxClick; 16 | final ValueChanged onChange; 17 | 18 | const SearchBar( 19 | {Key key, 20 | this.enabled = true, 21 | this.hideLeft, 22 | this.searchBarType = SearchBarType.normal, 23 | this.hint, // 提示文案 24 | this.defaultText, 25 | this.leftButtonClick, 26 | this.rightButtonClick, 27 | this.speakClick, 28 | this.inputBoxClick, 29 | this.city, 30 | this.onChange}) 31 | : super(key: key); 32 | 33 | @override 34 | _SearchBarState createState() => _SearchBarState(); 35 | } 36 | 37 | class _SearchBarState extends State { 38 | bool showClear = false; 39 | final TextEditingController _controller = TextEditingController(); 40 | 41 | @override 42 | void initState() { 43 | // TODO: implement initState 44 | if (widget.defaultText != null) { 45 | _controller.text = widget.defaultText; 46 | } 47 | super.initState(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return widget.searchBarType == SearchBarType.normal 53 | ? _genNormalSearch() 54 | : _genHomeSearch(); 55 | } 56 | 57 | _genNormalSearch() { 58 | return Container( 59 | child: Row( 60 | children: [ 61 | _wrapTap( 62 | Container( 63 | padding: EdgeInsets.fromLTRB(6, 5, 10, 5), 64 | child: widget?.hideLeft ?? false 65 | ? null 66 | : Icon( 67 | Icons.arrow_back, 68 | color: Colors.grey, 69 | size: 26, 70 | ), 71 | ), 72 | widget.leftButtonClick), 73 | Expanded( 74 | flex: 1, 75 | child: _inputBox(), 76 | ), 77 | _wrapTap( 78 | Container( 79 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5), 80 | child: Text( 81 | '搜索', 82 | style: TextStyle( 83 | color: Colors.blue, 84 | fontSize: 17, 85 | ), 86 | ), 87 | ), 88 | widget.rightButtonClick), 89 | ], 90 | ), 91 | ); 92 | } 93 | 94 | _genHomeSearch() { 95 | return Container( 96 | child: Row( 97 | children: [ 98 | _wrapTap( 99 | Container( 100 | padding: EdgeInsets.fromLTRB(6, 5, 5, 5), 101 | child: Row( 102 | children: [ 103 | Text( 104 | widget.city, 105 | style: TextStyle(color: _homeFontColor(), fontSize: 14), 106 | ), 107 | Icon( 108 | Icons.expand_more, 109 | color: _homeFontColor(), 110 | size: 22, 111 | ) 112 | ], 113 | ), 114 | ), 115 | widget.leftButtonClick), 116 | Expanded( 117 | flex: 1, 118 | child: _inputBox(), 119 | ), 120 | _wrapTap( 121 | Container( 122 | padding: EdgeInsets.fromLTRB(10, 5, 10, 5), 123 | child: Icon( 124 | Icons.comment, 125 | color: _homeFontColor(), 126 | size: 26, 127 | ), 128 | ), 129 | widget.rightButtonClick), 130 | ], 131 | ), 132 | ); 133 | } 134 | 135 | _inputBox() { 136 | Color inputBoxColor; 137 | if (widget.searchBarType == SearchBarType.home) { 138 | inputBoxColor = Colors.white; 139 | } else { 140 | inputBoxColor = Color(int.parse('0xffEDEDED')); 141 | } 142 | 143 | return Container( 144 | height: 30, 145 | padding: EdgeInsets.fromLTRB(10, 0, 10, 0), 146 | decoration: BoxDecoration( 147 | color: inputBoxColor, 148 | borderRadius: BorderRadius.circular( 149 | widget.searchBarType == SearchBarType.normal ? 5 : 15)), 150 | child: Row( 151 | children: [ 152 | Icon( 153 | Icons.search, 154 | size: 20, 155 | color: widget.searchBarType == SearchBarType.normal 156 | ? Color(0xffA9A9A9) 157 | : Colors.blue, 158 | ), 159 | Expanded( 160 | flex: 1, 161 | child: widget.searchBarType == SearchBarType.normal 162 | ? TextField( 163 | controller: _controller, 164 | onChanged: _onChanged, 165 | autofocus: false, 166 | style: TextStyle( 167 | fontSize: 15, 168 | color: Colors.black, 169 | fontWeight: FontWeight.w300, 170 | textBaseline: TextBaseline.alphabetic), 171 | decoration: InputDecoration( 172 | contentPadding: EdgeInsets.fromLTRB(5, 0, 5, 0), 173 | border: InputBorder.none, 174 | hintText: widget.hint ?? "", 175 | hintStyle: TextStyle(fontSize: 15), 176 | isDense: true, 177 | ), 178 | ) 179 | : _wrapTap( 180 | Container( 181 | child: Text( 182 | widget.defaultText, 183 | style: TextStyle(fontSize: 13, color: Colors.grey), 184 | ), 185 | ), 186 | widget.inputBoxClick), 187 | ), 188 | !showClear 189 | ? _wrapTap( 190 | Icon( 191 | Icons.mic, 192 | size: 20, 193 | color: widget.searchBarType == SearchBarType.normal 194 | ? Colors.blue 195 | : Colors.grey, 196 | ), 197 | widget.speakClick) 198 | : _wrapTap( 199 | Icon( 200 | Icons.clear, 201 | size: 20, 202 | color: Colors.grey, 203 | ), () { 204 | setState(() { 205 | _controller.clear(); 206 | }); 207 | _onChanged(''); 208 | }) 209 | ], 210 | ), 211 | ); 212 | } 213 | 214 | _onChanged(String text) { 215 | if (text.length > 0) { 216 | setState(() { 217 | showClear = true; 218 | }); 219 | } else { 220 | setState(() { 221 | showClear = false; 222 | }); 223 | } 224 | 225 | if (widget.onChange != null) { 226 | widget.onChange(text); 227 | } 228 | } 229 | 230 | _wrapTap(Widget child, void Function() callback) { 231 | return GestureDetector( 232 | onTap: () { 233 | if (callback != null) callback(); 234 | }, 235 | child: child, 236 | ); 237 | } 238 | 239 | _homeFontColor() { 240 | return widget.searchBarType == SearchBarType.homeLight 241 | ? Colors.black54 242 | : Colors.white; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /lib/widgets/sub_nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_wtrip/model/common_model.dart'; 3 | import 'package:flutter_wtrip/widgets/webview.dart'; 4 | 5 | class SubNav extends StatelessWidget { 6 | final List subNavList; 7 | 8 | const SubNav({Key key, @required this.subNavList}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | // TODO: implement build 13 | return Container( 14 | // height: 64, 15 | decoration: BoxDecoration( 16 | color: Colors.white, 17 | borderRadius: BorderRadius.all(Radius.circular(6))), 18 | child: Padding( 19 | padding: EdgeInsets.all(7), 20 | child: _items(context), 21 | ), 22 | ); 23 | } 24 | 25 | _items(BuildContext context) { 26 | if (subNavList.length == 0) return null; 27 | 28 | List items = []; 29 | subNavList.forEach((model) { 30 | items.add(_item(context, model)); 31 | }); 32 | 33 | var spec = (items.length / 2 + 0.5).toInt(); 34 | 35 | return Column( 36 | children: [ 37 | Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 39 | children: items.sublist(0, spec), 40 | ), 41 | Padding( 42 | padding: EdgeInsets.only(top: 10), 43 | child: Row( 44 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 45 | children: items.sublist(spec, items.length), 46 | ), 47 | ) 48 | ], 49 | ); 50 | } 51 | 52 | Widget _item(BuildContext context, CommonModel model) { 53 | return Expanded( 54 | flex: 1, 55 | child: GestureDetector( 56 | onTap: () { 57 | Navigator.push( 58 | context, 59 | MaterialPageRoute( 60 | builder: (context) => (WebView( 61 | url: model.url, 62 | statusBarColor: model.statusBarColor, 63 | hideAppBar: model.hideAppBar, 64 | )))); 65 | }, 66 | child: Column( 67 | children: [ 68 | Image.network( 69 | model.icon, 70 | width: 18, 71 | height: 18, 72 | ), 73 | Padding( 74 | padding: EdgeInsets.only(top: 3), 75 | child: Text( 76 | model.title, 77 | style: TextStyle(fontSize: 12), 78 | ), 79 | ) 80 | ], 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/widgets/webview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 5 | 6 | const CATCH_URLS = ['m.ctrip.com','m.ctrip.com/html5/','m.ctrip.com/html5','https://m.ctrip.com/webapp']; 7 | 8 | class WebView extends StatefulWidget { 9 | final String url; 10 | final String statusBarColor; 11 | final String title; 12 | final bool hideAppBar; 13 | final bool backForbid; // 禁止返回 14 | 15 | 16 | 17 | WebView( 18 | {Key key, 19 | this.url, 20 | this.statusBarColor, 21 | this.title, 22 | this.hideAppBar, 23 | this.backForbid = false}); 24 | 25 | @override 26 | _WebViewState createState() => _WebViewState(); 27 | } 28 | 29 | class _WebViewState extends State { 30 | final flutterWebviewPlugin = new FlutterWebviewPlugin(); 31 | 32 | StreamSubscription _onUrlChangeListener; 33 | StreamSubscription _onStateChangeListener; 34 | 35 | bool exiting = false; 36 | 37 | @override 38 | void initState() { 39 | // TODO: implement initState 40 | super.initState(); 41 | flutterWebviewPlugin.close(); 42 | _onUrlChangeListener = 43 | flutterWebviewPlugin.onUrlChanged.listen((String url) {}); 44 | _onStateChangeListener = 45 | flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) { 46 | switch (state.type) { 47 | case WebViewState.startLoad: 48 | if(_isToMain(state.url) && !exiting) { 49 | if(widget.backForbid) { 50 | flutterWebviewPlugin.launch(widget.url); 51 | } else { 52 | Navigator.pop(context); 53 | exiting = true; 54 | } 55 | } 56 | break; 57 | default: 58 | break; 59 | } 60 | }); 61 | } 62 | 63 | _isToMain(String url){ 64 | bool contain = false; 65 | for(final value in CATCH_URLS) { 66 | if(url?.endsWith(value)??false) { 67 | contain = true; 68 | break; 69 | } 70 | } 71 | 72 | return contain; 73 | } 74 | 75 | @override 76 | void dispose() { 77 | // TODO: implement dispose 78 | _onUrlChangeListener.cancel(); 79 | _onStateChangeListener.cancel(); 80 | flutterWebviewPlugin.dispose(); 81 | super.dispose(); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | 87 | String statusBarColor = widget.statusBarColor ?? 'ffffff'; 88 | // TODO: implement build 89 | Color backButtonColor; 90 | if(statusBarColor == 'ffffff') { 91 | backButtonColor = Colors.black; 92 | } else { 93 | backButtonColor = Colors.white; 94 | } 95 | return Scaffold( 96 | body: Column( 97 | children: [ 98 | _appBar(Color(int.parse('0xff'+statusBarColor)), backButtonColor), // 将字符串颜色转成16进制颜色 99 | Expanded( 100 | child: WebviewScaffold(url: widget.url, 101 | withZoom: true, 102 | hidden: true, 103 | initialChild: Container( 104 | color: Colors.white, 105 | child: Center( 106 | child: new Column( 107 | mainAxisAlignment: MainAxisAlignment.center, 108 | crossAxisAlignment: CrossAxisAlignment.center, 109 | children: [ 110 | new CircularProgressIndicator(), 111 | new Padding(padding: const EdgeInsets.only(top: 20.0), 112 | child: new Text( 113 | '加载中', 114 | style: new TextStyle(fontSize: 12.0, color: Colors.lightBlue), 115 | ), 116 | ) 117 | ], 118 | ), 119 | ), 120 | ), 121 | ), 122 | ) 123 | ], 124 | ), 125 | ); 126 | } 127 | 128 | _appBar(Color backgroundColor, Color backButtonColor) { 129 | if(widget.hideAppBar??false) { 130 | return Container( 131 | height: 30, 132 | color: backgroundColor, 133 | ); 134 | } 135 | return Container( 136 | color: backgroundColor, 137 | padding: EdgeInsets.fromLTRB(0, 40, 0, 10), 138 | child: FractionallySizedBox( 139 | widthFactor: 1, 140 | child: Stack( 141 | children: [ 142 | GestureDetector( 143 | onTap: (){ 144 | Navigator.pop(context); 145 | }, 146 | child: Container( 147 | margin: EdgeInsets.only(left: 10), 148 | child: Icon( 149 | Icons.arrow_back, 150 | color: backButtonColor, 151 | size: 26, 152 | ), 153 | ), 154 | ), 155 | Positioned( 156 | left: 0, 157 | right: 0, 158 | child: Center( 159 | child: Text( 160 | widget.title ?? "", 161 | style: TextStyle(color: backButtonColor, fontSize: 20), 162 | ), 163 | )) 164 | ], 165 | ), 166 | )); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "0.36.4" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.0.11" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.5.2" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.4.0" 32 | azlistview: 33 | dependency: "direct main" 34 | description: 35 | name: azlistview 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "0.1.2" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.0.5" 46 | build: 47 | dependency: transitive 48 | description: 49 | name: build 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.1.6" 53 | build_config: 54 | dependency: transitive 55 | description: 56 | name: build_config 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "0.4.1+1" 60 | build_daemon: 61 | dependency: transitive 62 | description: 63 | name: build_daemon 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.1.4" 67 | build_resolvers: 68 | dependency: transitive 69 | description: 70 | name: build_resolvers 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "1.2.1" 74 | build_runner: 75 | dependency: transitive 76 | description: 77 | name: build_runner 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "1.6.9" 81 | build_runner_core: 82 | dependency: transitive 83 | description: 84 | name: build_runner_core 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "3.1.1" 88 | built_collection: 89 | dependency: transitive 90 | description: 91 | name: built_collection 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "4.3.2" 95 | built_value: 96 | dependency: transitive 97 | description: 98 | name: built_value 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "7.1.0" 102 | cached_network_image: 103 | dependency: "direct main" 104 | description: 105 | name: cached_network_image 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "2.2.0+1" 109 | charcode: 110 | dependency: transitive 111 | description: 112 | name: charcode 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "1.1.2" 116 | checked_yaml: 117 | dependency: transitive 118 | description: 119 | name: checked_yaml 120 | url: "https://pub.flutter-io.cn" 121 | source: hosted 122 | version: "1.0.2" 123 | clock: 124 | dependency: transitive 125 | description: 126 | name: clock 127 | url: "https://pub.flutter-io.cn" 128 | source: hosted 129 | version: "1.0.1" 130 | code_builder: 131 | dependency: transitive 132 | description: 133 | name: code_builder 134 | url: "https://pub.flutter-io.cn" 135 | source: hosted 136 | version: "3.3.0" 137 | collection: 138 | dependency: transitive 139 | description: 140 | name: collection 141 | url: "https://pub.flutter-io.cn" 142 | source: hosted 143 | version: "1.14.11" 144 | convert: 145 | dependency: transitive 146 | description: 147 | name: convert 148 | url: "https://pub.flutter-io.cn" 149 | source: hosted 150 | version: "2.1.1" 151 | crypto: 152 | dependency: transitive 153 | description: 154 | name: crypto 155 | url: "https://pub.flutter-io.cn" 156 | source: hosted 157 | version: "2.1.3" 158 | csslib: 159 | dependency: transitive 160 | description: 161 | name: csslib 162 | url: "https://pub.flutter-io.cn" 163 | source: hosted 164 | version: "0.16.1" 165 | cupertino_icons: 166 | dependency: "direct main" 167 | description: 168 | name: cupertino_icons 169 | url: "https://pub.flutter-io.cn" 170 | source: hosted 171 | version: "0.1.3" 172 | dart_style: 173 | dependency: transitive 174 | description: 175 | name: dart_style 176 | url: "https://pub.flutter-io.cn" 177 | source: hosted 178 | version: "1.2.9" 179 | dio: 180 | dependency: "direct main" 181 | description: 182 | name: dio 183 | url: "https://pub.flutter-io.cn" 184 | source: hosted 185 | version: "3.0.9" 186 | event_bus: 187 | dependency: "direct main" 188 | description: 189 | name: event_bus 190 | url: "https://pub.flutter-io.cn" 191 | source: hosted 192 | version: "1.1.1" 193 | file: 194 | dependency: transitive 195 | description: 196 | name: file 197 | url: "https://pub.flutter-io.cn" 198 | source: hosted 199 | version: "5.1.0" 200 | fixnum: 201 | dependency: transitive 202 | description: 203 | name: fixnum 204 | url: "https://pub.flutter-io.cn" 205 | source: hosted 206 | version: "0.10.11" 207 | flutter: 208 | dependency: "direct main" 209 | description: flutter 210 | source: sdk 211 | version: "0.0.0" 212 | flutter_cache_manager: 213 | dependency: transitive 214 | description: 215 | name: flutter_cache_manager 216 | url: "https://pub.flutter-io.cn" 217 | source: hosted 218 | version: "1.2.2" 219 | flutter_page_indicator: 220 | dependency: transitive 221 | description: 222 | name: flutter_page_indicator 223 | url: "https://pub.flutter-io.cn" 224 | source: hosted 225 | version: "0.0.3" 226 | flutter_spinkit: 227 | dependency: "direct main" 228 | description: 229 | name: flutter_spinkit 230 | url: "https://pub.flutter-io.cn" 231 | source: hosted 232 | version: "4.1.2+1" 233 | flutter_staggered_grid_view: 234 | dependency: "direct main" 235 | description: 236 | name: flutter_staggered_grid_view 237 | url: "https://pub.flutter-io.cn" 238 | source: hosted 239 | version: "0.3.0" 240 | flutter_swiper: 241 | dependency: "direct main" 242 | description: 243 | name: flutter_swiper 244 | url: "https://pub.flutter-io.cn" 245 | source: hosted 246 | version: "1.1.6" 247 | flutter_test: 248 | dependency: "direct dev" 249 | description: flutter 250 | source: sdk 251 | version: "0.0.0" 252 | flutter_webview_plugin: 253 | dependency: "direct main" 254 | description: 255 | name: flutter_webview_plugin 256 | url: "https://pub.flutter-io.cn" 257 | source: hosted 258 | version: "0.3.11" 259 | front_end: 260 | dependency: transitive 261 | description: 262 | name: front_end 263 | url: "https://pub.flutter-io.cn" 264 | source: hosted 265 | version: "0.1.19" 266 | glob: 267 | dependency: transitive 268 | description: 269 | name: glob 270 | url: "https://pub.flutter-io.cn" 271 | source: hosted 272 | version: "1.2.0" 273 | graphs: 274 | dependency: transitive 275 | description: 276 | name: graphs 277 | url: "https://pub.flutter-io.cn" 278 | source: hosted 279 | version: "0.2.0" 280 | html: 281 | dependency: transitive 282 | description: 283 | name: html 284 | url: "https://pub.flutter-io.cn" 285 | source: hosted 286 | version: "0.14.0+3" 287 | http: 288 | dependency: "direct main" 289 | description: 290 | name: http 291 | url: "https://pub.flutter-io.cn" 292 | source: hosted 293 | version: "0.12.1" 294 | http_multi_server: 295 | dependency: transitive 296 | description: 297 | name: http_multi_server 298 | url: "https://pub.flutter-io.cn" 299 | source: hosted 300 | version: "2.2.0" 301 | http_parser: 302 | dependency: transitive 303 | description: 304 | name: http_parser 305 | url: "https://pub.flutter-io.cn" 306 | source: hosted 307 | version: "3.1.4" 308 | image: 309 | dependency: transitive 310 | description: 311 | name: image 312 | url: "https://pub.flutter-io.cn" 313 | source: hosted 314 | version: "2.1.4" 315 | intl: 316 | dependency: transitive 317 | description: 318 | name: intl 319 | url: "https://pub.flutter-io.cn" 320 | source: hosted 321 | version: "0.16.1" 322 | io: 323 | dependency: transitive 324 | description: 325 | name: io 326 | url: "https://pub.flutter-io.cn" 327 | source: hosted 328 | version: "0.3.4" 329 | js: 330 | dependency: transitive 331 | description: 332 | name: js 333 | url: "https://pub.flutter-io.cn" 334 | source: hosted 335 | version: "0.6.2" 336 | json_annotation: 337 | dependency: transitive 338 | description: 339 | name: json_annotation 340 | url: "https://pub.flutter-io.cn" 341 | source: hosted 342 | version: "2.3.0" 343 | json_model: 344 | dependency: "direct main" 345 | description: 346 | name: json_model 347 | url: "https://pub.flutter-io.cn" 348 | source: hosted 349 | version: "0.0.2" 350 | json_serializable: 351 | dependency: transitive 352 | description: 353 | name: json_serializable 354 | url: "https://pub.flutter-io.cn" 355 | source: hosted 356 | version: "2.3.0" 357 | kernel: 358 | dependency: transitive 359 | description: 360 | name: kernel 361 | url: "https://pub.flutter-io.cn" 362 | source: hosted 363 | version: "0.3.19" 364 | logging: 365 | dependency: transitive 366 | description: 367 | name: logging 368 | url: "https://pub.flutter-io.cn" 369 | source: hosted 370 | version: "0.11.4" 371 | lpinyin: 372 | dependency: "direct main" 373 | description: 374 | name: lpinyin 375 | url: "https://pub.flutter-io.cn" 376 | source: hosted 377 | version: "1.0.9" 378 | matcher: 379 | dependency: transitive 380 | description: 381 | name: matcher 382 | url: "https://pub.flutter-io.cn" 383 | source: hosted 384 | version: "0.12.6" 385 | meta: 386 | dependency: transitive 387 | description: 388 | name: meta 389 | url: "https://pub.flutter-io.cn" 390 | source: hosted 391 | version: "1.1.8" 392 | mime: 393 | dependency: transitive 394 | description: 395 | name: mime 396 | url: "https://pub.flutter-io.cn" 397 | source: hosted 398 | version: "0.9.6+3" 399 | node_interop: 400 | dependency: transitive 401 | description: 402 | name: node_interop 403 | url: "https://pub.flutter-io.cn" 404 | source: hosted 405 | version: "1.1.1" 406 | node_io: 407 | dependency: transitive 408 | description: 409 | name: node_io 410 | url: "https://pub.flutter-io.cn" 411 | source: hosted 412 | version: "1.1.1" 413 | package_config: 414 | dependency: transitive 415 | description: 416 | name: package_config 417 | url: "https://pub.flutter-io.cn" 418 | source: hosted 419 | version: "1.9.3" 420 | package_info: 421 | dependency: "direct main" 422 | description: 423 | name: package_info 424 | url: "https://pub.flutter-io.cn" 425 | source: hosted 426 | version: "0.4.0+18" 427 | package_resolver: 428 | dependency: transitive 429 | description: 430 | name: package_resolver 431 | url: "https://pub.flutter-io.cn" 432 | source: hosted 433 | version: "1.0.10" 434 | path: 435 | dependency: transitive 436 | description: 437 | name: path 438 | url: "https://pub.flutter-io.cn" 439 | source: hosted 440 | version: "1.6.4" 441 | path_provider: 442 | dependency: transitive 443 | description: 444 | name: path_provider 445 | url: "https://pub.flutter-io.cn" 446 | source: hosted 447 | version: "1.6.8" 448 | path_provider_macos: 449 | dependency: transitive 450 | description: 451 | name: path_provider_macos 452 | url: "https://pub.flutter-io.cn" 453 | source: hosted 454 | version: "0.0.4+2" 455 | path_provider_platform_interface: 456 | dependency: transitive 457 | description: 458 | name: path_provider_platform_interface 459 | url: "https://pub.flutter-io.cn" 460 | source: hosted 461 | version: "1.0.2" 462 | pedantic: 463 | dependency: transitive 464 | description: 465 | name: pedantic 466 | url: "https://pub.flutter-io.cn" 467 | source: hosted 468 | version: "1.8.0+1" 469 | petitparser: 470 | dependency: transitive 471 | description: 472 | name: petitparser 473 | url: "https://pub.flutter-io.cn" 474 | source: hosted 475 | version: "2.4.0" 476 | platform: 477 | dependency: transitive 478 | description: 479 | name: platform 480 | url: "https://pub.flutter-io.cn" 481 | source: hosted 482 | version: "2.2.1" 483 | plugin_platform_interface: 484 | dependency: transitive 485 | description: 486 | name: plugin_platform_interface 487 | url: "https://pub.flutter-io.cn" 488 | source: hosted 489 | version: "1.0.2" 490 | pool: 491 | dependency: transitive 492 | description: 493 | name: pool 494 | url: "https://pub.flutter-io.cn" 495 | source: hosted 496 | version: "1.4.0" 497 | provider: 498 | dependency: "direct main" 499 | description: 500 | name: provider 501 | url: "https://pub.flutter-io.cn" 502 | source: hosted 503 | version: "3.2.0" 504 | pub_semver: 505 | dependency: transitive 506 | description: 507 | name: pub_semver 508 | url: "https://pub.flutter-io.cn" 509 | source: hosted 510 | version: "1.4.4" 511 | pubspec_parse: 512 | dependency: transitive 513 | description: 514 | name: pubspec_parse 515 | url: "https://pub.flutter-io.cn" 516 | source: hosted 517 | version: "0.1.5" 518 | quiver: 519 | dependency: transitive 520 | description: 521 | name: quiver 522 | url: "https://pub.flutter-io.cn" 523 | source: hosted 524 | version: "2.0.5" 525 | rxdart: 526 | dependency: transitive 527 | description: 528 | name: rxdart 529 | url: "https://pub.flutter-io.cn" 530 | source: hosted 531 | version: "0.24.1" 532 | shelf: 533 | dependency: transitive 534 | description: 535 | name: shelf 536 | url: "https://pub.flutter-io.cn" 537 | source: hosted 538 | version: "0.7.7" 539 | shelf_web_socket: 540 | dependency: transitive 541 | description: 542 | name: shelf_web_socket 543 | url: "https://pub.flutter-io.cn" 544 | source: hosted 545 | version: "0.2.3" 546 | sky_engine: 547 | dependency: transitive 548 | description: flutter 549 | source: sdk 550 | version: "0.0.99" 551 | source_gen: 552 | dependency: transitive 553 | description: 554 | name: source_gen 555 | url: "https://pub.flutter-io.cn" 556 | source: hosted 557 | version: "0.9.4+4" 558 | source_span: 559 | dependency: transitive 560 | description: 561 | name: source_span 562 | url: "https://pub.flutter-io.cn" 563 | source: hosted 564 | version: "1.5.5" 565 | sqflite: 566 | dependency: transitive 567 | description: 568 | name: sqflite 569 | url: "https://pub.flutter-io.cn" 570 | source: hosted 571 | version: "1.3.0+1" 572 | sqflite_common: 573 | dependency: transitive 574 | description: 575 | name: sqflite_common 576 | url: "https://pub.flutter-io.cn" 577 | source: hosted 578 | version: "1.0.1" 579 | stack_trace: 580 | dependency: transitive 581 | description: 582 | name: stack_trace 583 | url: "https://pub.flutter-io.cn" 584 | source: hosted 585 | version: "1.9.3" 586 | stream_channel: 587 | dependency: transitive 588 | description: 589 | name: stream_channel 590 | url: "https://pub.flutter-io.cn" 591 | source: hosted 592 | version: "2.0.0" 593 | stream_transform: 594 | dependency: transitive 595 | description: 596 | name: stream_transform 597 | url: "https://pub.flutter-io.cn" 598 | source: hosted 599 | version: "0.0.20" 600 | string_scanner: 601 | dependency: transitive 602 | description: 603 | name: string_scanner 604 | url: "https://pub.flutter-io.cn" 605 | source: hosted 606 | version: "1.0.5" 607 | synchronized: 608 | dependency: transitive 609 | description: 610 | name: synchronized 611 | url: "https://pub.flutter-io.cn" 612 | source: hosted 613 | version: "2.2.0" 614 | term_glyph: 615 | dependency: transitive 616 | description: 617 | name: term_glyph 618 | url: "https://pub.flutter-io.cn" 619 | source: hosted 620 | version: "1.1.0" 621 | test_api: 622 | dependency: transitive 623 | description: 624 | name: test_api 625 | url: "https://pub.flutter-io.cn" 626 | source: hosted 627 | version: "0.2.11" 628 | timing: 629 | dependency: transitive 630 | description: 631 | name: timing 632 | url: "https://pub.flutter-io.cn" 633 | source: hosted 634 | version: "0.1.1+2" 635 | transformer_page_view: 636 | dependency: transitive 637 | description: 638 | name: transformer_page_view 639 | url: "https://pub.flutter-io.cn" 640 | source: hosted 641 | version: "0.1.6" 642 | typed_data: 643 | dependency: transitive 644 | description: 645 | name: typed_data 646 | url: "https://pub.flutter-io.cn" 647 | source: hosted 648 | version: "1.1.6" 649 | uuid: 650 | dependency: transitive 651 | description: 652 | name: uuid 653 | url: "https://pub.flutter-io.cn" 654 | source: hosted 655 | version: "2.0.4" 656 | vector_math: 657 | dependency: transitive 658 | description: 659 | name: vector_math 660 | url: "https://pub.flutter-io.cn" 661 | source: hosted 662 | version: "2.0.8" 663 | watcher: 664 | dependency: transitive 665 | description: 666 | name: watcher 667 | url: "https://pub.flutter-io.cn" 668 | source: hosted 669 | version: "0.9.7+15" 670 | web_socket_channel: 671 | dependency: transitive 672 | description: 673 | name: web_socket_channel 674 | url: "https://pub.flutter-io.cn" 675 | source: hosted 676 | version: "1.1.0" 677 | xml: 678 | dependency: transitive 679 | description: 680 | name: xml 681 | url: "https://pub.flutter-io.cn" 682 | source: hosted 683 | version: "3.5.0" 684 | yaml: 685 | dependency: transitive 686 | description: 687 | name: yaml 688 | url: "https://pub.flutter-io.cn" 689 | source: hosted 690 | version: "2.2.1" 691 | sdks: 692 | dart: ">=2.7.0 <3.0.0" 693 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 694 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_wtrip 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | flutter_swiper: ^1.1.6 27 | http: ^0.12.0+4 28 | flutter_webview_plugin: ^0.3.10+1 29 | flutter_staggered_grid_view: ^0.3.0 30 | azlistview: ^0.1.2 31 | lpinyin: ^1.0.8 32 | package_info: ^0.4.0+18 33 | dio: ^3.0.3 34 | cached_network_image: ^2.2.0+1 35 | event_bus: ^1.1.1 36 | flutter_spinkit: ^4.1.2+1 37 | json_model: ^0.0.2 38 | provider: ^3.0.0 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | 43 | 44 | # For information on the generic Dart part of this file, see the 45 | # following page: https://dart.dev/tools/pub/pubspec 46 | 47 | # The following section is specific to Flutter. 48 | flutter: 49 | 50 | # The following line ensures that the Material Icons font is 51 | # included with your application, so that you can use the icons in 52 | # the material Icons class. 53 | uses-material-design: true 54 | 55 | # To add assets to your application, add an assets section, like this: 56 | assets: 57 | - images/launch_image.png 58 | - assets/data/cities.json 59 | 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.dev/assets-and-images/#resolution-aware. 62 | 63 | # For details regarding adding assets from package dependencies, see 64 | # https://flutter.dev/assets-and-images/#from-packages 65 | 66 | # To add custom fonts to your application, add a fonts section here, 67 | # in this "flutter" section. Each entry in this list should have a 68 | # "family" key with the font family name, and a "fonts" key with a 69 | # list giving the asset and other descriptors for the font. For 70 | # example: 71 | # fonts: 72 | # - family: Schyler 73 | # fonts: 74 | # - asset: fonts/Schyler-Regular.ttf 75 | # - asset: fonts/Schyler-Italic.ttf 76 | # style: italic 77 | # - family: Trajan Pro 78 | # fonts: 79 | # - asset: fonts/TrajanPro.ttf 80 | # - asset: fonts/TrajanPro_Bold.ttf 81 | # weight: 700 82 | # 83 | # For details regarding fonts from package dependencies, 84 | # see https://flutter.dev/custom-fonts/#from-packages -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_wtrip/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------