├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── README.md ├── android ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── easy_market │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ ├── launch_background.xml │ │ │ └── open.jpg │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ ├── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── key.properties └── settings.gradle ├── assets └── images │ ├── address.png │ ├── collect.png │ ├── good.png │ ├── hongbao.png │ ├── huiyuan.png │ ├── ic_tab_group_active.png │ ├── ic_tab_group_normal.png │ ├── ic_tab_home_active.png │ ├── ic_tab_home_normal.png │ ├── ic_tab_profile_active.png │ ├── ic_tab_profile_normal.png │ ├── ic_tab_shiji_active.png │ ├── ic_tab_shiji_normal.png │ ├── ic_tab_subject_active.png │ ├── ic_tab_subject_normal.png │ ├── issure.png │ ├── kefu.png │ ├── logout.png │ ├── more.png │ ├── openAd.jpg │ ├── oppen.png │ ├── order.png │ ├── safe.png │ ├── tab_cart_active.png │ ├── tab_cart_default.png │ ├── tab_copy_active.png │ ├── tab_copy_default.png │ ├── tab_home_active.png │ ├── tab_home_default.png │ ├── tab_mine_active.png │ ├── tab_mine_default.png │ ├── tab_sort_active.png │ ├── tab_sort_default.png │ ├── timg.jpg │ └── week.png ├── imges ├── ad.png ├── brand.png ├── catalog.png ├── goodsDetail.png ├── goodsSize.png ├── home.png ├── login.png ├── mine.png ├── qrCode.png ├── sort.png ├── topic.png └── topicDetail.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── 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 │ └── main.m ├── lib ├── Advertisement │ └── openAd.dart ├── api │ └── index.dart ├── component │ ├── SliverCustomHeader.dart │ ├── bottom_sheet.dart │ ├── count.dart │ ├── linearBar.dart │ ├── swiper.dart │ ├── tab.dart │ ├── tabAppBar.dart │ └── verticalTab.dart ├── main.dart ├── model │ └── index.dart ├── page │ ├── app.dart │ ├── cart │ │ └── index.dart │ ├── home │ │ ├── index.dart │ │ └── topic.dart │ ├── index.dart │ ├── mine │ │ └── index.dart │ ├── sort │ │ └── index.dart │ ├── topic │ │ └── index.dart │ └── wrapper.dart ├── router │ ├── brand │ │ └── index.dart │ ├── catalog │ │ ├── catalogGoods.dart │ │ └── index.dart │ ├── goodsDetail │ │ └── index.dart │ ├── index.dart │ ├── login │ │ └── index.dart │ ├── moreComment │ │ └── index.dart │ ├── noFound.dart │ ├── search │ │ └── index.dart │ ├── tipicDetail │ │ └── index.dart │ └── writeComment │ │ └── index.dart └── utils │ ├── cache.dart │ ├── help.dart │ ├── http.dart │ └── rem.dart ├── pubspec.lock └── pubspec.yaml /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_webview_plugin","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_webview_plugin-0.3.7/","dependencies":[]},{"name":"path_provider","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.11/","dependencies":[]},{"name":"shared_preferences","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.4.3/","dependencies":[]},{"name":"sqflite","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.1/","dependencies":[]}],"android":[{"name":"flutter_webview_plugin","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_webview_plugin-0.3.7/","dependencies":[]},{"name":"path_provider","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.11/","dependencies":[]},{"name":"shared_preferences","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.4.3/","dependencies":[]},{"name":"sqflite","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.1/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4+3/","dependencies":[]},{"name":"sqflite","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.3.1/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/peroluo/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_linux-0.0.1+2/","dependencies":[]}],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_webview_plugin","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"shared_preferences","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-08-03 17:03:07.842902","version":"1.17.5"} -------------------------------------------------------------------------------- /.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 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | -------------------------------------------------------------------------------- /.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyMarketFlutter 2 | 3 | > 1、EasyMarketFlutter 是仿网易严选 设计的 App(商城类)。项目是基于 Flutter 框架的混合开发(Android 与 IOS) 4 | > 5 | > 2、EasyMarketFlutter 主要技术栈:provider(全局数据管理)、dio(数据请求)、shared_preferences(缓存)。 6 | > 7 | > 3、本项目主要是以学习 Flutter 框架为主,主要可学内容:UI 设计、组件封装、解决 Flutter 中遇到的坑。可能项目中有许多缺陷,希望吐槽,来不断完善。 8 | 9 | ## 扫码体验 Android 版(请在浏览器内扫码!) 10 | 11 |
12 | 13 |
14 | 15 | ## 预览 16 | 17 | | ![](./imges/ad.png) | ![](./imges/goodsDetail.png) | ![](./imges/goodsSize.png) | ![](./imges/sort.png) | 18 | | ---------------------------- | ---------------------------- | -------------------------- | ---------------------- | 19 | | ![](./imges/mine.png) | ![](./imges/login.png) | ![](./imges/catalog.png) | ![](./imges/brand.png) | 20 | | ![](./imges/topicDetail.png) | ![](./imges/home.png) | ![](./imges/topic.png) | | 21 | 22 | ## 已实现 23 | 24 | - Provider+shared_preferences 实现数据状态管理、缓存。 25 | - App 的启动页、广告页。 26 | - Rem 适配方案。 27 | - Dio 的二次封装,API 模块化。 28 | - 封装侧边栏 Tab 切换组件。 29 | - 封装 Appbar 与 Tab 的 AppbarTab 组件。 30 | - 封装滚动渐变的 Appbar 组件。 31 | - 下拉刷新与上拉加载更多。 32 | - 路由模块管理。 33 | 34 | ## 还需要做的事 35 | 36 | 1. 此项目还在开发中,后续会把业务补充完整。(商品、专题、制造商、购物车、用户信息管理)页面的完善。 37 | 2. 项目优化,从 UI 到性能分析,我也是 Flutter 的初学者,后续如果有学到好的方案,需迭代。 38 | 3. 尝试将部分模块以 WebView+H5 进行混合开发。 39 | 4. 后续会补充我在开发中遇到的问题,进行补充、和记录。 40 | 41 | ## 需注意的东西 42 | 43 | - 目前只提供了 android 的下载体验,IOS 请自行下载代码体验。 44 | - 在开发环境的 debug 可能会稍微卡顿,属正常现象,良好的体验请进行打 release 包。 45 | 46 | ## 友情链接 47 | 48 | 1. [Flutter 中文网](https://flutterchina.club/) 49 | 2. [Dart 从入门到放弃](http://dart.goodev.org/) 50 | 3. [Flutter 从入门到放弃](https://book.flutterchina.club/) 51 | 4. [Flutter-go](https://github.com/alibaba/flutter-go) 52 | 5. [Flutter 路由管理](https://github.com/theyakka/fluro) 53 | 6. [Flutter 很全的 Api 说明](https://github.com/yang7229693/flutter-study) 54 | 7. [我写的项目](https://github.com/Peroluo/easyMarketFlutter) 55 | 8. [常用的一些包](https://www.cnblogs.com/yangyxd/p/9232308.html) 56 | 9. [阿里巴巴 Flutter 代码规范](https://github.com/alibaba/flutter-go/blob/master/Flutter_Go%20%E4%BB%A3%E7%A0%81%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83.md) 57 | 58 | ## 最后 59 | 60 | - 如果 EasyMarketFlutter 对你有帮助,留下你的 Star 或者 fork,你的支持是我不断更新的动力! 61 | - 欢迎你们的 Issues,希望 Flutter 越来越好,大家一起学习!Love Coding! 62 | - Thanks! 63 | 64 | ## 关于我 65 | 66 | Name: pero 罗 67 | 68 | QQ: 1025558554 69 | 70 | Email:[1025558554@qq.com](mailto:1025558554@qq.com) 71 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | def keystorePropertiesFile = rootProject.file("key.properties") 27 | def keystoreProperties = new Properties() 28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 29 | android { 30 | compileSdkVersion 28 31 | 32 | lintOptions { 33 | disable 'InvalidPackage' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "com.example.easy_market" 39 | minSdkVersion 16 40 | targetSdkVersion 28 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 44 | } 45 | signingConfigs { 46 | release { 47 | keyAlias keystoreProperties['keyAlias'] 48 | keyPassword keystoreProperties['keyPassword'] 49 | storeFile file("D:/Android_keystore/key.jks") 50 | storePassword keystoreProperties['storePassword'] 51 | } 52 | } 53 | buildTypes { 54 | release { 55 | signingConfig signingConfigs.release 56 | 57 | // minifyEnabled true 58 | // useProguard true 59 | 60 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 61 | // TODO: Add your own signing config for the release build. 62 | // Signing with the debug keys for now, so `flutter run --release` works. 63 | // signingConfig signingConfigs.debug 64 | } 65 | } 66 | } 67 | 68 | flutter { 69 | source '../..' 70 | } 71 | 72 | dependencies { 73 | testImplementation 'junit:junit:4.12' 74 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 75 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 76 | } 77 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/easy_market/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.easy_market; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/open.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/drawable/open.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=lgx112535 2 | keyPassword=lgx112535 3 | keyAlias=key 4 | storeFile=D:\Android_keystore/key.jks -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /assets/images/address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/address.png -------------------------------------------------------------------------------- /assets/images/collect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/collect.png -------------------------------------------------------------------------------- /assets/images/good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/good.png -------------------------------------------------------------------------------- /assets/images/hongbao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/hongbao.png -------------------------------------------------------------------------------- /assets/images/huiyuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/huiyuan.png -------------------------------------------------------------------------------- /assets/images/ic_tab_group_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_group_active.png -------------------------------------------------------------------------------- /assets/images/ic_tab_group_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_group_normal.png -------------------------------------------------------------------------------- /assets/images/ic_tab_home_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_home_active.png -------------------------------------------------------------------------------- /assets/images/ic_tab_home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_home_normal.png -------------------------------------------------------------------------------- /assets/images/ic_tab_profile_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_profile_active.png -------------------------------------------------------------------------------- /assets/images/ic_tab_profile_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_profile_normal.png -------------------------------------------------------------------------------- /assets/images/ic_tab_shiji_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_shiji_active.png -------------------------------------------------------------------------------- /assets/images/ic_tab_shiji_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_shiji_normal.png -------------------------------------------------------------------------------- /assets/images/ic_tab_subject_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_subject_active.png -------------------------------------------------------------------------------- /assets/images/ic_tab_subject_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/ic_tab_subject_normal.png -------------------------------------------------------------------------------- /assets/images/issure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/issure.png -------------------------------------------------------------------------------- /assets/images/kefu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/kefu.png -------------------------------------------------------------------------------- /assets/images/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/logout.png -------------------------------------------------------------------------------- /assets/images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/more.png -------------------------------------------------------------------------------- /assets/images/openAd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/openAd.jpg -------------------------------------------------------------------------------- /assets/images/oppen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/oppen.png -------------------------------------------------------------------------------- /assets/images/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/order.png -------------------------------------------------------------------------------- /assets/images/safe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/safe.png -------------------------------------------------------------------------------- /assets/images/tab_cart_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_cart_active.png -------------------------------------------------------------------------------- /assets/images/tab_cart_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_cart_default.png -------------------------------------------------------------------------------- /assets/images/tab_copy_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_copy_active.png -------------------------------------------------------------------------------- /assets/images/tab_copy_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_copy_default.png -------------------------------------------------------------------------------- /assets/images/tab_home_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_home_active.png -------------------------------------------------------------------------------- /assets/images/tab_home_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_home_default.png -------------------------------------------------------------------------------- /assets/images/tab_mine_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_mine_active.png -------------------------------------------------------------------------------- /assets/images/tab_mine_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_mine_default.png -------------------------------------------------------------------------------- /assets/images/tab_sort_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_sort_active.png -------------------------------------------------------------------------------- /assets/images/tab_sort_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/tab_sort_default.png -------------------------------------------------------------------------------- /assets/images/timg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/timg.jpg -------------------------------------------------------------------------------- /assets/images/week.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/assets/images/week.png -------------------------------------------------------------------------------- /imges/ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/ad.png -------------------------------------------------------------------------------- /imges/brand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/brand.png -------------------------------------------------------------------------------- /imges/catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/catalog.png -------------------------------------------------------------------------------- /imges/goodsDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/goodsDetail.png -------------------------------------------------------------------------------- /imges/goodsSize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/goodsSize.png -------------------------------------------------------------------------------- /imges/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/home.png -------------------------------------------------------------------------------- /imges/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/login.png -------------------------------------------------------------------------------- /imges/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/mine.png -------------------------------------------------------------------------------- /imges/qrCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/qrCode.png -------------------------------------------------------------------------------- /imges/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/sort.png -------------------------------------------------------------------------------- /imges/topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/topic.png -------------------------------------------------------------------------------- /imges/topicDetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/imges/topicDetail.png -------------------------------------------------------------------------------- /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/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/peroluo/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/peroluo/github/easyMarketFlutter" 5 | export "FLUTTER_TARGET=/Users/peroluo/github/easyMarketFlutter/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter" 9 | export "FLUTTER_FRAMEWORK_DIR=/Users/peroluo/flutter/bin/cache/artifacts/engine/ios" 10 | export "FLUTTER_BUILD_NAME=1.0.0" 11 | export "FLUTTER_BUILD_NUMBER=1" 12 | export "TRACK_WIDGET_CREATION=true" 13 | export "DART_DEFINES=flutter.inspector.structuredErrors=true" 14 | -------------------------------------------------------------------------------- /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 | # Flutter Pod 37 | 38 | copied_flutter_dir = File.join(__dir__, 'Flutter') 39 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 40 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 41 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 42 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 43 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 44 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 45 | 46 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 47 | unless File.exist?(generated_xcode_build_settings_path) 48 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 49 | end 50 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 51 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 52 | 53 | unless File.exist?(copied_framework_path) 54 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 55 | end 56 | unless File.exist?(copied_podspec_path) 57 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 58 | end 59 | end 60 | 61 | # Keep pod path relative so it can be checked into Podfile.lock. 62 | pod 'Flutter', :path => 'Flutter' 63 | 64 | # Plugin Pods 65 | 66 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 67 | # referring to absolute paths on developers' machines. 68 | system('rm -rf .symlinks') 69 | system('mkdir -p .symlinks/plugins') 70 | plugin_pods = parse_KV_file('../.flutter-plugins') 71 | plugin_pods.each do |name, path| 72 | symlink = File.join('.symlinks', 'plugins', name) 73 | File.symlink(path, symlink) 74 | pod name, :path => File.join(symlink, 'ios') 75 | end 76 | end 77 | 78 | post_install do |installer| 79 | installer.pods_project.targets.each do |target| 80 | target.build_configurations.each do |config| 81 | config.build_settings['ENABLE_BITCODE'] = 'NO' 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /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 | - path_provider (0.0.1): 9 | - Flutter 10 | - path_provider_linux (0.0.1): 11 | - Flutter 12 | - path_provider_macos (0.0.1): 13 | - Flutter 14 | - shared_preferences (0.0.1): 15 | - Flutter 16 | - sqflite (0.0.1): 17 | - Flutter 18 | - FMDB (~> 2.7.2) 19 | 20 | DEPENDENCIES: 21 | - Flutter (from `Flutter`) 22 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 23 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 24 | - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) 25 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 26 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 27 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 28 | 29 | SPEC REPOS: 30 | trunk: 31 | - FMDB 32 | 33 | EXTERNAL SOURCES: 34 | Flutter: 35 | :path: Flutter 36 | flutter_webview_plugin: 37 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 38 | path_provider: 39 | :path: ".symlinks/plugins/path_provider/ios" 40 | path_provider_linux: 41 | :path: ".symlinks/plugins/path_provider_linux/ios" 42 | path_provider_macos: 43 | :path: ".symlinks/plugins/path_provider_macos/ios" 44 | shared_preferences: 45 | :path: ".symlinks/plugins/shared_preferences/ios" 46 | sqflite: 47 | :path: ".symlinks/plugins/sqflite/ios" 48 | 49 | SPEC CHECKSUMS: 50 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 51 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 52 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 53 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 54 | path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 55 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 56 | shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 57 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 58 | 59 | PODFILE CHECKSUM: f32fb4e7c14f8b3ca19a369d7be425dd9241af27 60 | 61 | COCOAPODS: 1.9.3 62 | -------------------------------------------------------------------------------- /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.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoguoxiong/easyMarketFlutter/770ba75f0b0b2279f0505ebdde6a5e179e1e0b21/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 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | NSAllowsArbitraryLoadsInWebContent 10 | 11 | 12 | CFBundleDevelopmentRegion 13 | $(DEVELOPMENT_LANGUAGE) 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | easy_market 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleSignature 27 | ???? 28 | CFBundleVersion 29 | $(FLUTTER_BUILD_NUMBER) 30 | LSRequiresIPhoneOS 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/Advertisement/openAd.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 广告页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | class OpenAd extends StatelessWidget { 9 | OpenAd(this.time); 10 | final int time; 11 | Widget build(BuildContext context) { 12 | return Container( 13 | width: double.infinity, 14 | height: double.infinity, 15 | decoration: BoxDecoration( 16 | image: DecorationImage( 17 | image: AssetImage('assets/images/timg.jpg'), 18 | fit: BoxFit.cover, 19 | ), 20 | ), 21 | child: Stack( 22 | children: [ 23 | Positioned( 24 | bottom: 30, 25 | right: 10, 26 | child: Container( 27 | width: 60, 28 | height: 30, 29 | decoration: BoxDecoration( 30 | color: Color.fromARGB(100, 59, 70, 88), 31 | borderRadius: new BorderRadius.all(new Radius.circular(15.0)), 32 | ), 33 | child: Center( 34 | child: Text( 35 | '$time S', 36 | style: TextStyle(color: Colors.white), 37 | ), 38 | ), 39 | ), 40 | ) 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/api/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: API管理 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'package:easy_market/utils/http.dart'; 7 | 8 | var http = new HttpUtils(); 9 | 10 | class Api { 11 | // 获取首页数据 12 | static Future getHomeData() async { 13 | return await http.get('/'); 14 | } 15 | 16 | // 获取专题页数据 17 | static Future getTopicData({int page, int size}) async { 18 | return await http.get('/topic/list', {'page': page, 'size': size}); 19 | } 20 | 21 | // 获取分类页tabList 22 | static Future getSortTabs() async { 23 | return await http.get('/catalog/index'); 24 | } 25 | 26 | // 获取所有商品的数量 27 | static Future getGoodsCount() async { 28 | return await http.get('/goods/count'); 29 | } 30 | 31 | // 获取某分类的相关信息 32 | static Future getCategoryMsg({int id}) async { 33 | return await http.get('/catalog/current', {'id': id}); 34 | } 35 | 36 | // 通过手机号码登录 37 | static Future loginByMobile({String mobile, String password}) async { 38 | return await http 39 | .post('/auth/loginByMobile', {'mobile': mobile, 'password': password}); 40 | } 41 | 42 | // 获取兄弟分类 43 | static Future getBrotherCatalog({int id}) async { 44 | return await http.get('/goods/category', {'id': id}); 45 | } 46 | 47 | // 某分类的商品 48 | static Future getGoods({ 49 | int page, 50 | int size, 51 | int categoryId, 52 | }) async { 53 | return await http.get( 54 | '/goods/list', {'page': page, 'size': size, 'categoryId': categoryId}); 55 | } 56 | 57 | // 某制造商的相关信息 58 | static Future getBrandMsg({int id}) async { 59 | return await http.get('/brand/detail', {'id': id}); 60 | } 61 | 62 | // 某制造商下的商品 63 | static Future getBrandGoods({ 64 | int page, 65 | int size, 66 | int brandId, 67 | }) async { 68 | return await http 69 | .get('/goods/list', {'page': page, 'size': size, 'brandId': brandId}); 70 | } 71 | 72 | // 某专题的相关信息 73 | static Future getTopicMsg({int id}) async { 74 | return await http.get('/topic/detail', {'id': id}); 75 | } 76 | 77 | // 获取某专题的评论 78 | static Future getTopicComment({ 79 | int valueId, 80 | int typeId, 81 | int page, 82 | int size, 83 | }) async { 84 | return await http.get('/comment/list', 85 | {'valueId': valueId, 'typeId': typeId, 'page': page, 'size': size}); 86 | } 87 | 88 | // 某专题相关专题 89 | static Future getRelatedTopic({int id}) async { 90 | return await http.get('/topic/related', {'id': id}); 91 | } 92 | 93 | // 商品信息 94 | static Future getGoodsMSG({int id, String token}) async { 95 | return await http.getToken('/goods/detail', token, {'id': id}); 96 | } 97 | 98 | // 获取购物信息 99 | static Future getCartMsg({String token, int id}) async { 100 | return await http.getToken('/cart/index', token, {'id': id}); 101 | } 102 | 103 | // 添加到购物车 104 | static Future postAddCart({String token, int id}) async { 105 | return await http 106 | .postToken('/collect/addordelete', token, {'valueId': id, 'typeId': 0}); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/component/SliverCustomHeader.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate { 4 | final double collapsedHeight; 5 | final double expandedHeight; 6 | final double paddingTop; 7 | final Widget child; 8 | final String title; 9 | 10 | SliverCustomHeaderDelegate({ 11 | this.collapsedHeight, 12 | this.expandedHeight, 13 | this.paddingTop, 14 | this.child, 15 | this.title, 16 | }); 17 | 18 | @override 19 | double get minExtent => this.collapsedHeight + this.paddingTop; 20 | 21 | @override 22 | double get maxExtent => this.expandedHeight; 23 | 24 | @override 25 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { 26 | return true; 27 | } 28 | 29 | Color makeStickyHeaderBgColor(shrinkOffset) { 30 | final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255) 31 | .clamp(0, 255) 32 | .toInt(); 33 | return Color.fromARGB(alpha, 255, 255, 255); 34 | } 35 | 36 | Color makeStickyHeaderTextColor(shrinkOffset, isIcon) { 37 | if (shrinkOffset <= 50) { 38 | return isIcon ? Colors.white : Colors.transparent; 39 | } else { 40 | final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255) 41 | .clamp(0, 255) 42 | .toInt(); 43 | return Color.fromARGB(alpha, 0, 0, 0); 44 | } 45 | } 46 | 47 | @override 48 | Widget build( 49 | BuildContext context, double shrinkOffset, bool overlapsContent) { 50 | return Container( 51 | height: this.maxExtent, 52 | width: MediaQuery.of(context).size.width, 53 | child: Stack( 54 | fit: StackFit.expand, 55 | children: [ 56 | // 背景图 57 | this.child, 58 | // 收起头部 59 | Positioned( 60 | left: 0, 61 | right: 0, 62 | top: 0, 63 | child: Container( 64 | color: this.makeStickyHeaderBgColor(shrinkOffset), // 背景颜色 65 | child: SafeArea( 66 | bottom: false, 67 | child: Container( 68 | height: this.collapsedHeight, 69 | child: Row( 70 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 71 | children: [ 72 | IconButton( 73 | icon: Icon( 74 | Icons.keyboard_arrow_left, 75 | color: this.makeStickyHeaderTextColor( 76 | shrinkOffset, true), // 返回图标颜色 77 | ), 78 | onPressed: () => Navigator.pop(context), 79 | ), 80 | Text( 81 | this.title, 82 | style: TextStyle( 83 | fontSize: 18, 84 | color: this.makeStickyHeaderTextColor( 85 | shrinkOffset, false), // 标题颜色 86 | ), 87 | ), 88 | IconButton( 89 | icon: Icon( 90 | Icons.share, 91 | color: Colors.transparent, // 分享图标颜色 92 | ), 93 | onPressed: () {}, 94 | ), 95 | ], 96 | ), 97 | ), 98 | ), 99 | ), 100 | ), 101 | ], 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/component/bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:flutter/scheduler.dart'; 9 | import 'package:flutter/widgets.dart'; 10 | 11 | import 'package:flutter/material.dart'; 12 | 13 | const Duration _bottomSheetDuration = Duration(milliseconds: 200); 14 | const double _minFlingVelocity = 700.0; 15 | const double _closeProgressThreshold = 0.5; 16 | 17 | /// A material design bottom sheet. 18 | /// 19 | /// There are two kinds of bottom sheets in material design: 20 | /// 21 | /// * _Persistent_. A persistent bottom sheet shows information that 22 | /// supplements the primary content of the app. A persistent bottom sheet 23 | /// remains visible even when the user interacts with other parts of the app. 24 | /// Persistent bottom sheets can be created and displayed with the 25 | /// [ScaffoldState.showBottomSheet] function or by specifying the 26 | /// [Scaffold.bottomSheet] constructor parameter. 27 | /// 28 | /// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and 29 | /// prevents the user from interacting with the rest of the app. Modal bottom 30 | /// sheets can be created and displayed with the [showModalBottomSheet] 31 | /// function. 32 | /// 33 | /// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to 34 | /// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or 35 | /// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet]. 36 | /// 37 | /// See also: 38 | /// 39 | /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing 40 | /// non-modal "persistent" bottom sheets. 41 | /// * [showModalBottomSheet], which can be used to display a modal bottom 42 | /// sheet. 43 | /// * 44 | class BottomSheet extends StatefulWidget { 45 | /// Creates a bottom sheet. 46 | /// 47 | /// Typically, bottom sheets are created implicitly by 48 | /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by 49 | /// [showModalBottomSheet], for modal bottom sheets. 50 | const BottomSheet({ 51 | Key key, 52 | this.animationController, 53 | this.enableDrag = true, 54 | this.backgroundColor, 55 | this.elevation, 56 | this.shape, 57 | @required this.onClosing, 58 | @required this.builder, 59 | }) : assert(enableDrag != null), 60 | assert(onClosing != null), 61 | assert(builder != null), 62 | assert(elevation == null || elevation >= 0.0), 63 | super(key: key); 64 | 65 | /// The animation controller that controls the bottom sheet's entrance and 66 | /// exit animations. 67 | /// 68 | /// The BottomSheet widget will manipulate the position of this animation, it 69 | /// is not just a passive observer. 70 | final AnimationController animationController; 71 | 72 | /// Called when the bottom sheet begins to close. 73 | /// 74 | /// A bottom sheet might be prevented from closing (e.g., by user 75 | /// interaction) even after this callback is called. For this reason, this 76 | /// callback might be call multiple times for a given bottom sheet. 77 | final VoidCallback onClosing; 78 | 79 | /// A builder for the contents of the sheet. 80 | /// 81 | /// The bottom sheet will wrap the widget produced by this builder in a 82 | /// [Material] widget. 83 | final WidgetBuilder builder; 84 | 85 | /// If true, the bottom sheet can be dragged up and down and dismissed by 86 | /// swiping downards. 87 | /// 88 | /// Default is true. 89 | final bool enableDrag; 90 | 91 | /// The bottom sheet's background color. 92 | /// 93 | /// Defines the bottom sheet's [Material.color]. 94 | /// 95 | /// Defaults to null and falls back to [Material]'s default. 96 | final Color backgroundColor; 97 | 98 | /// The z-coordinate at which to place this material relative to its parent. 99 | /// 100 | /// This controls the size of the shadow below the material. 101 | /// 102 | /// Defaults to 0. The value is non-negative. 103 | final double elevation; 104 | 105 | /// The shape of the bottom sheet. 106 | /// 107 | /// Defines the bottom sheet's [Material.shape]. 108 | /// 109 | /// Defaults to null and falls back to [Material]'s default. 110 | final ShapeBorder shape; 111 | 112 | @override 113 | _BottomSheetState createState() => _BottomSheetState(); 114 | 115 | /// Creates an [AnimationController] suitable for a 116 | /// [BottomSheet.animationController]. 117 | /// 118 | /// This API available as a convenience for a Material compliant bottom sheet 119 | /// animation. If alternative animation durations are required, a different 120 | /// animation controller could be provided. 121 | static AnimationController createAnimationController(TickerProvider vsync) { 122 | return AnimationController( 123 | duration: _bottomSheetDuration, 124 | debugLabel: 'BottomSheet', 125 | vsync: vsync, 126 | ); 127 | } 128 | } 129 | 130 | class _BottomSheetState extends State { 131 | final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child'); 132 | 133 | double get _childHeight { 134 | final RenderBox renderBox = _childKey.currentContext.findRenderObject(); 135 | return renderBox.size.height; 136 | } 137 | 138 | bool get _dismissUnderway => 139 | widget.animationController.status == AnimationStatus.reverse; 140 | 141 | void _handleDragUpdate(DragUpdateDetails details) { 142 | assert(widget.enableDrag); 143 | if (_dismissUnderway) return; 144 | widget.animationController.value -= 145 | details.primaryDelta / (_childHeight ?? details.primaryDelta); 146 | } 147 | 148 | void _handleDragEnd(DragEndDetails details) { 149 | assert(widget.enableDrag); 150 | if (_dismissUnderway) return; 151 | if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) { 152 | final double flingVelocity = 153 | -details.velocity.pixelsPerSecond.dy / _childHeight; 154 | if (widget.animationController.value > 0.0) { 155 | widget.animationController.fling(velocity: flingVelocity); 156 | } 157 | if (flingVelocity < 0.0) { 158 | widget.onClosing(); 159 | } 160 | } else if (widget.animationController.value < _closeProgressThreshold) { 161 | if (widget.animationController.value > 0.0) 162 | widget.animationController.fling(velocity: -1.0); 163 | widget.onClosing(); 164 | } else { 165 | widget.animationController.forward(); 166 | } 167 | } 168 | 169 | bool extentChanged(DraggableScrollableNotification notification) { 170 | if (notification.extent == notification.minExtent) { 171 | widget.onClosing(); 172 | } 173 | return false; 174 | } 175 | 176 | @override 177 | Widget build(BuildContext context) { 178 | final BottomSheetThemeData bottomSheetTheme = 179 | Theme.of(context).bottomSheetTheme; 180 | final Color color = 181 | widget.backgroundColor ?? bottomSheetTheme.backgroundColor; 182 | final double elevation = 183 | widget.elevation ?? bottomSheetTheme.elevation ?? 0; 184 | final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape; 185 | 186 | final Widget bottomSheet = Material( 187 | key: _childKey, 188 | color: color, 189 | elevation: elevation, 190 | shape: shape, 191 | child: NotificationListener( 192 | onNotification: extentChanged, 193 | child: widget.builder(context), 194 | ), 195 | ); 196 | return !widget.enableDrag 197 | ? bottomSheet 198 | : GestureDetector( 199 | onVerticalDragUpdate: _handleDragUpdate, 200 | onVerticalDragEnd: _handleDragEnd, 201 | child: bottomSheet, 202 | excludeFromSemantics: true, 203 | ); 204 | } 205 | } 206 | 207 | // PERSISTENT BOTTOM SHEETS 208 | 209 | // See scaffold.dart 210 | 211 | // MODAL BOTTOM SHEETS 212 | class _ModalBottomSheetLayout extends SingleChildLayoutDelegate { 213 | _ModalBottomSheetLayout(this.progress, this.isScrollControlled); 214 | 215 | final double progress; 216 | final bool isScrollControlled; 217 | 218 | @override 219 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { 220 | return BoxConstraints( 221 | minWidth: constraints.maxWidth, 222 | maxWidth: constraints.maxWidth, 223 | minHeight: 0.0, 224 | // maxHeight: isScrollControlled 225 | // ? constraints.maxHeight 226 | // : constraints.maxHeight * 9.0 / 16.0, 227 | ); 228 | } 229 | 230 | @override 231 | Offset getPositionForChild(Size size, Size childSize) { 232 | return Offset(0.0, size.height - childSize.height * progress); 233 | } 234 | 235 | @override 236 | bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) { 237 | return progress != oldDelegate.progress; 238 | } 239 | } 240 | 241 | class _ModalBottomSheet extends StatefulWidget { 242 | const _ModalBottomSheet({ 243 | Key key, 244 | this.route, 245 | this.backgroundColor, 246 | this.elevation, 247 | this.shape, 248 | this.isScrollControlled = false, 249 | }) : assert(isScrollControlled != null), 250 | super(key: key); 251 | 252 | final _ModalBottomSheetRoute route; 253 | final bool isScrollControlled; 254 | final Color backgroundColor; 255 | final double elevation; 256 | final ShapeBorder shape; 257 | 258 | @override 259 | _ModalBottomSheetState createState() => _ModalBottomSheetState(); 260 | } 261 | 262 | class _ModalBottomSheetState extends State<_ModalBottomSheet> { 263 | String _getRouteLabel(MaterialLocalizations localizations) { 264 | switch (defaultTargetPlatform) { 265 | case TargetPlatform.iOS: 266 | return ''; 267 | case TargetPlatform.android: 268 | case TargetPlatform.fuchsia: 269 | return localizations.dialogLabel; 270 | } 271 | return null; 272 | } 273 | 274 | @override 275 | Widget build(BuildContext context) { 276 | assert(debugCheckHasMediaQuery(context)); 277 | assert(debugCheckHasMaterialLocalizations(context)); 278 | final MediaQueryData mediaQuery = MediaQuery.of(context); 279 | final MaterialLocalizations localizations = 280 | MaterialLocalizations.of(context); 281 | final String routeLabel = _getRouteLabel(localizations); 282 | 283 | return AnimatedBuilder( 284 | animation: widget.route.animation, 285 | builder: (BuildContext context, Widget child) { 286 | // Disable the initial animation when accessible navigation is on so 287 | // that the semantics are added to the tree at the correct time. 288 | final double animationValue = mediaQuery.accessibleNavigation 289 | ? 1.0 290 | : widget.route.animation.value; 291 | return Semantics( 292 | scopesRoute: true, 293 | namesRoute: true, 294 | label: routeLabel, 295 | explicitChildNodes: true, 296 | child: ClipRect( 297 | child: CustomSingleChildLayout( 298 | delegate: _ModalBottomSheetLayout( 299 | animationValue, widget.isScrollControlled), 300 | child: BottomSheet( 301 | animationController: widget.route._animationController, 302 | onClosing: () { 303 | if (widget.route.isCurrent) { 304 | Navigator.pop(context); 305 | } 306 | }, 307 | builder: widget.route.builder, 308 | backgroundColor: widget.backgroundColor, 309 | elevation: widget.elevation, 310 | shape: widget.shape, 311 | ), 312 | ), 313 | ), 314 | ); 315 | }, 316 | ); 317 | } 318 | } 319 | 320 | class _ModalBottomSheetRoute extends PopupRoute { 321 | _ModalBottomSheetRoute({ 322 | this.builder, 323 | this.theme, 324 | this.barrierLabel, 325 | this.backgroundColor, 326 | this.elevation, 327 | this.shape, 328 | @required this.isScrollControlled, 329 | RouteSettings settings, 330 | }) : assert(isScrollControlled != null), 331 | super(settings: settings); 332 | 333 | final WidgetBuilder builder; 334 | final ThemeData theme; 335 | final bool isScrollControlled; 336 | final Color backgroundColor; 337 | final double elevation; 338 | final ShapeBorder shape; 339 | 340 | @override 341 | Duration get transitionDuration => _bottomSheetDuration; 342 | 343 | @override 344 | bool get barrierDismissible => true; 345 | 346 | @override 347 | final String barrierLabel; 348 | 349 | @override 350 | Color get barrierColor => Colors.black54; 351 | 352 | AnimationController _animationController; 353 | 354 | @override 355 | AnimationController createAnimationController() { 356 | assert(_animationController == null); 357 | _animationController = 358 | BottomSheet.createAnimationController(navigator.overlay); 359 | return _animationController; 360 | } 361 | 362 | @override 363 | Widget buildPage(BuildContext context, Animation animation, 364 | Animation secondaryAnimation) { 365 | // By definition, the bottom sheet is aligned to the bottom of the page 366 | // and isn't exposed to the top padding of the MediaQuery. 367 | Widget bottomSheet = MediaQuery.removePadding( 368 | context: context, 369 | removeTop: true, 370 | child: _ModalBottomSheet( 371 | route: this, 372 | backgroundColor: backgroundColor, 373 | elevation: elevation, 374 | shape: shape, 375 | isScrollControlled: isScrollControlled), 376 | ); 377 | if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet); 378 | return bottomSheet; 379 | } 380 | } 381 | 382 | /// Shows a modal material design bottom sheet. 383 | /// 384 | /// A modal bottom sheet is an alternative to a menu or a dialog and prevents 385 | /// the user from interacting with the rest of the app. 386 | /// 387 | /// A closely related widget is a persistent bottom sheet, which shows 388 | /// information that supplements the primary content of the app without 389 | /// preventing the use from interacting with the app. Persistent bottom sheets 390 | /// can be created and displayed with the [showBottomSheet] function or the 391 | /// [ScaffoldState.showBottomSheet] method. 392 | /// 393 | /// The `context` argument is used to look up the [Navigator] and [Theme] for 394 | /// the bottom sheet. It is only used when the method is called. Its 395 | /// corresponding widget can be safely removed from the tree before the bottom 396 | /// sheet is closed. 397 | /// 398 | /// The `isScrollControlled` parameter specifies whether this is a route for 399 | /// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish 400 | /// to have a bottom sheet that has a scrollable child such as a [ListView] or 401 | /// a [GridView] and have the bottom sheet be draggable, you should set this 402 | /// parameter to true. 403 | /// 404 | /// Returns a `Future` that resolves to the value (if any) that was passed to 405 | /// [Navigator.pop] when the modal bottom sheet was closed. 406 | /// 407 | /// See also: 408 | /// 409 | /// * [BottomSheet], which is the widget normally returned by the function 410 | /// passed as the `builder` argument to [showModalBottomSheet]. 411 | /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing 412 | /// non-modal bottom sheets. 413 | /// * 414 | Future showModalBottomSheetOp({ 415 | @required BuildContext context, 416 | @required WidgetBuilder builder, 417 | Color backgroundColor, 418 | double elevation, 419 | ShapeBorder shape, 420 | bool isScrollControlled = false, 421 | }) { 422 | assert(context != null); 423 | assert(builder != null); 424 | assert(isScrollControlled != null); 425 | assert(debugCheckHasMediaQuery(context)); 426 | assert(debugCheckHasMaterialLocalizations(context)); 427 | 428 | return Navigator.push( 429 | context, 430 | _ModalBottomSheetRoute( 431 | builder: builder, 432 | theme: Theme.of(context, shadowThemeOnly: true), 433 | isScrollControlled: isScrollControlled, 434 | barrierLabel: 435 | MaterialLocalizations.of(context).modalBarrierDismissLabel, 436 | backgroundColor: backgroundColor, 437 | elevation: elevation, 438 | shape: shape, 439 | )); 440 | } 441 | 442 | /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If 443 | /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet]. 444 | /// 445 | /// Returns a controller that can be used to close and otherwise manipulate the 446 | /// bottom sheet. 447 | /// 448 | /// To rebuild the bottom sheet (e.g. if it is stateful), call 449 | /// [PersistentBottomSheetController.setState] on the controller returned by 450 | /// this method. 451 | /// 452 | /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing 453 | /// [ModalRoute] and a back button is added to the appbar of the [Scaffold] 454 | /// that closes the bottom sheet. 455 | /// 456 | /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and 457 | /// does not add a back button to the enclosing Scaffold's appbar, use the 458 | /// [Scaffold.bottomSheet] constructor parameter. 459 | /// 460 | /// A closely related widget is a modal bottom sheet, which is an alternative 461 | /// to a menu or a dialog and prevents the user from interacting with the rest 462 | /// of the app. Modal bottom sheets can be created and displayed with the 463 | /// [showModalBottomSheet] function. 464 | /// 465 | /// The `context` argument is used to look up the [Scaffold] for the bottom 466 | /// sheet. It is only used when the method is called. Its corresponding widget 467 | /// can be safely removed from the tree before the bottom sheet is closed. 468 | /// 469 | /// See also: 470 | /// 471 | /// * [BottomSheet], which is the widget typically returned by the `builder`. 472 | /// * [showModalBottomSheet], which can be used to display a modal bottom 473 | /// sheet. 474 | /// * [Scaffold.of], for information about how to obtain the [BuildContext]. 475 | /// * 476 | PersistentBottomSheetController showBottomSheet({ 477 | @required BuildContext context, 478 | @required WidgetBuilder builder, 479 | Color backgroundColor, 480 | double elevation, 481 | ShapeBorder shape, 482 | }) { 483 | assert(context != null); 484 | assert(builder != null); 485 | assert(debugCheckHasScaffold(context)); 486 | 487 | return Scaffold.of(context).showBottomSheet( 488 | builder, 489 | backgroundColor: backgroundColor, 490 | elevation: elevation, 491 | shape: shape, 492 | ); 493 | } 494 | -------------------------------------------------------------------------------- /lib/component/count.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:easy_market/utils/rem.dart'; 3 | 4 | // Color borderColor = Color.fromARGB(255, 0, 191, 255); 5 | Color borderColor = Colors.grey; 6 | 7 | class Count extends StatelessWidget { 8 | Count({ 9 | this.number, 10 | this.min, 11 | this.max, 12 | this.onChange, 13 | }); 14 | 15 | final ValueChanged onChange; 16 | final int number; 17 | final int min; 18 | final int max; 19 | 20 | final int a = 1; 21 | 22 | onClickBtn(String type) { 23 | if (type == 'remove' && number > min) { 24 | onChange(number - 1); 25 | } else if (type == 'add' && number < max) { 26 | onChange(number + 1); 27 | } 28 | } 29 | 30 | // 相当于render 31 | Widget build(BuildContext context) { 32 | return Container( 33 | height: Rem.getPxToRem(70), 34 | width: Rem.getPxToRem(260), 35 | decoration: BoxDecoration( 36 | border: Border.all( 37 | color: borderColor, 38 | ), 39 | ), 40 | child: Row( 41 | children: [ 42 | Container( 43 | width: Rem.getPxToRem(70), 44 | color: this.min >= this.number ? Colors.grey[200] : Colors.white, 45 | child: InkResponse( 46 | child: Center( 47 | child: Icon( 48 | Icons.remove, 49 | color: 50 | this.min >= this.number ? Colors.grey[500] : Colors.black, 51 | ), 52 | ), 53 | onTap: () { 54 | this.onClickBtn('remove'); 55 | }, 56 | ), 57 | ), 58 | Expanded( 59 | child: Container( 60 | decoration: BoxDecoration( 61 | border: Border( 62 | left: BorderSide(color: borderColor), 63 | right: BorderSide(color: borderColor), 64 | ), 65 | ), 66 | child: Center( 67 | child: Text( 68 | '${this.number}', 69 | style: TextStyle(fontSize: Rem.getPxToRem(32)), 70 | ), 71 | ), 72 | ), 73 | ), 74 | Container( 75 | width: Rem.getPxToRem(70), 76 | color: this.max <= this.number ? Colors.grey[200] : Colors.white, 77 | child: InkResponse( 78 | child: Center( 79 | child: Icon( 80 | Icons.add, 81 | color: 82 | this.max <= this.number ? Colors.grey[500] : Colors.black, 83 | ), 84 | ), 85 | onTap: () { 86 | this.onClickBtn('add'); 87 | }, 88 | ), 89 | ), 90 | ], 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/component/linearBar.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 滚动渐变的appBar组件 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-30 14:21:15 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:easy_market/utils/rem.dart'; 8 | 9 | const APPBAR_SCROLL_OFFSET = 150; 10 | 11 | class LinearBar extends StatefulWidget { 12 | LinearBar({this.removePadding, this.child, this.appBarColor, this.title}); 13 | 14 | final bool removePadding; 15 | final Widget child; 16 | final Color appBarColor; 17 | final String title; 18 | _LinearBar createState() => _LinearBar(); 19 | } 20 | 21 | class _LinearBar extends State { 22 | double appBarAlpha = 0; 23 | 24 | _onScroll(offset) { 25 | double alpha = offset / APPBAR_SCROLL_OFFSET; 26 | if (alpha < 0) { 27 | alpha = 0; 28 | } else if (alpha > 1) { 29 | alpha = 1; 30 | } 31 | setState(() { 32 | appBarAlpha = alpha; 33 | }); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | body: Stack( 40 | children: [ 41 | Padding( 42 | padding: EdgeInsets.only( 43 | top: 44 | widget.removePadding ? MediaQuery.of(context).padding.top : 0, 45 | ), 46 | child: MediaQuery.removePadding( 47 | removeTop: true, 48 | context: context, 49 | child: NotificationListener( 50 | onNotification: (scrollNotification) { 51 | if (scrollNotification is ScrollUpdateNotification && 52 | scrollNotification.depth == 0) { 53 | _onScroll(scrollNotification.metrics.pixels); 54 | } 55 | return null; 56 | }, 57 | child: widget.child, 58 | ), 59 | ), 60 | ), 61 | Opacity( 62 | //改变透明度都可以使用 Opacity 将其包裹 63 | opacity: appBarAlpha, 64 | child: Container( 65 | height: 66 | Rem.getPxToRem(100) + MediaQuery.of(context).padding.top, 67 | padding: 68 | EdgeInsets.only(top: MediaQuery.of(context).padding.top), 69 | decoration: BoxDecoration(color: widget.appBarColor), 70 | )), 71 | Container( 72 | height: Rem.getPxToRem(100) + MediaQuery.of(context).padding.top, 73 | padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), 74 | decoration: BoxDecoration(color: Colors.transparent), 75 | child: Row( 76 | children: [ 77 | InkResponse( 78 | child: Container( 79 | width: Rem.getPxToRem(100), 80 | child: Center( 81 | child: Icon( 82 | Icons.keyboard_arrow_left, 83 | color: Colors.white, 84 | ), 85 | ), 86 | ), 87 | onTap: () { 88 | Navigator.pop(context); 89 | }, 90 | ), 91 | Expanded( 92 | child: Padding( 93 | child: Center( 94 | child: Text( 95 | widget.title, 96 | overflow: TextOverflow.ellipsis, 97 | style: TextStyle( 98 | color: Colors.white, fontSize: Rem.getPxToRem(38)), 99 | ), 100 | ), 101 | padding: EdgeInsets.symmetric(horizontal: 20), 102 | ), 103 | ), 104 | Container( 105 | width: Rem.getPxToRem(100), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ], 111 | ), 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/component/swiper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_swiper/flutter_swiper.dart'; // 引入头文件 3 | 4 | class SwiperView extends StatefulWidget { 5 | final List bannerImg; 6 | SwiperView(this.bannerImg); 7 | @override 8 | _SwiperViewState createState() => _SwiperViewState(); 9 | } 10 | 11 | class _SwiperViewState extends State { 12 | List imageList; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | setState(() { 18 | imageList = widget.bannerImg; 19 | }); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Column(children: [firstSwiperView()]); 25 | } 26 | 27 | Widget firstSwiperView() { 28 | return Container( 29 | width: MediaQuery.of(context).size.width, 30 | height: 200, 31 | child: Swiper( 32 | itemCount: imageList.length, 33 | itemBuilder: _swiperBuilder, 34 | pagination: SwiperPagination( 35 | alignment: Alignment.bottomCenter, 36 | margin: const EdgeInsets.fromLTRB(0, 0, 0, 5), 37 | builder: DotSwiperPaginationBuilder( 38 | color: Colors.grey[200], 39 | size: 8, 40 | activeColor: Colors.red[400])), 41 | controller: SwiperController(), 42 | scrollDirection: Axis.horizontal, 43 | autoplay: true, 44 | autoplayDelay: 4000, 45 | onTap: (index) => print('点击了第$index'), 46 | ), 47 | ); 48 | } 49 | 50 | Widget _swiperBuilder(BuildContext context, int index) { 51 | return (imageList[index]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/component/tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 自定义Tab 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-15 10:08:01 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import '../utils/rem.dart'; 8 | 9 | class TabItem { 10 | final String name, defaultIcon, activeIcon; 11 | TabItem(this.name, this.defaultIcon, this.activeIcon); 12 | } 13 | 14 | class TabOp extends StatelessWidget { 15 | TabOp({Key key, this.currentIndex, this.onTabChange, this.items}) 16 | : super(key: key); 17 | final int currentIndex; 18 | final ValueChanged onTabChange; 19 | final List items; 20 | _handleTap(index) { 21 | if (currentIndex != index) { 22 | onTabChange(index); 23 | } 24 | } 25 | 26 | Widget build(BuildContext context) { 27 | final List tabs = List(items.length); 28 | for (var i = 0; i < items.length; i++) { 29 | tabs[i] = Expanded( 30 | child: InkResponse( 31 | onTap: () { 32 | _handleTap(i); 33 | }, 34 | child: Container( 35 | height: Rem.getPxToRem(110), 36 | child: new Column( 37 | children: [ 38 | Expanded( 39 | child: currentIndex == i 40 | ? AnimateTab(icon: items[i].activeIcon) 41 | : Image.asset( 42 | currentIndex == i 43 | ? items[i].activeIcon 44 | : items[i].defaultIcon, 45 | height: Rem.getPxToRem(40), 46 | width: Rem.getPxToRem(40), 47 | ), 48 | flex: 8, 49 | ), 50 | Expanded( 51 | child: new Text( 52 | items[i].name, 53 | style: TextStyle( 54 | fontSize: Rem.getPxToRem(24), 55 | color: currentIndex == i 56 | ? Color.fromARGB(255, 33, 150, 243) 57 | : Colors.black), 58 | ), 59 | flex: 4, 60 | ) 61 | ], 62 | ), 63 | )), 64 | ); 65 | } 66 | return Container( 67 | decoration: new BoxDecoration( 68 | color: Colors.white, 69 | border: new Border(top: BorderSide(width: 1, color: Colors.grey[300])), 70 | ), 71 | child: Row( 72 | children: tabs, 73 | ), 74 | ); 75 | } 76 | } 77 | 78 | class AnimateTab extends StatefulWidget { 79 | AnimateTab({Key key, this.icon}) : super(key: key); 80 | final String icon; 81 | @override 82 | _AnimateTab createState() => new _AnimateTab(); 83 | } 84 | 85 | class _AnimateTab extends State 86 | with SingleTickerProviderStateMixin { 87 | Animation animation; 88 | AnimationController controller; 89 | 90 | initState() { 91 | super.initState(); 92 | controller = new AnimationController( 93 | duration: const Duration(milliseconds: 500), vsync: this); 94 | animation = CurvedAnimation(parent: controller, curve: Cubic(0, -5, .5, 5)); 95 | animation = new Tween(begin: Rem.getPxToRem(35), end: Rem.getPxToRem(40)) 96 | .animate(animation) 97 | ..addListener(() { 98 | setState(() {}); 99 | }); 100 | controller.forward(); 101 | } 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | return Image.asset(widget.icon, 106 | width: animation.value, height: animation.value); 107 | } 108 | 109 | dispose() { 110 | controller.dispose(); 111 | super.dispose(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/component/tabAppBar.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 自定义有tab切换的appBar 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-28 14:47:00 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:easy_market/utils/rem.dart'; 8 | 9 | class TabAppBar extends StatelessWidget { 10 | TabAppBar({this.tabs, this.controller, this.title}); 11 | 12 | final List tabs; 13 | 14 | final TabController controller; 15 | 16 | final String title; 17 | Widget buildAppBar(BuildContext context) { 18 | return new Container( 19 | width: double.infinity, 20 | padding: new EdgeInsets.only(top: MediaQuery.of(context).padding.top), 21 | child: new Container( 22 | child: Row( 23 | children: [ 24 | InkResponse( 25 | child: Container( 26 | width: Rem.getPxToRem(100), 27 | child: Center( 28 | child: Icon( 29 | Icons.keyboard_arrow_left, 30 | color: Colors.white, 31 | ), 32 | ), 33 | ), 34 | onTap: () { 35 | Navigator.pop(context); 36 | }, 37 | ), 38 | Expanded( 39 | child: Center( 40 | child: Text( 41 | title, 42 | style: TextStyle( 43 | color: Colors.white, fontSize: Rem.getPxToRem(38)), 44 | ), 45 | ), 46 | ), 47 | Container( 48 | width: Rem.getPxToRem(100), 49 | ), 50 | ], 51 | ), 52 | height: Rem.getPxToRem(100), 53 | ), 54 | decoration: new BoxDecoration( 55 | gradient: new LinearGradient(colors: [Colors.teal, Colors.green]), 56 | ), 57 | ); 58 | } 59 | 60 | Widget buildTabBar() { 61 | return Container( 62 | width: double.infinity, 63 | decoration: BoxDecoration(color: Colors.white, boxShadow: [ 64 | new BoxShadow( 65 | color: Colors.grey[500], 66 | blurRadius: Rem.getPxToRem(1), 67 | spreadRadius: 0.8, 68 | ) 69 | ]), 70 | child: TabBar( 71 | isScrollable: true, 72 | controller: this.controller, 73 | labelStyle: TextStyle( 74 | fontSize: Rem.getPxToRem(30), fontWeight: FontWeight.bold), 75 | labelColor: Colors.green, 76 | unselectedLabelColor: Colors.black45, 77 | indicatorColor: Colors.green, 78 | indicatorWeight: Rem.getPxToRem(2), 79 | tabs: tabs.map((item) { 80 | return Container( 81 | height: Rem.getPxToRem(60), 82 | child: Center(child: Text(item)), 83 | ); 84 | }).toList(), 85 | )); 86 | } 87 | 88 | @override 89 | PreferredSize build(BuildContext context) { 90 | return new PreferredSize( 91 | child: Column( 92 | children: tabs.length > 0 93 | ? [buildAppBar(context), buildTabBar()] 94 | : [ 95 | buildAppBar(context), 96 | ], 97 | ), 98 | preferredSize: new Size( 99 | MediaQuery.of(context).size.width, 100 | tabs.length > 0 ? Rem.getPxToRem(164) : Rem.getPxToRem(100), 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/component/verticalTab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 垂直切换的Tab 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | const double opWidth = 100.0; 9 | 10 | const tabItemHeight = 50.0; 11 | 12 | const indexColor = Colors.green; 13 | 14 | class VerticalTab extends StatefulWidget { 15 | VerticalTab({Key key, this.tabs, this.onTabChange, this.activeIndex}); 16 | final List tabs; 17 | 18 | final ValueChanged onTabChange; 19 | 20 | final int activeIndex; 21 | @override 22 | State createState() { 23 | return _VerticalTab(); 24 | } 25 | } 26 | 27 | class _VerticalTab extends State { 28 | double _scrollY = 0; 29 | 30 | int _currentIndex = 0; 31 | ScrollController _controller = new ScrollController(); 32 | 33 | bool isTap = false; 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _controller.addListener(() { 38 | if (isTap) { 39 | setState(() { 40 | isTap = false; 41 | _scrollY = _controller.offset; 42 | }); 43 | } else { 44 | setState(() { 45 | _scrollY = _controller.offset; 46 | }); 47 | } 48 | }); 49 | } 50 | 51 | @override 52 | void dispose() { 53 | _controller.dispose(); 54 | super.dispose(); 55 | } 56 | 57 | Widget buildTabItem(item, index) { 58 | return InkResponse( 59 | onTap: () { 60 | if (_currentIndex != index) { 61 | setState(() { 62 | _currentIndex = index; 63 | isTap = true; 64 | widget.onTabChange(index); 65 | }); 66 | } 67 | }, 68 | child: Container( 69 | width: opWidth, 70 | height: tabItemHeight, 71 | child: Center( 72 | child: Text( 73 | '$item', 74 | style: TextStyle( 75 | color: index == _currentIndex ? Colors.green : Colors.black87, 76 | letterSpacing: 2, 77 | fontSize: index == _currentIndex ? 16 : 14), 78 | ), 79 | ), 80 | ), 81 | ); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | final List tabList = List(widget.tabs.length); 87 | for (var i = 0; i < widget.tabs.length; i++) { 88 | tabList[i] = buildTabItem(widget.tabs[i], i); 89 | } 90 | return Container( 91 | width: opWidth, 92 | height: double.infinity, 93 | color: Colors.white, 94 | child: tabList.isNotEmpty 95 | ? Stack( 96 | children: [ 97 | Positioned( 98 | child: SingleChildScrollView( 99 | controller: _controller, 100 | child: Column( 101 | children: tabList, 102 | ), 103 | ), 104 | ), 105 | AnimatedPositioned( 106 | duration: Duration(milliseconds: isTap ? 100 : 0), 107 | top: -_scrollY + _currentIndex * 50, 108 | child: Container( 109 | height: tabItemHeight, 110 | width: 4, 111 | color: indexColor, 112 | )) 113 | ], 114 | ) 115 | : null, 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: main 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-15 10:08:01 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:easy_market/utils/rem.dart'; 10 | import 'package:easy_market/router/index.dart'; 11 | import 'package:easy_market/model/index.dart'; 12 | import 'package:easy_market/utils/cache.dart'; 13 | import 'package:easy_market/page/index.dart'; 14 | 15 | void main() async { 16 | // * https: //stackoverflow.com/questions/57689492/flutter-unhandled-exception-servicesbinding-defaultbinarymessenger-was-accesse 17 | // 异步获取SpUtil时,版本升级会有问题WidgetsFlutterBinding.ensureInitialized(); 18 | WidgetsFlutterBinding.ensureInitialized(); 19 | var sq = await SpUtil.getInstance(); 20 | var token = sq.getString('token'); 21 | var userName = sq.getString('userName'); 22 | runApp(MyApp(token, userName)); 23 | 24 | // if (Platform.isAndroid) { 25 | // //设置Android头部的导航栏透明 26 | // SystemUiOverlayStyle systemUiOverlayStyle = 27 | // SystemUiOverlayStyle(statusBarColor: Colors.transparent); 28 | // SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 29 | // } 30 | //白色 31 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light 32 | .copyWith(statusBarBrightness: Brightness.dark)); 33 | } 34 | 35 | class MyApp extends StatelessWidget { 36 | MyApp(this.token, this.userName); 37 | 38 | final String token; 39 | 40 | final String userName; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | // 设置设计稿的宽度 45 | 46 | Rem.setDesignWidth(750.0); 47 | return MultiProvider( 48 | providers: [ 49 | ChangeNotifierProvider(builder: (_) => Model(token, userName)), 50 | ], 51 | child: Consumer( 52 | builder: (context, model, widget) { 53 | return RestartWidget( 54 | child: MaterialApp( 55 | theme: ThemeData(backgroundColor: Colors.transparent), 56 | // 监听路由跳转 57 | onGenerateRoute: (RouteSettings settings) { 58 | return Router.run(settings); 59 | }, 60 | home: Scaffold( 61 | resizeToAvoidBottomPadding: false, 62 | body: TabPage(), 63 | ), 64 | ), 65 | ); 66 | }, 67 | ), 68 | ); 69 | } 70 | } 71 | 72 | ///这个组件用来重新加载整个child Widget的。当我们需要重启APP的时候,可以使用这个方案 73 | ///https://stackoverflow.com/questions/50115311/flutter-how-to-force-an-application-restart-in-production-mode 74 | class RestartWidget extends StatefulWidget { 75 | final Widget child; 76 | 77 | RestartWidget({Key key, @required this.child}) 78 | : assert(child != null), 79 | super(key: key); 80 | 81 | static restartApp(BuildContext context) { 82 | final _RestartWidgetState state = 83 | context.ancestorStateOfType(const TypeMatcher<_RestartWidgetState>()); 84 | state.restartApp(); 85 | } 86 | 87 | @override 88 | _RestartWidgetState createState() => _RestartWidgetState(); 89 | } 90 | 91 | class _RestartWidgetState extends State { 92 | Key key = UniqueKey(); 93 | 94 | void restartApp() { 95 | setState(() { 96 | key = UniqueKey(); 97 | }); 98 | } 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | return Container( 103 | key: key, 104 | child: widget.child, 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/model/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: Provider数据管理 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-27 15:34:15 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | class Model with ChangeNotifier { 9 | Model(this._token, this._userName); 10 | 11 | String _token; // 用户token的值来区分是否登录(null?token) 12 | 13 | String _userName; 14 | 15 | String get token => _token; 16 | 17 | String get userName => _userName; 18 | 19 | void setToken(token) { 20 | _token = token; 21 | notifyListeners(); 22 | } 23 | 24 | void setUserName(name) { 25 | _userName = name; 26 | notifyListeners(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/page/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:easy_market/page/home/index.dart'; 3 | import 'package:easy_market/page/topic/index.dart'; 4 | import 'package:easy_market/page/sort/index.dart'; 5 | import 'package:easy_market/page/mine/index.dart'; 6 | import 'package:easy_market/page/cart/index.dart'; 7 | import 'package:easy_market/page/wrapper.dart'; 8 | 9 | class App extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _ApplicationPageState(); 13 | } 14 | } 15 | 16 | class _Item { 17 | String name, activeIcon, normalIcon; 18 | 19 | _Item(this.name, this.activeIcon, this.normalIcon); 20 | } 21 | 22 | class _ApplicationPageState extends State { 23 | int _currentPageIndex = 0; 24 | 25 | final pageList = [ 26 | WrapKeepState(Home()), 27 | WrapKeepState(Topic()), 28 | WrapKeepState(Sort()), 29 | // WrapKeepState(Cart()), 30 | Cart(), 31 | WrapKeepState(Mine()), 32 | ]; 33 | 34 | Widget getPage(_index) { 35 | return pageList[_index]; 36 | } 37 | 38 | final itemNames = [ 39 | _Item('首页', 'assets/images/ic_tab_home_active.png', 40 | 'assets/images/ic_tab_home_normal.png'), 41 | _Item('专题', 'assets/images/ic_tab_subject_active.png', 42 | 'assets/images/ic_tab_subject_normal.png'), 43 | _Item('分类', 'assets/images/ic_tab_group_active.png', 44 | 'assets/images/ic_tab_group_normal.png'), 45 | _Item('购物车', 'assets/images/ic_tab_shiji_active.png', 46 | 'assets/images/ic_tab_shiji_normal.png'), 47 | _Item('我的', 'assets/images/ic_tab_profile_active.png', 48 | 'assets/images/ic_tab_profile_normal.png') 49 | ]; 50 | List itemList; 51 | @override 52 | void initState() { 53 | super.initState(); 54 | if (itemList == null) { 55 | itemList = itemNames 56 | .map((item) => BottomNavigationBarItem( 57 | icon: Image.asset( 58 | item.normalIcon, 59 | width: 30.0, 60 | height: 30.0, 61 | ), 62 | title: Text( 63 | item.name, 64 | style: TextStyle(fontSize: 10.0), 65 | ), 66 | activeIcon: 67 | Image.asset(item.activeIcon, width: 30.0, height: 30.0))) 68 | .toList(); 69 | } 70 | } 71 | 72 | final pageController = PageController(); 73 | 74 | void onTap(int index) { 75 | pageController.jumpToPage(index); 76 | } 77 | 78 | void onPageChanged(int index) { 79 | setState(() { 80 | _currentPageIndex = index; 81 | }); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return new Scaffold( 87 | backgroundColor: Color.fromARGB(1, 200, 200, 200), 88 | // appBar: new PreferredSize( 89 | // child: new Container( 90 | // // decoration: new BoxDecoration( 91 | // // gradient: 92 | // // new LinearGradient(colors: [Colors.teal, Colors.lightGreen]), 93 | // // ), 94 | // color: Colors.green, 95 | // ), 96 | // preferredSize: new Size(MediaQuery.of(context).size.width, 0), 97 | // ), 98 | // PageView+wrapper封装保存页面状态 99 | body: PageView( 100 | children: pageList, 101 | controller: pageController, 102 | onPageChanged: onPageChanged, 103 | physics: NeverScrollableScrollPhysics(), // 禁止滑动 104 | ), 105 | bottomNavigationBar: BottomNavigationBar( 106 | items: itemList, 107 | onTap: (int index) { 108 | setState(() { 109 | _currentPageIndex = index; 110 | pageController.jumpToPage(index); 111 | }); 112 | }, 113 | iconSize: 24, 114 | currentIndex: _currentPageIndex, 115 | fixedColor: Color.fromARGB(255, 0, 188, 96), 116 | type: BottomNavigationBarType.fixed, 117 | ), 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/page/cart/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | 4 | class Cart extends StatefulWidget { 5 | @override 6 | State createState() { 7 | return _Cart(); 8 | } 9 | } 10 | 11 | class _Cart extends State { 12 | final GlobalKey _scaffoldKey = GlobalKey(); 13 | @override 14 | dispose() { 15 | super.dispose(); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | // final model = Provider.of(context); 21 | // return Center( 22 | // child: model.token != null ? Text('${model.token}') : Text('未登录'), 23 | // ); 24 | return Scaffold( 25 | key: _scaffoldKey, 26 | appBar: new PreferredSize( 27 | child: new Container( 28 | color: Colors.green, 29 | ), 30 | preferredSize: new Size(MediaQuery.of(context).size.width, 0), 31 | ), 32 | body: Container( 33 | child: WebviewScaffold( 34 | url: 'https://www.zhihu.com/', 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/page/home/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:dio/dio.dart'; 4 | import 'package:easy_market/component/swiper.dart'; 5 | import 'package:easy_market/api/index.dart'; 6 | import 'package:easy_market/utils/rem.dart'; 7 | import 'package:easy_market/router/index.dart'; 8 | import './topic.dart'; 9 | 10 | class Home extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return _Home(); 14 | } 15 | } 16 | 17 | class _Home extends State { 18 | bool isLoading = true; 19 | 20 | List banner, channel, brand, news, hot, topic, category = []; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _getDio(); 26 | } 27 | 28 | _getDio() async { 29 | Response data = await Api.getHomeData(); 30 | // 轮播图数据 31 | var bannerData = data.data['banner']; 32 | List bannerList = List(); 33 | bannerData.forEach((item) => bannerList.add(CachedNetworkImage( 34 | imageUrl: item['image_url'], 35 | errorWidget: (context, url, error) => new Icon(Icons.error), 36 | fit: BoxFit.fill, 37 | ))); 38 | // channel数据 39 | var channelData = data.data['channel']; 40 | // 制造商数据 41 | var brandData = data.data['brandList']; 42 | // 新品推荐 43 | var newsData = data.data['newGoodsList']; 44 | // 人气推荐 45 | var hotData = data.data['hotGoodsList']; 46 | // 专题精选 47 | var topicList = data.data['topicList']; 48 | // 推荐商品 49 | var categoryList = data.data['categoryList']; 50 | setState(() { 51 | banner = bannerList; 52 | channel = channelData; 53 | brand = brandData; 54 | news = newsData; 55 | hot = hotData; 56 | topic = topicList; 57 | category = categoryList; 58 | isLoading = false; 59 | }); 60 | } 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | if (isLoading) { 65 | return Center( 66 | child: SizedBox( 67 | width: 24.0, 68 | height: 24.0, 69 | child: CircularProgressIndicator(strokeWidth: 2.0)), 70 | ); 71 | } else { 72 | var sliversList = [ 73 | buildSwiper(), 74 | buildChannel(), 75 | buildTitle('品牌制造商直供', false), 76 | buildBrand(), 77 | buildTitle('新品首发'), 78 | buildNews(), 79 | buildTitle('人气推荐', false), 80 | buildHot(), 81 | buildTopic(), 82 | ]; 83 | for (var i = 0; i < category.length; i++) { 84 | sliversList.add(buildTitle(category[i]['name'])); 85 | sliversList.add(buildCategory( 86 | category[i]['goodsList'], category[i]['name'], category[i]['id'])); 87 | } 88 | return new SafeArea( 89 | child: CustomScrollView( 90 | slivers: sliversList, 91 | ), 92 | ); 93 | } 94 | } 95 | 96 | // 轮播图 97 | SliverList buildSwiper() { 98 | return SliverList( 99 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 100 | return SwiperView(banner); 101 | }, childCount: 1), 102 | ); 103 | } 104 | 105 | // 渠道 106 | SliverGrid buildChannel() { 107 | return SliverGrid( 108 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 109 | crossAxisCount: 5, 110 | childAspectRatio: 1.3, 111 | ), 112 | delegate: new SliverChildBuilderDelegate( 113 | (BuildContext context, int index) { 114 | return Router.link( 115 | Column( 116 | children: [ 117 | Expanded( 118 | flex: 2, 119 | child: new Container( 120 | color: Colors.white, 121 | padding: EdgeInsets.only( 122 | bottom: Rem.getPxToRem(4), 123 | left: Rem.getPxToRem(25), 124 | top: Rem.getPxToRem(25), 125 | right: Rem.getPxToRem(25)), 126 | child: Center( 127 | child: CachedNetworkImage( 128 | imageUrl: channel[index]['icon_url'], 129 | fit: BoxFit.fitWidth, 130 | ), 131 | ), 132 | ), 133 | ), 134 | Expanded( 135 | flex: 1, 136 | child: new Container( 137 | color: Colors.white, 138 | child: Center( 139 | child: new Text( 140 | channel[index]['name'], 141 | style: TextStyle( 142 | fontSize: Rem.getPxToRem(20), 143 | ), 144 | ), 145 | ), 146 | ), 147 | ), 148 | ], 149 | ), 150 | '/catalog', 151 | context, 152 | { 153 | 'id': channel[index]['id'], 154 | }); 155 | }, 156 | childCount: 5, 157 | ), 158 | ); 159 | } 160 | 161 | // 製造商 162 | SliverGrid buildBrand() { 163 | return SliverGrid( 164 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 165 | crossAxisCount: 2, 166 | childAspectRatio: 1.6, 167 | ), 168 | delegate: new SliverChildBuilderDelegate( 169 | (BuildContext context, int index) { 170 | Widget widget = Container( 171 | child: Column( 172 | children: [ 173 | Container( 174 | width: double.infinity, 175 | child: Text( 176 | brand[index]['name'], 177 | style: TextStyle(fontSize: Rem.getPxToRem(30)), 178 | ), 179 | ), 180 | Container( 181 | width: double.infinity, 182 | child: Text( 183 | '${brand[index]['floor_price']}元起', 184 | style: TextStyle( 185 | fontSize: Rem.getPxToRem(24), color: Colors.grey), 186 | ), 187 | ), 188 | ], 189 | ), 190 | padding: EdgeInsets.only( 191 | top: Rem.getPxToRem(10), left: Rem.getPxToRem(20)), 192 | decoration: BoxDecoration( 193 | image: DecorationImage( 194 | fit: BoxFit.fill, 195 | image: NetworkImage(brand[index]['new_pic_url']))), 196 | ); 197 | return Router.link(widget, '/brand', context, { 198 | 'id': brand[index]['id'], 199 | }); 200 | }, 201 | childCount: brand.length, 202 | ), 203 | ); 204 | } 205 | 206 | // 新品 207 | SliverGrid buildNews() { 208 | return SliverGrid( 209 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 210 | crossAxisCount: 2, 211 | mainAxisSpacing: 2.0, 212 | crossAxisSpacing: 2.0, 213 | childAspectRatio: .9, 214 | ), 215 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 216 | Widget widget = Container( 217 | color: Colors.white, 218 | child: Column( 219 | children: [ 220 | Expanded( 221 | child: Padding( 222 | padding: EdgeInsets.symmetric(horizontal: Rem.getPxToRem(20)), 223 | child: CachedNetworkImage( 224 | imageUrl: news[index]['list_pic_url'], 225 | height: Rem.getPxToRem(250), 226 | width: double.infinity, 227 | ), 228 | ), 229 | flex: 300, 230 | ), 231 | Expanded( 232 | child: Container( 233 | child: Center( 234 | child: Text( 235 | '${news[index]['name']}', 236 | style: TextStyle( 237 | fontWeight: FontWeight.bold, 238 | fontSize: Rem.getPxToRem(30)), 239 | ), 240 | ), 241 | ), 242 | flex: 40, 243 | ), 244 | Expanded( 245 | child: Container( 246 | child: Center( 247 | child: Text( 248 | '¥${news[index]['retail_price']}', 249 | style: TextStyle( 250 | color: Colors.red, fontSize: Rem.getPxToRem(28)), 251 | ), 252 | ), 253 | ), 254 | flex: 40, 255 | ), 256 | ], 257 | ), 258 | ); 259 | return Router.link(widget, '/goodsDetail', context, { 260 | 'id': news[index]['id'], 261 | }); 262 | }, childCount: news.length), 263 | ); 264 | } 265 | 266 | // 人气推荐 267 | SliverList buildHot() { 268 | return SliverList( 269 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 270 | Widget widget = Container( 271 | decoration: BoxDecoration( 272 | color: Colors.white, 273 | border: 274 | Border(top: BorderSide(color: Colors.grey[300], width: .5))), 275 | padding: EdgeInsets.fromLTRB(Rem.getPxToRem(30), Rem.getPxToRem(10), 276 | Rem.getPxToRem(30), Rem.getPxToRem(10)), 277 | child: Container( 278 | height: Rem.getPxToRem(220), 279 | child: Row( 280 | children: [ 281 | Container( 282 | child: CachedNetworkImage( 283 | imageUrl: hot[index]['list_pic_url'], 284 | fit: BoxFit.cover, 285 | ), 286 | height: Rem.getPxToRem(220), 287 | width: Rem.getPxToRem(220), 288 | ), 289 | Expanded( 290 | child: Container( 291 | padding: 292 | EdgeInsets.symmetric(vertical: Rem.getPxToRem(20)), 293 | margin: EdgeInsets.only(left: Rem.getPxToRem(10)), 294 | child: Column( 295 | children: [ 296 | Container( 297 | height: Rem.getPxToRem(60), 298 | width: double.infinity, 299 | child: Align( 300 | alignment: Alignment.centerLeft, 301 | child: Text( 302 | hot[index]['name'], 303 | overflow: TextOverflow.ellipsis, 304 | style: TextStyle( 305 | fontWeight: FontWeight.bold, 306 | fontSize: Rem.getPxToRem(28)), 307 | ), 308 | )), 309 | Container( 310 | height: Rem.getPxToRem(60), 311 | width: double.infinity, 312 | child: Align( 313 | alignment: Alignment.centerLeft, 314 | child: Text( 315 | hot[index]['goods_brief'], 316 | overflow: TextOverflow.ellipsis, 317 | style: TextStyle( 318 | fontSize: Rem.getPxToRem(24), 319 | color: Colors.grey), 320 | ), 321 | ), 322 | ), 323 | Container( 324 | height: Rem.getPxToRem(60), 325 | width: double.infinity, 326 | child: Align( 327 | alignment: Alignment.centerLeft, 328 | child: Text( 329 | '¥${hot[index]['retail_price']}', 330 | style: TextStyle( 331 | fontSize: Rem.getPxToRem(30), 332 | color: Colors.red), 333 | ), 334 | ), 335 | ), 336 | ], 337 | )), 338 | ) 339 | ], 340 | ), 341 | ), 342 | ); 343 | return Router.link(widget, '/goodsDetail', context, { 344 | 'id': hot[index]['id'], 345 | }); 346 | }, childCount: hot.length), 347 | ); 348 | } 349 | 350 | // 专题精选 351 | SliverList buildTopic() { 352 | return SliverList( 353 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 354 | return Topic(topic, context); 355 | }, childCount: 1), 356 | ); 357 | } 358 | 359 | // 某类型的商品 360 | SliverGrid buildCategory(goods, typeName, id) { 361 | return SliverGrid( 362 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 363 | crossAxisCount: 2, 364 | mainAxisSpacing: 2.0, 365 | crossAxisSpacing: 2.0, 366 | childAspectRatio: .9, 367 | ), 368 | delegate: new SliverChildBuilderDelegate( 369 | (BuildContext context, int index) { 370 | if (goods.length == index) { 371 | Widget widget = Container( 372 | color: Colors.white, 373 | child: Center( 374 | child: Container( 375 | height: Rem.getPxToRem(140), 376 | child: Column( 377 | children: [ 378 | Container( 379 | height: Rem.getPxToRem(80), 380 | child: Center( 381 | child: Text( 382 | '更多$typeName好物', 383 | style: TextStyle(fontSize: Rem.getPxToRem(26)), 384 | ), 385 | ), 386 | ), 387 | Image.asset( 388 | 'assets/images/more.png', 389 | height: Rem.getPxToRem(60), 390 | ) 391 | ], 392 | ), 393 | ), 394 | ), 395 | ); 396 | return Router.link(widget, '/catalog', context, { 397 | 'id': id, 398 | }); 399 | } 400 | Widget widget = Container( 401 | color: Colors.white, 402 | child: Column( 403 | children: [ 404 | Expanded( 405 | child: Container( 406 | padding: EdgeInsets.symmetric(horizontal: 20), 407 | child: CachedNetworkImage( 408 | imageUrl: goods[index]['list_pic_url'], 409 | fit: BoxFit.cover, 410 | ), 411 | ), 412 | flex: 350, 413 | ), 414 | Expanded( 415 | child: Container( 416 | padding: EdgeInsets.symmetric(horizontal: 20), 417 | child: Center( 418 | child: Text( 419 | goods[index]['name'], 420 | style: TextStyle(fontWeight: FontWeight.bold), 421 | overflow: TextOverflow.ellipsis, 422 | ), 423 | ), 424 | ), 425 | flex: 50, 426 | ), 427 | Expanded( 428 | child: Container( 429 | padding: EdgeInsets.only(bottom: 10), 430 | child: Center( 431 | child: Text( 432 | '¥${goods[index]['retail_price']}', 433 | style: TextStyle( 434 | color: Colors.red, 435 | ), 436 | ), 437 | ), 438 | ), 439 | flex: 60, 440 | ), 441 | ], 442 | ), 443 | ); 444 | return Router.link(widget, '/goodsDetail', context, { 445 | 'id': goods[index]['id'], 446 | }); 447 | }, 448 | childCount: goods.length + 1, 449 | )); 450 | } 451 | 452 | // 标题 453 | SliverList buildTitle(String title, [bool isBorder = true]) { 454 | return SliverList( 455 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 456 | return Container( 457 | height: Rem.getPxToRem(100), 458 | decoration: BoxDecoration( 459 | color: Colors.white, 460 | border: isBorder 461 | ? Border( 462 | bottom: BorderSide(color: Colors.grey[200], width: .5)) 463 | : null), 464 | margin: EdgeInsets.only(top: Rem.getPxToRem(20)), 465 | child: Center( 466 | child: Text( 467 | title, 468 | style: TextStyle( 469 | fontWeight: FontWeight.bold, 470 | wordSpacing: 2, 471 | fontSize: Rem.getPxToRem(30)), 472 | ), 473 | ), 474 | ); 475 | }, childCount: 1), 476 | ); 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /lib/page/home/topic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:easy_market/utils/rem.dart'; 3 | import 'package:flutter_swiper/flutter_swiper.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | 6 | class Topic extends StatelessWidget { 7 | final List data; 8 | final BuildContext contexts; 9 | Topic(this.data, this.contexts); 10 | final String title = '专题精选'; 11 | Widget swiper(List imgList) { 12 | return Container( 13 | width: double.infinity, 14 | height: Rem.getPxToRem(480), 15 | child: Swiper( 16 | itemCount: 3, 17 | itemBuilder: (BuildContext context, int index) { 18 | return Padding( 19 | padding: EdgeInsets.symmetric(horizontal: 10), 20 | child: Column( 21 | children: [ 22 | Container( 23 | height: Rem.getPxToRem(400), 24 | width: double.infinity, 25 | child: CachedNetworkImage( 26 | imageUrl: imgList[index]['scene_pic_url'], 27 | fit: BoxFit.cover, 28 | ), 29 | ), 30 | Container( 31 | height: Rem.getPxToRem(40), 32 | child: Align( 33 | alignment: Alignment.centerLeft, 34 | child: Text.rich(TextSpan(children: [ 35 | TextSpan( 36 | text: imgList[index]['title'], 37 | style: TextStyle( 38 | fontSize: Rem.getPxToRem(26), 39 | fontWeight: FontWeight.bold)), 40 | TextSpan( 41 | text: '¥${imgList[index]['price_info']}元起', 42 | style: TextStyle( 43 | color: Colors.red[600], 44 | fontSize: Rem.getPxToRem(26), 45 | fontWeight: FontWeight.bold, 46 | ), 47 | ), 48 | ]))), 49 | ), 50 | Container( 51 | height: Rem.getPxToRem(40), 52 | child: Align( 53 | alignment: Alignment.centerLeft, 54 | child: new Text( 55 | imgList[index]['subtitle'], 56 | overflow: TextOverflow.ellipsis, 57 | style: TextStyle( 58 | fontSize: Rem.getPxToRem(22), color: Colors.grey), 59 | ), 60 | ), 61 | ), 62 | ], 63 | )); 64 | }, 65 | controller: SwiperController(), 66 | scrollDirection: Axis.horizontal, 67 | onTap: (index) { 68 | Navigator.pushNamed( 69 | contexts, 70 | '/topicDetail', 71 | arguments: {'id': imgList[index]['id']}, 72 | ); 73 | }, 74 | viewportFraction: 0.8, 75 | ), 76 | ); 77 | } 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | List imgUrl = []; 82 | data.forEach((item) => imgUrl.add(item)); 83 | return Container( 84 | margin: EdgeInsets.only(top: Rem.getPxToRem(20)), 85 | color: Colors.white, 86 | child: Column( 87 | children: [ 88 | Container( 89 | height: Rem.getPxToRem(100), 90 | child: Center( 91 | child: Text( 92 | title, 93 | style: TextStyle( 94 | fontWeight: FontWeight.bold, 95 | wordSpacing: 2, 96 | fontSize: Rem.getPxToRem(30)), 97 | ), 98 | ), 99 | ), 100 | swiper(imgUrl), 101 | ], 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/page/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:easy_market/Advertisement/openAd.dart'; 4 | import './app.dart'; 5 | // import 'package:easy_market/utils/rem.dart'; 6 | 7 | class TabPage extends StatefulWidget { 8 | _Page createState() => _Page(); 9 | } 10 | 11 | class _Page extends State { 12 | // 是否启用广告 13 | bool showAd = true; 14 | // 广告展示时间 15 | int _seconds = 5; 16 | 17 | Timer _timer; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _startTimer(); 23 | } 24 | 25 | // 启动倒计时的计时器。 26 | void _startTimer() { 27 | if (showAd) { 28 | _timer = Timer.periodic(Duration(seconds: 1), (timer) { 29 | setState(() {}); 30 | if (_seconds <= 1) { 31 | setState(() { 32 | showAd = false; 33 | }); 34 | _cancelTimer(); 35 | return; 36 | } else { 37 | setState(() { 38 | _seconds = _seconds - 1; 39 | }); 40 | } 41 | }); 42 | } 43 | } 44 | 45 | @override 46 | void dispose() { 47 | _cancelTimer(); 48 | super.dispose(); 49 | } 50 | 51 | // 清除倒计时的计时器。 52 | void _cancelTimer() { 53 | _timer?.cancel(); 54 | } 55 | 56 | Widget build(BuildContext context) { 57 | return Stack( 58 | children: [ 59 | // 显示app 60 | Offstage( 61 | child: App(), 62 | offstage: showAd, 63 | ), 64 | // 显示广告 65 | Offstage( 66 | child: OpenAd(_seconds), 67 | offstage: !showAd, 68 | ), 69 | ], 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/page/mine/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 我的页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-15 10:08:01 5 | */ 6 | import 'package:cached_network_image/cached_network_image.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:easy_market/model/index.dart'; 10 | import 'package:easy_market/utils/rem.dart'; 11 | import 'package:easy_market/router/index.dart'; 12 | import 'package:easy_market/utils/cache.dart'; 13 | 14 | class Mine extends StatelessWidget { 15 | final List> gridList = [ 16 | {'name': '我的收藏', 'icon': 'assets/images/collect.png'}, 17 | {'name': '地址管理', 'icon': 'assets/images/address.png'}, 18 | {'name': '我的订单', 'icon': 'assets/images/order.png'}, 19 | {'name': '周末拼单', 'icon': 'assets/images/week.png'}, 20 | {'name': '优惠券', 'icon': 'assets/images/oppen.png'}, 21 | {'name': '优选购', 'icon': 'assets/images/good.png'}, 22 | {'name': '我的红包', 'icon': 'assets/images/hongbao.png'}, 23 | {'name': '客服咨询', 'icon': 'assets/images/kefu.png'}, 24 | {'name': '会员plus', 'icon': 'assets/images/huiyuan.png'}, 25 | {'name': '意见反馈', 'icon': 'assets/images/issure.png'}, 26 | {'name': '账户安全', 'icon': 'assets/images/safe.png'}, 27 | {'name': '退出登录', 'icon': 'assets/images/logout.png'} 28 | ]; 29 | 30 | void todo({int index, BuildContext context}) async { 31 | var item = gridList[index]['name']; 32 | if (item == '退出登录') { 33 | final model = Provider.of(context); 34 | var sq = await SpUtil.getInstance(); 35 | sq.remove('token'); 36 | model.setToken(null); 37 | } 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | final model = Provider.of(context); 43 | if (model.token != null) { 44 | return SafeArea( 45 | child: CustomScrollView( 46 | slivers: [ 47 | buildHeader(context), 48 | buildItem(), 49 | ], 50 | ), 51 | ); 52 | } else { 53 | return Center( 54 | child: RaisedButton( 55 | child: Text("请先登录!"), 56 | onPressed: () { 57 | Router.push('/login', context); 58 | }, 59 | ), 60 | ); 61 | } 62 | } 63 | 64 | SliverGrid buildItem() { 65 | return SliverGrid( 66 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 67 | crossAxisCount: 3, 68 | mainAxisSpacing: 2.0, 69 | crossAxisSpacing: 2.0, 70 | childAspectRatio: 1, 71 | ), 72 | delegate: new SliverChildBuilderDelegate( 73 | (BuildContext context, int index) { 74 | return InkResponse( 75 | child: Container( 76 | color: Colors.white, 77 | child: Center( 78 | child: Container( 79 | height: Rem.getPxToRem(120), 80 | child: Column( 81 | children: [ 82 | Container( 83 | width: Rem.getPxToRem(60), 84 | child: Image.asset( 85 | gridList[index]['icon'], 86 | width: Rem.getPxToRem(60), 87 | ), 88 | ), 89 | Expanded( 90 | child: Container( 91 | child: Center( 92 | child: Text( 93 | gridList[index]['name'], 94 | style: TextStyle(fontSize: Rem.getPxToRem(24)), 95 | ), 96 | ), 97 | ), 98 | ) 99 | ], 100 | ), 101 | ), 102 | ), 103 | ), 104 | onTap: () { 105 | todo(index: index, context: context); 106 | }, 107 | ); 108 | }, 109 | childCount: gridList.length, 110 | ), 111 | ); 112 | } 113 | 114 | SliverList buildHeader(BuildContext context) { 115 | final model = Provider.of(context); 116 | return SliverList( 117 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 118 | return Container( 119 | height: Rem.getPxToRem(300), 120 | child: Stack( 121 | children: [ 122 | Positioned( 123 | right: 0, 124 | left: 0, 125 | top: 0, 126 | bottom: 0, 127 | child: CachedNetworkImage( 128 | imageUrl: 129 | 'http://yanxuan.nosdn.127.net/d069279e5834bbca17065a9855a014bf.png', 130 | fit: BoxFit.cover, 131 | ), 132 | ), 133 | Positioned( 134 | top: Rem.getPxToRem(60), 135 | left: Rem.getPxToRem(50), 136 | child: Container( 137 | width: Rem.getPxToRem(180), 138 | height: Rem.getPxToRem(180), 139 | decoration: BoxDecoration( 140 | image: new DecorationImage( 141 | image: new NetworkImage( 142 | 'http://yanxuan.nosdn.127.net/8945ae63d940cc42406c3f67019c5cb6.png'), 143 | centerSlice: 144 | new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0), 145 | ), 146 | borderRadius: BorderRadius.all(new Radius.circular( 147 | Rem.getPxToRem(180), 148 | )), 149 | ), 150 | ), 151 | ), 152 | Positioned( 153 | child: Container( 154 | child: Center( 155 | child: Text( 156 | '${model.userName}', 157 | style: TextStyle(color: Colors.white), 158 | ), 159 | ), 160 | height: Rem.getPxToRem(80), 161 | ), 162 | top: Rem.getPxToRem(80), 163 | left: Rem.getPxToRem(260), 164 | ), 165 | Positioned( 166 | child: Container( 167 | child: Center( 168 | child: Text( 169 | '普通用户', 170 | style: TextStyle(color: Colors.white), 171 | ), 172 | ), 173 | height: Rem.getPxToRem(80), 174 | ), 175 | top: Rem.getPxToRem(130), 176 | left: Rem.getPxToRem(260), 177 | ) 178 | ], 179 | ), 180 | ); 181 | }, childCount: 1), 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/page/sort/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:easy_market/component/verticalTab.dart'; 4 | import 'package:easy_market/api/index.dart'; 5 | import 'package:easy_market/router/index.dart'; 6 | 7 | class Sort extends StatefulWidget { 8 | @override 9 | State createState() { 10 | return _Sort(); 11 | } 12 | } 13 | 14 | class _Sort extends State { 15 | int activeIndex = 0; 16 | List tabs = []; 17 | 18 | int goodsCount = 0; 19 | 20 | bool isLoading = true; 21 | 22 | bool isRequest = true; 23 | 24 | Map currentCategory; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | getInitData(); 30 | } 31 | 32 | getInitData() async { 33 | var response = await Future.wait([Api.getSortTabs(), Api.getGoodsCount()]); 34 | setState(() { 35 | tabs = response[0].data['categoryList']; 36 | goodsCount = response[1].data['goodsCount']; 37 | isLoading = false; 38 | }); 39 | if (tabs.isNotEmpty) { 40 | var id = response[0].data['categoryList'][0]['id']; 41 | getCategoryMsg(id); 42 | } 43 | } 44 | 45 | getCategoryMsg(id) async { 46 | setState(() { 47 | isRequest = true; 48 | }); 49 | var data = await Api.getCategoryMsg(id: id); 50 | setState(() { 51 | currentCategory = data.data['currentCategory']; 52 | isRequest = false; 53 | }); 54 | } 55 | 56 | // 搜索框 57 | Widget buildSearch(BuildContext context) { 58 | Widget widget = Container( 59 | height: 46, 60 | padding: EdgeInsets.fromLTRB(15, 5, 15, 5), 61 | decoration: BoxDecoration( 62 | color: Colors.white, 63 | border: 64 | Border(bottom: BorderSide(color: Colors.grey[400], width: .5))), 65 | child: Container( 66 | decoration: BoxDecoration( 67 | color: Color.fromARGB(255, 237, 237, 237), 68 | borderRadius: BorderRadius.circular(5)), 69 | child: Center( 70 | child: Text('搜索商品,共$goodsCount款好物', 71 | style: TextStyle(color: Color.fromARGB(255, 102, 102, 102))), 72 | ), 73 | )); 74 | return Router.link(widget, '/search', context); 75 | } 76 | 77 | Widget buildContent() { 78 | if (isRequest) { 79 | return Center( 80 | child: SizedBox( 81 | width: 24.0, 82 | height: 24.0, 83 | child: CircularProgressIndicator(strokeWidth: 2.0)), 84 | ); 85 | } else { 86 | return CustomScrollView( 87 | slivers: [ 88 | SliverList( 89 | delegate: 90 | SliverChildBuilderDelegate((BuildContext context, int index) { 91 | return Stack( 92 | children: [ 93 | Positioned( 94 | child: Container( 95 | height: 120, 96 | child: CachedNetworkImage( 97 | imageUrl: currentCategory['wap_banner_url'], 98 | fit: BoxFit.fill, 99 | ), 100 | ), 101 | ), 102 | Positioned( 103 | child: Container( 104 | height: 120, 105 | child: Center( 106 | child: Text( 107 | '${currentCategory['front_name']}', 108 | style: TextStyle(color: Colors.white, fontSize: 12), 109 | ), 110 | ), 111 | ), 112 | ) 113 | ], 114 | ); 115 | }, childCount: 1), 116 | ), 117 | SliverPadding( 118 | padding: const EdgeInsets.all(4.0), 119 | sliver: SliverGrid( 120 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 121 | crossAxisCount: 2, 122 | mainAxisSpacing: 1.0, 123 | crossAxisSpacing: 1.0, 124 | childAspectRatio: .8, 125 | ), 126 | delegate: new SliverChildBuilderDelegate( 127 | (BuildContext context, int index) { 128 | Widget widget = Card( 129 | child: Column( 130 | children: [ 131 | Expanded( 132 | child: CachedNetworkImage( 133 | imageUrl: currentCategory['subCategoryList'] 134 | [index]['wap_banner_url'], 135 | ), 136 | flex: 4, 137 | ), 138 | Expanded( 139 | child: Text(currentCategory['subCategoryList'] 140 | [index]['name']), 141 | flex: 1, 142 | ) 143 | ], 144 | ), 145 | ); 146 | return Router.link( 147 | widget, 148 | '/catalog', 149 | context, 150 | {'id': currentCategory['subCategoryList'][index]['id']}, 151 | ); 152 | }, 153 | childCount: currentCategory['subCategoryList'].length, 154 | ), 155 | )), 156 | ], 157 | ); 158 | } 159 | } 160 | 161 | @override 162 | Widget build(BuildContext context) { 163 | if (isLoading) { 164 | return Center( 165 | child: SizedBox( 166 | width: 24.0, 167 | height: 24.0, 168 | child: CircularProgressIndicator(strokeWidth: 2.0)), 169 | ); 170 | } else { 171 | List newTabs = []; 172 | for (var i = 0; i < tabs.length; i++) { 173 | newTabs.add(tabs[i]['name']); 174 | } 175 | return Column( 176 | children: [ 177 | buildSearch(context), 178 | Expanded( 179 | child: Container( 180 | child: Row( 181 | children: [ 182 | VerticalTab( 183 | tabs: newTabs, 184 | onTabChange: (index) { 185 | getCategoryMsg(tabs[index]['id']); 186 | }, 187 | activeIndex: 0, 188 | ), 189 | Expanded( 190 | child: buildContent(), 191 | ) 192 | ], 193 | ), 194 | ), 195 | ) 196 | ], 197 | ); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /lib/page/topic/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:easy_market/api/index.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:easy_market/utils/rem.dart'; 6 | import 'package:easy_market/router/index.dart'; 7 | 8 | class Topic extends StatefulWidget { 9 | @override 10 | _InfiniteListViewState createState() => new _InfiniteListViewState(); 11 | } 12 | 13 | class _InfiniteListViewState extends State { 14 | static String loadingTag = "##loading##"; //表尾标记 15 | 16 | bool isFirstLoading = true; // is第一次加载 17 | 18 | final int pageSize = 5; // 每次加载的条数 19 | 20 | int page = 1; // 当前加载的页数 21 | 22 | int total = 0; 23 | 24 | List topicData = ['##loading##']; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | 30 | _getInitData(); 31 | } 32 | 33 | // 第一次加载 34 | _getInitData() async { 35 | Response data = await Api.getTopicData(page: 1, size: pageSize); 36 | topicData.insertAll(0, data.data['data']); 37 | setState(() { 38 | isFirstLoading = false; 39 | page = 2; 40 | total = data.data['count']; 41 | }); 42 | } 43 | 44 | // 加载更多 45 | _getMore() async { 46 | // 为了效果所以延迟1s 47 | Future.delayed(Duration(seconds: 1)).then((e) async { 48 | Response data = await Api.getTopicData(page: page, size: pageSize); 49 | topicData.insertAll(topicData.length - 1, data.data['data']); 50 | setState(() { 51 | page = page + 1; 52 | total = data.data['count']; 53 | }); 54 | }); 55 | } 56 | 57 | // 下拉刷新数据 58 | Future _handleRefresh() async { 59 | Response data = await Api.getTopicData(page: 1, size: pageSize); 60 | List newData = ['##loading##']; 61 | newData.insertAll(0, data.data['data']); 62 | print(newData.length); 63 | setState(() { 64 | page = 2; 65 | total = data.data['count']; 66 | topicData = newData; 67 | }); 68 | return null; 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | if (isFirstLoading) { 74 | return Center( 75 | child: SizedBox( 76 | width: 24.0, 77 | height: 24.0, 78 | child: CircularProgressIndicator(strokeWidth: 2.0)), 79 | ); 80 | } 81 | return RefreshIndicator( 82 | onRefresh: _handleRefresh, 83 | child: ListView.builder( 84 | itemCount: topicData.length, 85 | itemBuilder: (context, index) { 86 | //如果到了表尾 87 | if (topicData[index] == loadingTag) { 88 | if (topicData.length - 1 < total) { 89 | _getMore(); 90 | 91 | return Container( 92 | padding: const EdgeInsets.all(16.0), 93 | alignment: Alignment.center, 94 | child: SizedBox( 95 | width: 24.0, 96 | height: 24.0, 97 | child: CircularProgressIndicator(strokeWidth: 2.0)), 98 | ); 99 | } else { 100 | return Container( 101 | alignment: Alignment.center, 102 | padding: EdgeInsets.all(16.0), 103 | child: Text( 104 | "没有更多了", 105 | style: TextStyle(color: Colors.grey), 106 | )); 107 | } 108 | } 109 | Widget widget = Card( 110 | color: Colors.white, 111 | elevation: 4.0, 112 | margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0), 113 | child: Container( 114 | child: Column( 115 | children: [ 116 | Container( 117 | height: Rem.getPxToRem(400), 118 | width: double.infinity, 119 | child: CachedNetworkImage( 120 | imageUrl: topicData[index]['scene_pic_url'], 121 | fit: BoxFit.fill, 122 | ), 123 | ), 124 | Padding( 125 | padding: EdgeInsets.fromLTRB(20, 10, 20, 0), 126 | child: Center( 127 | child: Text( 128 | '${topicData[index]['title']}', 129 | style: TextStyle(fontSize: Rem.getPxToRem(32)), 130 | ), 131 | ), 132 | ), 133 | Padding( 134 | padding: EdgeInsets.fromLTRB(20, 10, 20, 10), 135 | child: Center( 136 | child: Text( 137 | '${topicData[index]['subtitle']}', 138 | overflow: TextOverflow.ellipsis, 139 | style: TextStyle( 140 | color: Colors.grey, fontSize: Rem.getPxToRem(30)), 141 | ), 142 | ), 143 | ) 144 | ], 145 | ))); 146 | return Router.link( 147 | widget, '/topicDetail', context, {'id': topicData[index]['id']}); 148 | }, 149 | ), 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/page/wrapper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: page模块状态缓存HOC 3 | * @Author: luoguoxiong 4 | * @Date: 2019-09-02 14:31:53 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | class WrapKeepState extends StatefulWidget { 9 | WrapKeepState(this.hocWidget); 10 | final Widget hocWidget; 11 | @override 12 | State createState() { 13 | return _WrapKeepState(); 14 | } 15 | } 16 | 17 | class _WrapKeepState extends State 18 | with AutomaticKeepAliveClientMixin { 19 | @override 20 | bool get wantKeepAlive => true; 21 | @override 22 | Widget build(BuildContext context) { 23 | super.build(context); 24 | return Scaffold( 25 | appBar: new PreferredSize( 26 | child: new Container( 27 | color: Colors.green, 28 | ), 29 | preferredSize: new Size(MediaQuery.of(context).size.width, 0), 30 | ), 31 | body: widget.hocWidget, 32 | ); 33 | // return widget.hocWidget; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/router/brand/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 制造商详情页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 15:59:04 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | import 'package:easy_market/component/linearBar.dart'; 9 | import 'package:easy_market/utils/rem.dart'; 10 | import 'package:easy_market/api/index.dart'; 11 | import 'package:easy_market/router/index.dart'; 12 | 13 | class Brand extends StatefulWidget { 14 | Brand({this.arguments}); 15 | final Map arguments; 16 | @override 17 | State createState() { 18 | return _Brand(); 19 | } 20 | } 21 | 22 | class _Brand extends State { 23 | bool isLoading = true; // 第一次加载 24 | 25 | int total = 0; // 商品总是 26 | 27 | int page = 1; // 当前页数 28 | 29 | int size = 6; // 每页的商品数 30 | 31 | Map brandMsg; // 制造商信息 32 | 33 | List brandGoods; // 制造商商品 34 | 35 | bool moreLoading = false; // 是否加载更多 36 | 37 | ScrollController _scrollController = new ScrollController(); 38 | 39 | // 初始化信息 40 | void getInitGoods() async { 41 | var data = await Future.wait([ 42 | Api.getBrandGoods( 43 | brandId: widget.arguments['id'], page: page, size: size), 44 | Api.getBrandMsg(id: widget.arguments['id']) 45 | ]); 46 | setState(() { 47 | isLoading = false; 48 | total = data[0].data['count']; 49 | brandGoods = data[0].data['data']; 50 | brandMsg = data[1].data['brand']; 51 | }); 52 | } 53 | 54 | // 获取更多制造商商品 55 | void getMoreBrandGoods() async { 56 | setState(() { 57 | moreLoading = true; 58 | }); 59 | Future.delayed(Duration(seconds: 1)).then((e) async { 60 | var data = await Api.getBrandGoods( 61 | brandId: widget.arguments['id'], page: page + 1, size: size); 62 | brandGoods.insertAll(brandGoods.length, data.data['data']); 63 | setState(() { 64 | total = data.data['count']; 65 | page = page + 1; 66 | moreLoading = false; 67 | }); 68 | }); 69 | } 70 | 71 | // 制造商logo 72 | Widget buildBrandLogo() { 73 | return Container( 74 | width: double.infinity, 75 | height: Rem.getPxToRem(400), 76 | child: CachedNetworkImage( 77 | imageUrl: brandMsg['list_pic_url'], 78 | fit: BoxFit.cover, 79 | ), 80 | ); 81 | } 82 | 83 | // 制造商描述 84 | Widget buildBrandDes() { 85 | return Container( 86 | color: Colors.white, 87 | padding: EdgeInsets.all(Rem.getPxToRem(20)), 88 | child: Center( 89 | child: Text( 90 | ' ${(brandMsg["simple_desc"]).replaceAll('\n', '')}', 91 | style: TextStyle( 92 | height: 1.2, color: Colors.grey, fontSize: Rem.getPxToRem(30)), 93 | ), 94 | )); 95 | } 96 | 97 | @override 98 | void initState() { 99 | super.initState(); 100 | getInitGoods(); 101 | _scrollController.addListener(() { 102 | // 如果下拉的当前位置到scroll的最下面 103 | if (_scrollController.position.pixels == 104 | _scrollController.position.maxScrollExtent) { 105 | if (!moreLoading && (total > brandGoods.length)) { 106 | getMoreBrandGoods(); 107 | } 108 | } 109 | }); 110 | } 111 | 112 | @override 113 | void dispose() { 114 | super.dispose(); 115 | _scrollController.dispose(); 116 | } 117 | 118 | // 加载更多 119 | Widget buildMore() { 120 | if (brandGoods.length == total) { 121 | return Container( 122 | alignment: Alignment.center, 123 | padding: EdgeInsets.all(16.0), 124 | child: Text( 125 | "没有更多商品!", 126 | style: TextStyle(color: Colors.grey), 127 | )); 128 | } else { 129 | return Container( 130 | padding: const EdgeInsets.all(16.0), 131 | alignment: Alignment.center, 132 | child: SizedBox( 133 | width: 24.0, 134 | height: 24.0, 135 | child: CircularProgressIndicator(strokeWidth: 2.0)), 136 | ); 137 | } 138 | } 139 | 140 | // ListView只加载一个 141 | SliverList buildOneWidget(Widget widget) { 142 | return SliverList( 143 | delegate: new SliverChildBuilderDelegate( 144 | (BuildContext context, int index) { 145 | return widget; 146 | }, 147 | childCount: 1, 148 | )); 149 | } 150 | 151 | // 商品 152 | SliverPadding buildBrandGoods() { 153 | return SliverPadding( 154 | padding: const EdgeInsets.all(8.0), 155 | sliver: new SliverGrid( 156 | //Grid 157 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 158 | crossAxisCount: 2, //Grid按两列显示 159 | mainAxisSpacing: 8.0, 160 | crossAxisSpacing: 8.0, 161 | childAspectRatio: 0.8, 162 | ), 163 | delegate: new SliverChildBuilderDelegate( 164 | (BuildContext context, int index) { 165 | //创建子widget 166 | Widget widget = new Container( 167 | decoration: BoxDecoration( 168 | color: Colors.white, 169 | borderRadius: new BorderRadius.all(new Radius.circular(5.0))), 170 | child: Column( 171 | children: [ 172 | Expanded( 173 | child: CachedNetworkImage( 174 | imageUrl: brandGoods[index]['list_pic_url'], 175 | ), 176 | ), 177 | Container( 178 | height: Rem.getPxToRem(50), 179 | padding: EdgeInsets.symmetric(horizontal: 10), 180 | child: Center( 181 | child: Text( 182 | '${brandGoods[index]['name']}', 183 | overflow: TextOverflow.ellipsis, 184 | style: TextStyle( 185 | color: Colors.grey, fontSize: Rem.getPxToRem(28)), 186 | ), 187 | ), 188 | ), 189 | Container( 190 | height: Rem.getPxToRem(50), 191 | child: Center( 192 | child: Text( 193 | '¥${brandGoods[index]['retail_price']}', 194 | style: TextStyle( 195 | color: Colors.red, fontSize: Rem.getPxToRem(30)), 196 | ), 197 | ), 198 | ), 199 | ], 200 | ), 201 | ); 202 | return Router.link(widget, '/goodsDetail', context, 203 | {'id': brandGoods[index]['id']}); 204 | }, 205 | childCount: brandGoods.length, 206 | ), 207 | ), 208 | ); 209 | } 210 | 211 | @override 212 | Widget build(BuildContext context) { 213 | if (isLoading) { 214 | return Material( 215 | child: SafeArea( 216 | child: Center( 217 | child: SizedBox( 218 | width: 24.0, 219 | height: 24.0, 220 | child: CircularProgressIndicator(strokeWidth: 2.0)), 221 | ), 222 | ), 223 | ); 224 | } else { 225 | return LinearBar( 226 | child: CustomScrollView( 227 | controller: _scrollController, 228 | slivers: [ 229 | buildOneWidget(buildBrandLogo()), 230 | buildOneWidget(buildBrandDes()), 231 | buildBrandGoods(), 232 | buildOneWidget(buildMore()), 233 | ], 234 | ), 235 | removePadding: true, 236 | title: brandMsg['name'], 237 | appBarColor: Colors.green, 238 | ); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /lib/router/catalog/catalogGoods.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:easy_market/api/index.dart'; 3 | import 'package:easy_market/utils/help.dart'; 4 | import 'package:easy_market/utils/rem.dart'; 5 | import 'package:easy_market/router/index.dart'; 6 | import 'package:cached_network_image/cached_network_image.dart'; 7 | 8 | class CatalogGoods extends StatefulWidget { 9 | CatalogGoods(this.id); 10 | final int id; 11 | @override 12 | State createState() { 13 | return _CatalogGoods(); 14 | } 15 | } 16 | 17 | class _CatalogGoods extends State 18 | with AutomaticKeepAliveClientMixin { 19 | bool isLoading = true; 20 | 21 | int page = 1; 22 | 23 | int size = 8; 24 | 25 | int total = 0; 26 | 27 | static int chunk = 2; //没列显示多少个商品 28 | 29 | List dataList = ['isLoading']; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _getInitData(); 35 | } 36 | 37 | @override 38 | bool get wantKeepAlive => true; 39 | 40 | _getInitData() async { 41 | var data = await Api.getGoods(page: 1, size: size, categoryId: widget.id); 42 | var resData = data.data; 43 | var goodsList = resData['data']; 44 | var newDataList = listToSort(toSort: goodsList, chunk: chunk); 45 | dataList.insertAll(dataList.length - 1, newDataList); 46 | setState(() { 47 | isLoading = false; 48 | total = resData['count']; 49 | page = page + 1; 50 | dataList = dataList; 51 | }); 52 | } 53 | 54 | // 加载更多 55 | _getMore() async { 56 | // 为了效果所以延迟1s 57 | Future.delayed(Duration(seconds: 1)).then((e) async { 58 | var data = 59 | await Api.getGoods(page: page, size: size, categoryId: widget.id); 60 | var resData = data.data; 61 | var goodsList = resData['data']; 62 | var newDataList = listToSort(toSort: goodsList, chunk: chunk); 63 | dataList.insertAll(dataList.length - 1, newDataList); 64 | setState(() { 65 | page = page + 1; 66 | total = data.data['count']; 67 | }); 68 | }); 69 | } 70 | 71 | @override 72 | void dispose() { 73 | super.dispose(); 74 | } 75 | 76 | // 下拉刷新数据 77 | Future _handleRefresh() async { 78 | var data = await Api.getGoods(page: 1, size: size, categoryId: widget.id); 79 | List newData = ['isLoading']; 80 | var resData = data.data; 81 | var goodsList = resData['data']; 82 | var newDataList = listToSort(toSort: goodsList, chunk: chunk); 83 | newData.insertAll(0, newDataList); 84 | setState(() { 85 | page = 2; 86 | total = resData['count']; 87 | dataList = newData; 88 | }); 89 | return null; 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | super.build(context); 95 | if (isLoading) { 96 | return Center( 97 | child: SizedBox( 98 | width: 24.0, 99 | height: 24.0, 100 | child: CircularProgressIndicator(strokeWidth: 2.0)), 101 | ); 102 | } else { 103 | return RefreshIndicator( 104 | child: ListView.builder( 105 | itemCount: dataList.length, 106 | itemBuilder: (BuildContext context, int index) { 107 | if (dataList[index] == 'isLoading') { 108 | if ((dataList.length - 1) * 2 < total) { 109 | _getMore(); 110 | 111 | return Container( 112 | padding: const EdgeInsets.all(16.0), 113 | alignment: Alignment.center, 114 | child: SizedBox( 115 | width: 24.0, 116 | height: 24.0, 117 | child: CircularProgressIndicator(strokeWidth: 2.0)), 118 | ); 119 | } else { 120 | return Container( 121 | alignment: Alignment.center, 122 | padding: EdgeInsets.all(16.0), 123 | child: Text( 124 | "没有更多了", 125 | style: TextStyle(color: Colors.grey), 126 | )); 127 | } 128 | } 129 | List listwidget = []; 130 | for (var i = 0; i < dataList[index].length; i++) { 131 | var item = dataList[index][i]; 132 | var paddingDes; 133 | if (index == 0 && i == 0) { 134 | paddingDes = EdgeInsets.fromLTRB(4, 4, 2, 2); 135 | } 136 | if (index == 0 && i == 1) { 137 | paddingDes = EdgeInsets.fromLTRB(2, 4, 4, 2); 138 | } 139 | if (index == dataList.length - 2 && i == 0) { 140 | paddingDes = EdgeInsets.fromLTRB(4, 2, 2, 4); 141 | } 142 | if (index == dataList.length - 2 && i == 1) { 143 | paddingDes = EdgeInsets.fromLTRB(2, 2, 4, 4); 144 | } else { 145 | if (i == 0) { 146 | paddingDes = EdgeInsets.fromLTRB(4, 2, 2, 2); 147 | } else { 148 | paddingDes = EdgeInsets.fromLTRB(2, 2, 4, 2); 149 | } 150 | } 151 | listwidget.add(Router.link( 152 | Container( 153 | width: Rem.getPxToRem(375), 154 | padding: paddingDes, 155 | child: Container( 156 | decoration: BoxDecoration( 157 | color: Colors.white, 158 | borderRadius: 159 | BorderRadius.all(Radius.circular(2)), 160 | boxShadow: [ 161 | new BoxShadow( 162 | color: Colors.grey[500], 163 | blurRadius: Rem.getPxToRem(1), 164 | spreadRadius: 0, 165 | ) 166 | ]), 167 | child: Column( 168 | children: [ 169 | Container( 170 | height: Rem.getPxToRem(360), 171 | child: CachedNetworkImage( 172 | imageUrl: item['list_pic_url'], 173 | fit: BoxFit.fill, 174 | ), 175 | ), 176 | Container( 177 | child: Center( 178 | child: Text( 179 | "¥${item['retail_price']}", 180 | style: TextStyle( 181 | color: Colors.red, 182 | fontSize: Rem.getPxToRem(30)), 183 | ), 184 | ), 185 | ), 186 | Container( 187 | height: Rem.getPxToRem(50), 188 | padding: EdgeInsets.symmetric(horizontal: 10), 189 | child: Center( 190 | child: Text( 191 | item['name'], 192 | style: TextStyle( 193 | color: Colors.grey, 194 | fontSize: Rem.getPxToRem(28)), 195 | overflow: TextOverflow.ellipsis, 196 | ), 197 | ), 198 | ), 199 | ], 200 | ), 201 | )), 202 | '/goodsDetail', 203 | context, 204 | { 205 | 'id': item['id'], 206 | })); 207 | } 208 | return Row(children: listwidget); 209 | }), 210 | onRefresh: _handleRefresh, 211 | ); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /lib/router/catalog/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 商品分类页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 15:59:04 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:easy_market/component/tabAppBar.dart'; 8 | import 'package:easy_market/api/index.dart'; 9 | import './catalogGoods.dart'; 10 | 11 | class Catalog extends StatefulWidget { 12 | Catalog({this.arguments}); 13 | final Map arguments; 14 | @override 15 | State createState() { 16 | return _Catalog(); 17 | } 18 | } 19 | 20 | class _Catalog extends State with TickerProviderStateMixin { 21 | TabController mController; 22 | List catalogList = []; 23 | int activeIndex; 24 | bool isLoading = true; 25 | @override 26 | Widget build(BuildContext context) { 27 | List tabItem = []; 28 | for (var i = 0; i < catalogList.length; i++) { 29 | tabItem.add(catalogList[i]['name']); 30 | } 31 | return Scaffold( 32 | appBar: TabAppBar( 33 | tabs: tabItem, 34 | controller: mController, 35 | title: '${tabItem.length > 0 ? tabItem[activeIndex] : '奇趣'}分类', 36 | ).build(context), 37 | body: isLoading 38 | ? Center( 39 | child: SizedBox( 40 | width: 24.0, 41 | height: 24.0, 42 | child: CircularProgressIndicator(strokeWidth: 2.0)), 43 | ) 44 | : _tabBarView()); 45 | } 46 | 47 | // 第一次加载 48 | _getInitData() async { 49 | var data = await Api.getBrotherCatalog(id: widget.arguments['id']); 50 | List brotherCategory = data.data['brotherCategory']; 51 | int index; 52 | for (var i = 0; i < brotherCategory.length; i++) { 53 | if (widget.arguments['id'] == brotherCategory[i]['id']) { 54 | index = i; 55 | } 56 | } 57 | if (mController != null) { 58 | mController.dispose(); 59 | } 60 | setState(() { 61 | isLoading = false; 62 | catalogList = brotherCategory; 63 | activeIndex = index; 64 | mController = TabController( 65 | length: brotherCategory.length, vsync: this, initialIndex: index); 66 | }); 67 | mController.addListener(() { 68 | setState(() { 69 | activeIndex = mController.index; 70 | }); 71 | }); 72 | } 73 | 74 | @override 75 | void initState() { 76 | super.initState(); 77 | mController = TabController(length: catalogList.length, vsync: this); 78 | _getInitData(); 79 | } 80 | 81 | @override 82 | void dispose() { 83 | super.dispose(); 84 | mController.dispose(); 85 | } 86 | 87 | Widget _tabBarView() { 88 | return TabBarView( 89 | controller: mController, 90 | children: catalogList.map((item) { 91 | return CatalogGoods(item['id']); 92 | }).toList(), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/router/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 路由管理 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-24 17:29:18 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import './catalog/index.dart'; 9 | import './brand/index.dart'; 10 | import './goodsDetail/index.dart'; 11 | import './tipicDetail/index.dart'; 12 | import './search/index.dart'; 13 | import './noFound.dart'; 14 | import './login/index.dart'; 15 | import './writeComment/index.dart'; 16 | import './moreComment/index.dart'; 17 | import 'package:easy_market/page/index.dart'; 18 | 19 | class Router { 20 | // 路由声明 21 | static Map routes = { 22 | '/catalog': (context, {arguments}) => Catalog(arguments: arguments), 23 | '/goodsDetail': (context, {arguments}) => GoodsDetail(arguments: arguments), 24 | '/topicDetail': (context, {arguments}) => TopicDetail(arguments: arguments), 25 | '/search': (context) => Search(), 26 | '/home': (context) => TabPage(), 27 | '/login': (context) => Login(), 28 | '/brand': (context, {arguments}) => Brand(arguments: arguments), 29 | '/writeComment': (context, {arguments}) => 30 | WriteComment(arguments: arguments), 31 | '/moreComment': (context, {arguments}) => MoreComment(arguments: arguments), 32 | }; 33 | 34 | // 路由初始化 35 | static run(RouteSettings settings) { 36 | final Function pageContentBuilder = Router.routes[settings.name]; 37 | 38 | if (pageContentBuilder != null) { 39 | if (settings.arguments != null) { 40 | // 传参路由 41 | return MaterialPageRoute( 42 | builder: (context) => 43 | pageContentBuilder(context, arguments: settings.arguments)); 44 | } else { 45 | // 无参数路由 46 | return MaterialPageRoute( 47 | builder: (context) => pageContentBuilder(context)); 48 | } 49 | } else { 50 | // 404页 51 | return MaterialPageRoute(builder: (context) => NoFoundPage()); 52 | } 53 | } 54 | 55 | // 组件跳转 56 | static link(Widget child, String routeName, BuildContext context, 57 | [Map parmas, Function callBack]) { 58 | return GestureDetector( 59 | onTap: () { 60 | if (parmas != null) { 61 | Navigator.pushNamed(context, routeName, arguments: parmas) 62 | .then((onValue) { 63 | if (callBack != null) { 64 | callBack(); 65 | } 66 | }); 67 | } else { 68 | Navigator.pushNamed(context, routeName).then((onValue) { 69 | if (callBack != null) { 70 | callBack(); 71 | } 72 | }); 73 | } 74 | }, 75 | child: child, 76 | ); 77 | } 78 | 79 | // 方法跳转 80 | static push(String routeName, BuildContext context, 81 | [Map parmas, Function callBack]) { 82 | if (parmas != null) { 83 | Navigator.pushNamed(context, routeName, arguments: parmas) 84 | .then((onValue) { 85 | if (callBack != null) { 86 | callBack(); 87 | } 88 | }); 89 | } else { 90 | Navigator.pushNamed(context, routeName).then((onValue) { 91 | if (callBack != null) { 92 | callBack(); 93 | } 94 | }); 95 | } 96 | } 97 | 98 | static pop(context) { 99 | Navigator.pop(context); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/router/login/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:easy_market/model/index.dart'; 5 | import 'package:easy_market/router/index.dart'; 6 | import 'package:easy_market/utils/cache.dart'; 7 | import 'package:easy_market/api/index.dart'; 8 | 9 | class Login extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _Login(); 13 | } 14 | } 15 | 16 | class _Login extends State { 17 | TextEditingController _unameController = new TextEditingController(); 18 | TextEditingController _pwdController = new TextEditingController(); 19 | GlobalKey _formKey = new GlobalKey(); 20 | 21 | void login(context) async { 22 | final sq = await SpUtil.getInstance(); 23 | final model = Provider.of(context); 24 | var data = await Api.loginByMobile( 25 | mobile: _unameController.text, password: _pwdController.text); 26 | sq.putString('token', data.data['sessionKey']); 27 | sq.putString('userName', data.data['mobile']); 28 | model.setToken(data.data['sessionKey']); 29 | model.setUserName(data.data['mobile']); 30 | Router.pop(context); 31 | } 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _unameController.text = "15323807318"; 37 | _pwdController.text = "123456"; 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | // child: InkResponse( 43 | // child: Text('点击登录!'), 44 | // onTap: () { 45 | // login(context); 46 | // }, 47 | // ), 48 | return Material( 49 | child: SafeArea( 50 | child: Column( 51 | children: [ 52 | Container( 53 | width: double.infinity, 54 | color: Colors.white, 55 | padding: EdgeInsets.fromLTRB(50, 100, 50, 40), 56 | child: CachedNetworkImage( 57 | imageUrl: 58 | 'http://yanxuan.nosdn.127.net/bd139d2c42205f749cd4ab78fa3d6c60.png', 59 | ), 60 | ), 61 | Expanded( 62 | child: Container( 63 | color: Colors.white, 64 | padding: 65 | const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), 66 | child: Form( 67 | key: _formKey, //设置globalKey,用于后面获取FormState 68 | autovalidate: true, //开启自动校验 69 | child: Column( 70 | children: [ 71 | TextFormField( 72 | controller: _unameController, 73 | decoration: InputDecoration( 74 | labelText: "手机号码", 75 | hintText: "请输入您的手机号码", 76 | icon: Icon(Icons.person)), 77 | // 校验用户名 78 | validator: (v) { 79 | if (v.trim().length == 0) { 80 | return '手机号码不能为空'; 81 | } else if (v != '15323807318') { 82 | return '测试的账号15323807318!'; 83 | } else { 84 | return null; 85 | } 86 | }), 87 | TextFormField( 88 | controller: _pwdController, 89 | decoration: InputDecoration( 90 | labelText: "密码", 91 | hintText: "您的登录密码", 92 | icon: Icon(Icons.lock)), 93 | obscureText: true, 94 | //校验密码 95 | validator: (v) { 96 | if (v.trim().length == 0) { 97 | return '登录密码不能为空'; 98 | } else if (v != '123456') { 99 | return '测试的登录密码时123456!'; 100 | } else { 101 | return null; 102 | } 103 | }), 104 | // 登录按钮 105 | Padding( 106 | padding: const EdgeInsets.only(top: 28.0), 107 | child: Row( 108 | children: [ 109 | Expanded( 110 | child: RaisedButton( 111 | padding: EdgeInsets.all(15.0), 112 | child: Text("登录"), 113 | color: Theme.of(context).primaryColor, 114 | textColor: Colors.white, 115 | onPressed: () { 116 | if ((_formKey.currentState as FormState) 117 | .validate()) { 118 | login(context); 119 | //验证通过提交数据 120 | } 121 | }, 122 | ), 123 | ), 124 | ], 125 | ), 126 | ) 127 | ], 128 | ), 129 | ), 130 | ), 131 | ) 132 | ], 133 | ))); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/router/moreComment/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 写评论 3 | * @Author: luoguoxiong 4 | * @Date: 2019-09-05 17:16:00 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class MoreComment extends StatefulWidget { 10 | MoreComment({this.arguments}); 11 | final Map arguments; 12 | @override 13 | State createState() { 14 | return _MoreComment(); 15 | } 16 | } 17 | 18 | class _MoreComment extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Material( 22 | child: Center( 23 | child: Text('更多评论!'), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/router/noFound.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NoFoundPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Center( 7 | child: Text('404'), 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/router/search/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 商品详情页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 16:41:58 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class Search extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _Search(); 13 | } 14 | } 15 | 16 | class _Search extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Material( 20 | child: Center( 21 | child: Text('商品查询页,\n当前页面尚未开发,请耐心等候!'), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/router/tipicDetail/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 专题详情页 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 16:41:58 5 | */ 6 | 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:provider/provider.dart'; 10 | import 'package:easy_market/model/index.dart'; 11 | import 'package:easy_market/component/linearBar.dart'; 12 | import 'package:easy_market/api/index.dart'; 13 | import 'package:easy_market/utils/rem.dart'; 14 | import 'package:easy_market/router/index.dart'; 15 | 16 | class TopicDetail extends StatefulWidget { 17 | TopicDetail({this.arguments}); 18 | final Map arguments; 19 | @override 20 | State createState() { 21 | return _TopicDetail(); 22 | } 23 | } 24 | 25 | // 正则太菜,先勉强用着。。。 26 | List getImgUrl(String str) { 27 | var newString = str.replaceAll('\n '); 29 | List reList = []; 30 | for (int i = 0; i < list.length; i++) { 31 | if (i == 0) { 32 | reList.add(list[i].replaceAll('"', '')); 33 | } else if (i == list.length - 1) { 34 | reList.add(list[list.length - 1].replaceAll('">', '')); 35 | } else { 36 | reList.add(list[i]); 37 | } 38 | } 39 | return reList; 40 | } 41 | 42 | class _TopicDetail extends State { 43 | bool initLoading = true; 44 | String title; 45 | List content; 46 | 47 | List comment; 48 | 49 | int commentCount = 0; 50 | 51 | @override 52 | void initState() { 53 | super.initState(); 54 | getInitData(); 55 | } 56 | 57 | getInitData() async { 58 | int id = widget.arguments['id']; 59 | var data = await Future.wait([ 60 | Api.getTopicMsg(id: id), 61 | Api.getTopicComment(valueId: id, typeId: 1, page: 1, size: 5), 62 | ]); 63 | var topicMsg = data[0].data; 64 | var commentMsg = data[1].data; 65 | setState(() { 66 | initLoading = false; 67 | title = topicMsg['title']; 68 | content = getImgUrl(topicMsg['content']); 69 | comment = commentMsg['data']; 70 | commentCount = commentMsg['count']; 71 | }); 72 | } 73 | 74 | toWrite(context) { 75 | final model = Provider.of(context); 76 | if (model.token != null) { 77 | Router.push('/writeComment', context, {'id': widget.arguments['id']}); 78 | } else { 79 | Router.push('/login', context); 80 | } 81 | } 82 | 83 | buildCommentList() { 84 | if (comment.length > 0) { 85 | List commentList = []; 86 | for (int i = 0; i < comment.length; i++) { 87 | commentList.add(Container( 88 | padding: EdgeInsets.symmetric( 89 | horizontal: Rem.getPxToRem(8), 90 | ), 91 | color: Colors.white, 92 | child: Container( 93 | height: Rem.getPxToRem(160), 94 | decoration: BoxDecoration( 95 | border: Border( 96 | bottom: BorderSide(width: .6, color: Colors.grey), 97 | ), 98 | ), 99 | child: Column( 100 | children: [ 101 | Expanded( 102 | child: Row( 103 | children: [ 104 | Expanded( 105 | child: Align( 106 | alignment: Alignment.centerLeft, 107 | child: Text( 108 | comment[i]['user_info']['username'] == null 109 | ? '匿名用户' 110 | : comment[i]['user_info']['username']), 111 | ), 112 | ), 113 | Expanded( 114 | child: Align( 115 | alignment: Alignment.centerRight, 116 | child: Text( 117 | comment[i]['add_time'], 118 | style: TextStyle(color: Colors.grey), 119 | ), 120 | ), 121 | ) 122 | ], 123 | ), 124 | ), 125 | Expanded( 126 | child: Align( 127 | alignment: Alignment.centerLeft, 128 | child: Text( 129 | comment[i]['content'], 130 | overflow: TextOverflow.ellipsis, 131 | style: TextStyle(color: Colors.grey), 132 | ), 133 | ), 134 | ) 135 | ], 136 | ), 137 | ), 138 | )); 139 | } 140 | return Column( 141 | children: commentList, 142 | ); 143 | } else { 144 | return Container( 145 | height: 100, 146 | color: Colors.white, 147 | child: Center( 148 | child: Text( 149 | '留言板空空如也~ ', 150 | style: TextStyle(color: Colors.grey), 151 | ), 152 | ), 153 | ); 154 | } 155 | } 156 | 157 | SliverList buildComment() { 158 | return SliverList( 159 | delegate: SliverChildBuilderDelegate((BuildContext context, int index) { 160 | return Column( 161 | children: [ 162 | Container( 163 | padding: EdgeInsets.symmetric( 164 | horizontal: Rem.getPxToRem(8), 165 | ), 166 | margin: EdgeInsets.only(top: Rem.getPxToRem(20)), 167 | color: Colors.white, 168 | child: Container( 169 | height: Rem.getPxToRem(100), 170 | decoration: BoxDecoration( 171 | border: Border( 172 | bottom: BorderSide(width: .5, color: Colors.grey), 173 | ), 174 | ), 175 | child: Center( 176 | child: Text( 177 | '专题留言', 178 | style: TextStyle( 179 | color: Colors.green, 180 | fontSize: Rem.getPxToRem(36), 181 | ), 182 | ), 183 | ), 184 | ), 185 | ), 186 | buildCommentList(), 187 | commentCount > 5 188 | ? Container( 189 | color: Colors.white, 190 | padding: EdgeInsets.symmetric(horizontal: 20), 191 | child: Row( 192 | children: [ 193 | Expanded( 194 | child: FlatButton( 195 | color: Colors.lightBlue[200], 196 | highlightColor: Colors.blue[700], 197 | colorBrightness: Brightness.dark, 198 | splashColor: Colors.grey, 199 | child: Text("更多"), 200 | shape: RoundedRectangleBorder( 201 | borderRadius: BorderRadius.circular(5.0)), 202 | onPressed: () { 203 | Router.push('/moreComment', context, 204 | {'id': widget.arguments['id']}); 205 | }, 206 | ), 207 | ), 208 | Container( 209 | width: 20, 210 | ), 211 | Expanded( 212 | child: FlatButton( 213 | color: Colors.lightBlue[200], 214 | highlightColor: Colors.blue[700], 215 | colorBrightness: Brightness.dark, 216 | splashColor: Colors.grey, 217 | child: Text("留言"), 218 | shape: RoundedRectangleBorder( 219 | borderRadius: BorderRadius.circular(5.0)), 220 | onPressed: () { 221 | toWrite(context); 222 | }, 223 | ), 224 | ), 225 | ], 226 | ), 227 | ) 228 | : Container( 229 | color: Colors.white, 230 | height: Rem.getPxToRem(120), 231 | width: double.infinity, 232 | padding: EdgeInsets.fromLTRB( 233 | Rem.getPxToRem(20), 234 | Rem.getPxToRem(8), 235 | Rem.getPxToRem(20), 236 | Rem.getPxToRem(8)), 237 | child: FlatButton( 238 | color: Colors.lightBlue[200], 239 | highlightColor: Colors.blue[700], 240 | colorBrightness: Brightness.dark, 241 | splashColor: Colors.grey, 242 | child: Text("我要留言"), 243 | onPressed: () { 244 | toWrite(context); 245 | }, 246 | ), 247 | ) 248 | ], 249 | ); 250 | }, childCount: 1), 251 | ); 252 | } 253 | 254 | SliverList buildDes() { 255 | return new SliverList( 256 | delegate: new SliverChildBuilderDelegate( 257 | (BuildContext context, int index) { 258 | return new Container( 259 | constraints: BoxConstraints( 260 | minHeight: 150, 261 | ), 262 | child: CachedNetworkImage( 263 | imageUrl: 'http:${content[index]}', 264 | fit: BoxFit.fitWidth, 265 | ), 266 | ); 267 | }, 268 | childCount: content.length, 269 | ), 270 | ); 271 | } 272 | 273 | @override 274 | Widget build(BuildContext context) { 275 | if (initLoading) { 276 | return Material( 277 | child: SafeArea( 278 | child: Center( 279 | child: SizedBox( 280 | width: 24.0, 281 | height: 24.0, 282 | child: CircularProgressIndicator(strokeWidth: 2.0)), 283 | ), 284 | ), 285 | ); 286 | } 287 | return LinearBar( 288 | child: CustomScrollView(slivers: [ 289 | buildDes(), 290 | buildComment(), 291 | ]), 292 | removePadding: true, 293 | title: '$title', 294 | appBarColor: Colors.green, 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /lib/router/writeComment/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 写评论 3 | * @Author: luoguoxiong 4 | * @Date: 2019-09-05 17:16:00 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class WriteComment extends StatefulWidget { 10 | WriteComment({this.arguments}); 11 | final Map arguments; 12 | @override 13 | State createState() { 14 | return _WriteComment(); 15 | } 16 | } 17 | 18 | class _WriteComment extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Material( 22 | child: Center( 23 | child: Text('写评论'), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/cache.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 本地存储工具 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'dart:async'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | /// 用来做shared_preferences的存储 10 | class SpUtil { 11 | static SpUtil _instance; 12 | static Future get instance async { 13 | return await getInstance(); 14 | } 15 | 16 | static SharedPreferences _spf; 17 | 18 | SpUtil._(); 19 | 20 | Future _init() async { 21 | _spf = await SharedPreferences.getInstance(); 22 | } 23 | 24 | // 避免重复初始化 25 | static Future getInstance() async { 26 | if (_instance == null) { 27 | _instance = new SpUtil._(); 28 | await _instance._init(); 29 | } 30 | return _instance; 31 | } 32 | 33 | static bool _beforeCheck() { 34 | if (_spf == null) { 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | // 判断是否存在数据 41 | bool hasKey(String key) { 42 | Set keys = getKeys(); 43 | return keys.contains(key); 44 | } 45 | 46 | Set getKeys() { 47 | if (_beforeCheck()) return null; 48 | return _spf.getKeys(); 49 | } 50 | 51 | get(String key) { 52 | if (_beforeCheck()) return null; 53 | return _spf.get(key); 54 | } 55 | 56 | getString(String key) { 57 | if (_beforeCheck()) return null; 58 | return _spf.getString(key); 59 | } 60 | 61 | Future putString(String key, String value) { 62 | if (_beforeCheck()) return null; 63 | return _spf.setString(key, value); 64 | } 65 | 66 | bool getBool(String key) { 67 | if (_beforeCheck()) return null; 68 | return _spf.getBool(key); 69 | } 70 | 71 | Future putBool(String key, bool value) { 72 | if (_beforeCheck()) return null; 73 | return _spf.setBool(key, value); 74 | } 75 | 76 | int getInt(String key) { 77 | if (_beforeCheck()) return null; 78 | return _spf.getInt(key); 79 | } 80 | 81 | Future putInt(String key, int value) { 82 | if (_beforeCheck()) return null; 83 | return _spf.setInt(key, value); 84 | } 85 | 86 | double getDouble(String key) { 87 | if (_beforeCheck()) return null; 88 | return _spf.getDouble(key); 89 | } 90 | 91 | Future putDouble(String key, double value) { 92 | if (_beforeCheck()) return null; 93 | return _spf.setDouble(key, value); 94 | } 95 | 96 | List getStringList(String key) { 97 | return _spf.getStringList(key); 98 | } 99 | 100 | Future putStringList(String key, List value) { 101 | if (_beforeCheck()) return null; 102 | return _spf.setStringList(key, value); 103 | } 104 | 105 | dynamic getDynamic(String key) { 106 | if (_beforeCheck()) return null; 107 | return _spf.get(key); 108 | } 109 | 110 | Future remove(String key) { 111 | if (_beforeCheck()) return null; 112 | return _spf.remove(key); 113 | } 114 | 115 | Future clear() { 116 | if (_beforeCheck()) return null; 117 | return _spf.clear(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/utils/help.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 常用的一些方法 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-29 17:25:44 5 | */ 6 | // 数组分组 7 | List listToSort({List toSort, int chunk}) { 8 | var newList = []; 9 | for (var i = 0; i < toSort.length; i += chunk) { 10 | var end = i + chunk > toSort.length ? toSort.length : i + chunk; 11 | newList.add(toSort.sublist(i, end)); 12 | } 13 | return newList; 14 | } 15 | -------------------------------------------------------------------------------- /lib/utils/http.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: Dio的封装 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'package:dio/dio.dart'; 7 | 8 | class HttpUtils { 9 | static Dio http; 10 | String baseApi = 'http://202.96.155.121:8888/api'; 11 | 12 | HttpUtils() { 13 | BaseOptions options = new BaseOptions( 14 | baseUrl: baseApi, 15 | ); 16 | http = new Dio(options); 17 | // 添加拦截器 18 | http.interceptors 19 | .add(InterceptorsWrapper(onRequest: (RequestOptions options) { 20 | return options; //continue 21 | }, onResponse: (Response response) { 22 | if (response.data['errno'] == 0) { 23 | return response.data['data']; 24 | } else { 25 | return null; 26 | } 27 | }, onError: (DioError e) { 28 | print(e); 29 | return e; //continue 30 | })); 31 | // 开启日志 32 | // http.interceptors.add(LogInterceptor(responseBody: false)); 33 | } 34 | 35 | Future get(String url, [Map params]) { 36 | return http.get(url, queryParameters: params == null ? {} : params); 37 | } 38 | 39 | Future post(String url, [Map params]) { 40 | return http.post(url, data: params == null ? {} : params); 41 | } 42 | 43 | Future postToken(String url, String token, [Map params]) { 44 | Dio req = setToken(token); 45 | return req.post(url, data: params == null ? {} : params); 46 | } 47 | 48 | Future getToken(String url, String token, [Map params]) { 49 | Dio req = setToken(token); 50 | return req.get(url, queryParameters: params == null ? {} : params); 51 | } 52 | 53 | setToken(String token) { 54 | BaseOptions options = new BaseOptions( 55 | baseUrl: baseApi, 56 | headers: { 57 | 'x-nideshop-token': token, 58 | }, 59 | ); 60 | Dio req = new Dio(options); 61 | // 添加拦截器 62 | req.interceptors 63 | .add(InterceptorsWrapper(onRequest: (RequestOptions options) { 64 | return options; //continue 65 | }, onResponse: (Response response) { 66 | if (response.data['errno'] == 0) { 67 | return response.data['data']; 68 | } else { 69 | return null; 70 | } 71 | }, onError: (DioError e) { 72 | print(e); 73 | return e; //continue 74 | })); 75 | return req; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/utils/rem.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: Rem适配 3 | * @Author: luoguoxiong 4 | * @Date: 2019-08-26 17:29:18 5 | */ 6 | import 'dart:ui' as ui; 7 | import 'package:flutter/material.dart'; 8 | 9 | class Rem { 10 | static double _designWidth = 750.0; 11 | static double _windowWidth; 12 | static bool _isInit = false; 13 | 14 | static double getPxToRem(val) { 15 | return _windowWidth * val / _designWidth; 16 | } 17 | 18 | static double getRemToPx(val) { 19 | return val * _designWidth / _windowWidth; 20 | } 21 | 22 | static setDesignWidth(val) { 23 | if (!_isInit) { 24 | _designWidth = val; 25 | _windowWidth = MediaQueryData.fromWindow(ui.window).size.width; 26 | _isInit = true; 27 | } else { 28 | // print('is init!!'); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.0.0" 32 | cached_network_image: 33 | dependency: "direct main" 34 | description: 35 | name: cached_network_image 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "2.2.0+1" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.1.3" 46 | clock: 47 | dependency: transitive 48 | description: 49 | name: clock 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.0.1" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.14.12" 60 | convert: 61 | dependency: transitive 62 | description: 63 | name: convert 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.1.1" 67 | crypto: 68 | dependency: transitive 69 | description: 70 | name: crypto 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "2.1.4" 74 | csslib: 75 | dependency: transitive 76 | description: 77 | name: csslib 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "0.16.1" 81 | cupertino_icons: 82 | dependency: "direct main" 83 | description: 84 | name: cupertino_icons 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "0.1.2" 88 | dio: 89 | dependency: "direct main" 90 | description: 91 | name: dio 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "3.0.9" 95 | english_words: 96 | dependency: "direct main" 97 | description: 98 | name: english_words 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "3.1.5" 102 | file: 103 | dependency: transitive 104 | description: 105 | name: file 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "5.2.1" 109 | flutter: 110 | dependency: "direct main" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_cache_manager: 115 | dependency: transitive 116 | description: 117 | name: flutter_cache_manager 118 | url: "https://pub.flutter-io.cn" 119 | source: hosted 120 | version: "1.4.1" 121 | flutter_html: 122 | dependency: "direct main" 123 | description: 124 | name: flutter_html 125 | url: "https://pub.flutter-io.cn" 126 | source: hosted 127 | version: "0.11.0" 128 | flutter_page_indicator: 129 | dependency: transitive 130 | description: 131 | name: flutter_page_indicator 132 | url: "https://pub.flutter-io.cn" 133 | source: hosted 134 | version: "0.0.3" 135 | flutter_swiper: 136 | dependency: "direct main" 137 | description: 138 | name: flutter_swiper 139 | url: "https://pub.flutter-io.cn" 140 | source: hosted 141 | version: "1.1.6" 142 | flutter_test: 143 | dependency: "direct dev" 144 | description: flutter 145 | source: sdk 146 | version: "0.0.0" 147 | flutter_webview_plugin: 148 | dependency: "direct main" 149 | description: 150 | name: flutter_webview_plugin 151 | url: "https://pub.flutter-io.cn" 152 | source: hosted 153 | version: "0.3.7" 154 | html: 155 | dependency: transitive 156 | description: 157 | name: html 158 | url: "https://pub.flutter-io.cn" 159 | source: hosted 160 | version: "0.14.0+2" 161 | http: 162 | dependency: transitive 163 | description: 164 | name: http 165 | url: "https://pub.flutter-io.cn" 166 | source: hosted 167 | version: "0.12.0+2" 168 | http_parser: 169 | dependency: transitive 170 | description: 171 | name: http_parser 172 | url: "https://pub.flutter-io.cn" 173 | source: hosted 174 | version: "3.1.3" 175 | image: 176 | dependency: transitive 177 | description: 178 | name: image 179 | url: "https://pub.flutter-io.cn" 180 | source: hosted 181 | version: "2.1.12" 182 | intl: 183 | dependency: transitive 184 | description: 185 | name: intl 186 | url: "https://pub.flutter-io.cn" 187 | source: hosted 188 | version: "0.16.1" 189 | matcher: 190 | dependency: transitive 191 | description: 192 | name: matcher 193 | url: "https://pub.flutter-io.cn" 194 | source: hosted 195 | version: "0.12.6" 196 | meta: 197 | dependency: transitive 198 | description: 199 | name: meta 200 | url: "https://pub.flutter-io.cn" 201 | source: hosted 202 | version: "1.1.8" 203 | path: 204 | dependency: transitive 205 | description: 206 | name: path 207 | url: "https://pub.flutter-io.cn" 208 | source: hosted 209 | version: "1.6.4" 210 | path_provider: 211 | dependency: transitive 212 | description: 213 | name: path_provider 214 | url: "https://pub.flutter-io.cn" 215 | source: hosted 216 | version: "1.6.11" 217 | path_provider_linux: 218 | dependency: transitive 219 | description: 220 | name: path_provider_linux 221 | url: "https://pub.flutter-io.cn" 222 | source: hosted 223 | version: "0.0.1+2" 224 | path_provider_macos: 225 | dependency: transitive 226 | description: 227 | name: path_provider_macos 228 | url: "https://pub.flutter-io.cn" 229 | source: hosted 230 | version: "0.0.4+3" 231 | path_provider_platform_interface: 232 | dependency: transitive 233 | description: 234 | name: path_provider_platform_interface 235 | url: "https://pub.flutter-io.cn" 236 | source: hosted 237 | version: "1.0.2" 238 | pedantic: 239 | dependency: transitive 240 | description: 241 | name: pedantic 242 | url: "https://pub.flutter-io.cn" 243 | source: hosted 244 | version: "1.9.0" 245 | petitparser: 246 | dependency: transitive 247 | description: 248 | name: petitparser 249 | url: "https://pub.flutter-io.cn" 250 | source: hosted 251 | version: "2.4.0" 252 | platform: 253 | dependency: transitive 254 | description: 255 | name: platform 256 | url: "https://pub.flutter-io.cn" 257 | source: hosted 258 | version: "2.2.1" 259 | plugin_platform_interface: 260 | dependency: transitive 261 | description: 262 | name: plugin_platform_interface 263 | url: "https://pub.flutter-io.cn" 264 | source: hosted 265 | version: "1.0.2" 266 | process: 267 | dependency: transitive 268 | description: 269 | name: process 270 | url: "https://pub.flutter-io.cn" 271 | source: hosted 272 | version: "3.0.13" 273 | provider: 274 | dependency: "direct main" 275 | description: 276 | name: provider 277 | url: "https://pub.flutter-io.cn" 278 | source: hosted 279 | version: "3.1.0" 280 | quiver: 281 | dependency: transitive 282 | description: 283 | name: quiver 284 | url: "https://pub.flutter-io.cn" 285 | source: hosted 286 | version: "2.1.3" 287 | rxdart: 288 | dependency: transitive 289 | description: 290 | name: rxdart 291 | url: "https://pub.flutter-io.cn" 292 | source: hosted 293 | version: "0.24.1" 294 | shared_preferences: 295 | dependency: "direct main" 296 | description: 297 | name: shared_preferences 298 | url: "https://pub.flutter-io.cn" 299 | source: hosted 300 | version: "0.4.3" 301 | sky_engine: 302 | dependency: transitive 303 | description: flutter 304 | source: sdk 305 | version: "0.0.99" 306 | source_span: 307 | dependency: transitive 308 | description: 309 | name: source_span 310 | url: "https://pub.flutter-io.cn" 311 | source: hosted 312 | version: "1.7.0" 313 | sqflite: 314 | dependency: transitive 315 | description: 316 | name: sqflite 317 | url: "https://pub.flutter-io.cn" 318 | source: hosted 319 | version: "1.3.1" 320 | sqflite_common: 321 | dependency: transitive 322 | description: 323 | name: sqflite_common 324 | url: "https://pub.flutter-io.cn" 325 | source: hosted 326 | version: "1.0.2+1" 327 | stack_trace: 328 | dependency: transitive 329 | description: 330 | name: stack_trace 331 | url: "https://pub.flutter-io.cn" 332 | source: hosted 333 | version: "1.9.3" 334 | stream_channel: 335 | dependency: transitive 336 | description: 337 | name: stream_channel 338 | url: "https://pub.flutter-io.cn" 339 | source: hosted 340 | version: "2.0.0" 341 | string_scanner: 342 | dependency: transitive 343 | description: 344 | name: string_scanner 345 | url: "https://pub.flutter-io.cn" 346 | source: hosted 347 | version: "1.0.5" 348 | synchronized: 349 | dependency: transitive 350 | description: 351 | name: synchronized 352 | url: "https://pub.flutter-io.cn" 353 | source: hosted 354 | version: "2.1.0+1" 355 | term_glyph: 356 | dependency: transitive 357 | description: 358 | name: term_glyph 359 | url: "https://pub.flutter-io.cn" 360 | source: hosted 361 | version: "1.1.0" 362 | test_api: 363 | dependency: transitive 364 | description: 365 | name: test_api 366 | url: "https://pub.flutter-io.cn" 367 | source: hosted 368 | version: "0.2.15" 369 | transformer_page_view: 370 | dependency: transitive 371 | description: 372 | name: transformer_page_view 373 | url: "https://pub.flutter-io.cn" 374 | source: hosted 375 | version: "0.1.6" 376 | transparent_image: 377 | dependency: "direct main" 378 | description: 379 | name: transparent_image 380 | url: "https://pub.flutter-io.cn" 381 | source: hosted 382 | version: "0.1.0" 383 | typed_data: 384 | dependency: transitive 385 | description: 386 | name: typed_data 387 | url: "https://pub.flutter-io.cn" 388 | source: hosted 389 | version: "1.1.6" 390 | uuid: 391 | dependency: transitive 392 | description: 393 | name: uuid 394 | url: "https://pub.flutter-io.cn" 395 | source: hosted 396 | version: "2.0.2" 397 | vector_math: 398 | dependency: transitive 399 | description: 400 | name: vector_math 401 | url: "https://pub.flutter-io.cn" 402 | source: hosted 403 | version: "2.0.8" 404 | xdg_directories: 405 | dependency: transitive 406 | description: 407 | name: xdg_directories 408 | url: "https://pub.flutter-io.cn" 409 | source: hosted 410 | version: "0.1.0" 411 | xml: 412 | dependency: transitive 413 | description: 414 | name: xml 415 | url: "https://pub.flutter-io.cn" 416 | source: hosted 417 | version: "3.6.1" 418 | sdks: 419 | dart: ">=2.7.0 <3.0.0" 420 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 421 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: easy_market 2 | description: A new Flutter project. 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.0.6 27 | dio: ^3.0.9 28 | transparent_image: ^0.1.0 29 | english_words: ^3.1.0 30 | cached_network_image: ^2.0.0-rc 31 | shared_preferences: ^0.4.3 32 | provider: ^3.1.0 33 | flutter_webview_plugin: ^0.3.7 34 | flutter_html: ^0.11.0 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | 40 | # For information on the generic Dart part of this file, see the 41 | # following page: https://dart.dev/tools/pub/pubspec 42 | 43 | # The following section is specific to Flutter. 44 | flutter: 45 | # The following line ensures that the Material Icons font is 46 | # included with your application, so that you can use the icons in 47 | # the material Icons class. 48 | uses-material-design: true 49 | assets: 50 | - assets/images/ 51 | # To add assets to your application, add an assets section, like this: 52 | # assets: 53 | # - images/a_dot_burr.jpeg 54 | # - images/a_dot_ham.jpeg 55 | # An image asset can refer to one or more resolution-specific "variants", see 56 | # https://flutter.dev/assets-and-images/#resolution-aware. 57 | # For details regarding adding assets from package dependencies, see 58 | # https://flutter.dev/assets-and-images/#from-packages 59 | # To add custom fonts to your application, add a fonts section here, 60 | # in this "flutter" section. Each entry in this list should have a 61 | # "family" key with the font family name, and a "fonts" key with a 62 | # list giving the asset and other descriptors for the font. For 63 | # example: 64 | # fonts: 65 | # - family: Schyler 66 | # fonts: 67 | # - asset: fonts/Schyler-Regular.ttf 68 | # - asset: fonts/Schyler-Italic.ttf 69 | # style: italic 70 | # - family: Trajan Pro 71 | # fonts: 72 | # - asset: fonts/TrajanPro.ttf 73 | # - asset: fonts/TrajanPro_Bold.ttf 74 | # weight: 700 75 | # 76 | # For details regarding fonts from package dependencies, 77 | # see https://flutter.dev/custom-fonts/#from-packages 78 | --------------------------------------------------------------------------------