├── .gitignore ├── .metadata ├── LICENSE ├── QRCode_258.png ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── noder │ │ │ │ └── flutter_weixin │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── 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 ├── common │ ├── event │ │ ├── HttpErrorEvent.dart │ │ └── ThemeChangeEvent.dart │ ├── net │ │ └── Code.dart │ └── style │ │ └── Style.dart ├── components │ ├── BottomNavigationBar │ │ ├── bar.dart │ │ └── demo.dart │ ├── ListState.dart │ ├── PullLoadWidget.dart │ ├── UserIconWidget.dart │ └── list_refresh.dart ├── main.dart ├── model │ ├── base.dart │ ├── contact.dart │ ├── conversation.dart │ └── find.dart ├── resources │ └── shared_preferences_keys.dart ├── routers │ ├── application.dart │ ├── router_handler.dart │ └── routers.dart ├── utils │ ├── net_utils.dart │ ├── provider.dart │ ├── shared_preferences.dart │ ├── sql.dart │ ├── style.dart │ └── util.dart └── views │ ├── 404.dart │ ├── contacts_detail_page.dart │ ├── contacts_group_chat_page.dart │ ├── contacts_new_friend_page.dart │ ├── contacts_page.dart │ ├── contacts_tags_page.dart │ ├── find_page.dart │ ├── home_chat_page.dart │ ├── home_page.dart │ ├── my_info_page.dart │ └── my_page.dart ├── pubspec.lock ├── pubspec.yaml ├── static ├── app.db ├── font │ ├── demo.css │ ├── demo_index.html │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.js │ ├── iconfont.svg │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.woff2 └── images │ ├── chat_bg.png │ ├── contact_group.png │ ├── contact_new_friend.png │ ├── contact_public_number.png │ ├── contact_tag.png │ ├── default_img.png │ ├── default_nor_avatar.png │ ├── dianxin.jpg │ ├── find_bottle.png │ ├── find_friend_circle.png │ ├── find_game.png │ ├── find_nearby.png │ ├── find_scan.png │ ├── find_shake.png │ ├── find_shop.png │ ├── find_show.png │ ├── find_sou.png │ ├── find_xcx.png │ ├── home_chat.jpg │ ├── main.gif │ ├── main2.gif │ ├── me_college.png │ ├── me_face.png │ ├── me_gallary.png │ ├── me_pay.png │ ├── me_setting.png │ ├── me_wallet.png │ ├── wechat_contact.jpg │ ├── wechat_contacts_detail.jpg │ ├── wechat_contacts_nf.jpg │ ├── wechat_contacts_x.jpg │ ├── wechat_find.jpg │ ├── wechat_home.jpg │ ├── wechat_me.jpg │ ├── xinwen.jpg │ ├── xsG11.png │ └── zushou.jpg └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/key.jks 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/key.properties 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: 035e0765cc575c3b455689c2402cce073d564fce 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 leeo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QRCode_258.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/QRCode_258.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_weixin 2 | 3 | A copy of WeiXin Flutter application. 4 | 用Flutter仿的微信UI 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 | ## android端下载地址(下载密码:123456) 36 | 37 | 38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /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.noder.flutter_weixin" 39 | minSdkVersion 16 40 | targetSdkVersion 28 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 44 | } 45 | 46 | signingConfigs { 47 | release { 48 | keyAlias keystoreProperties['keyAlias'] 49 | keyPassword keystoreProperties['keyPassword'] 50 | storeFile file(keystoreProperties['storeFile']) 51 | storePassword keystoreProperties['storePassword'] 52 | } 53 | } 54 | buildTypes { 55 | release { 56 | signingConfig signingConfigs.release 57 | } 58 | } 59 | } 60 | 61 | flutter { 62 | source '../..' 63 | } 64 | 65 | dependencies { 66 | testImplementation 'junit:junit:4.12' 67 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 68 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 69 | } 70 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 15 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/noder/flutter_weixin/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.noder.flutter_weixin; 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 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/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.3.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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /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 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/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/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_weixin 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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/common/event/HttpErrorEvent.dart: -------------------------------------------------------------------------------- 1 | 2 | class HttpErrorEvent { 3 | final int code; 4 | 5 | final String message; 6 | 7 | HttpErrorEvent(this.code, this.message); 8 | } 9 | -------------------------------------------------------------------------------- /lib/common/event/ThemeChangeEvent.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | 3 | class ThemeChangeEvent { 4 | final int color; 5 | 6 | ThemeChangeEvent(this.color); 7 | } 8 | 9 | class ThemeChangeHandle { 10 | static final EventBus eventBus = new EventBus(); 11 | 12 | static themeChangeHandle(color) { 13 | eventBus.fire(new ThemeChangeEvent(color)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/common/net/Code.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:flutter_weixin/common/event/HttpErrorEvent.dart'; 3 | ///错误编码 4 | class Code { 5 | ///网络错误 6 | static const NETWORK_ERROR = -1; 7 | 8 | ///网络超时 9 | static const NETWORK_TIMEOUT = -2; 10 | 11 | ///网络返回数据格式化一次 12 | static const NETWORK_JSON_EXCEPTION = -3; 13 | 14 | static const SUCCESS = 200; 15 | 16 | static final EventBus eventBus = new EventBus(); 17 | 18 | static errorHandleFunction(code, message, noTip) { 19 | if(noTip) { 20 | return message; 21 | } 22 | eventBus.fire(new HttpErrorEvent(code, message)); 23 | return message; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/common/style/Style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Style { 4 | static const int primaryValue = 0xffEDEDED; 5 | static const int primaryLightValue = 0xFFFFFFFF; 6 | static const int primaryDarkValue = 0xFF121917; 7 | static const MaterialColor primarySwatchDefault = const MaterialColor( 8 | primaryValue, 9 | const { 10 | 50: const Color(primaryLightValue), 11 | 100: const Color(primaryLightValue), 12 | 200: const Color(primaryLightValue), 13 | 300: const Color(primaryLightValue), 14 | 400: const Color(primaryLightValue), 15 | 500: const Color(primaryValue), 16 | 600: const Color(primaryDarkValue), 17 | 700: const Color(primaryDarkValue), 18 | 800: const Color(primaryDarkValue), 19 | 900: const Color(primaryDarkValue), 20 | }, 21 | ); 22 | static const MaterialColor primarySwatch = const MaterialColor( 23 | primaryLightValue, 24 | const { 25 | 50: const Color(primaryLightValue), 26 | 100: const Color(primaryLightValue), 27 | 200: const Color(primaryLightValue), 28 | 300: const Color(primaryLightValue), 29 | 400: const Color(primaryLightValue), 30 | 500: const Color(primaryValue), 31 | 600: const Color(primaryDarkValue), 32 | 700: const Color(primaryDarkValue), 33 | 800: const Color(primaryDarkValue), 34 | 900: const Color(primaryDarkValue), 35 | }, 36 | ); 37 | } 38 | 39 | class ICons { 40 | static const String FONT_FAMILY = 'wxIconFont'; 41 | 42 | static const String DEFAULT_USER_ICON = 'static/images/logo.png'; 43 | static const String DEFAULT_IMAGE = 'static/images/default_img.png'; 44 | static const IconData XIANSHIQI = const IconData( 45 | 0xe61d, fontFamily: ICons.FONT_FAMILY); 46 | 47 | static const IconData QR = const IconData( 48 | 0xe646, fontFamily: ICons.FONT_FAMILY); 49 | 50 | static const IconData HOME = const IconData( 51 | 0xe65d, fontFamily: ICons.FONT_FAMILY); 52 | static const IconData HOME_CHECKED = const IconData( 53 | 0xe619, fontFamily: ICons.FONT_FAMILY); 54 | 55 | static const IconData ADDRESS_BOOK = const IconData( 56 | 0xe711, fontFamily: ICons.FONT_FAMILY); 57 | static const IconData ADDRESS_BOOK_CHECKED = const IconData( 58 | 0xe687, fontFamily: ICons.FONT_FAMILY); 59 | 60 | static const IconData FOUND = const IconData( 61 | 0xe60f, fontFamily: ICons.FONT_FAMILY); 62 | static const IconData FOUND_CHECKED = const IconData( 63 | 0xe746, fontFamily: ICons.FONT_FAMILY); 64 | 65 | static const IconData WO = const IconData( 66 | 0xe626, fontFamily: ICons.FONT_FAMILY); 67 | static const IconData WO_CHECKED = const IconData( 68 | 0xe627, fontFamily: ICons.FONT_FAMILY); 69 | 70 | static const IconData PUSH_ITEM_EDIT = Icons.mode_edit; 71 | static const IconData PUSH_ITEM_ADD = Icons.add_box; 72 | static const IconData PUSH_ITEM_MIN = Icons.indeterminate_check_box; 73 | } 74 | -------------------------------------------------------------------------------- /lib/components/BottomNavigationBar/bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart'; 3 | import 'package:flutter_weixin/views/home_page.dart'; 4 | import 'package:flutter_weixin/views/contacts_page.dart'; 5 | import 'package:flutter_weixin/views/find_page.dart'; 6 | import 'package:flutter_weixin/views/my_page.dart'; 7 | import 'dart:async'; 8 | import 'package:flutter_weixin/common/event/ThemeChangeEvent.dart' 9 | show ThemeChangeEvent, ThemeChangeHandle; 10 | import 'package:barcode_scan/barcode_scan.dart'; 11 | import 'package:flutter/services.dart'; 12 | 13 | class Bar extends StatefulWidget { 14 | Bar({Key key}) : super(key: key); 15 | 16 | @override 17 | State createState() { 18 | // TODO: implement createState 19 | return _BarState(); 20 | } 21 | } 22 | 23 | class _BarState extends State { 24 | PageController _pageController; 25 | static String appBarTitle = tabData[0]['text']; 26 | static List tabData = [ 27 | {'text': '微信', 'icon': new Icon(ICons.HOME)}, 28 | {'text': '通讯录', 'icon': new Icon(ICons.ADDRESS_BOOK)}, 29 | {'text': '发现', 'icon': new Icon(ICons.FOUND)}, 30 | {'text': '我', 'icon': new Icon(ICons.WO)} 31 | ]; 32 | List pages = []; 33 | int _currentIndex = 0; 34 | StreamSubscription stream; 35 | static Color themeDef = Color(0xffEDEDED); 36 | 37 | @override 38 | void initState() { 39 | // TODO: implement initState 40 | super.initState(); 41 | _pageController = PageController(initialPage: _currentIndex); 42 | pages = [HomePage(), ContactsPage(), FindPage(), MyPage()]; 43 | stream = ThemeChangeHandle.eventBus 44 | .on() 45 | .listen((ThemeChangeEvent onData) { 46 | print('监听改变主题事件========='); 47 | this.changeTheme(onData); 48 | }); 49 | } 50 | 51 | /** 52 | * 刷新主题样式 53 | */ 54 | void changeTheme(ThemeChangeEvent onData) { 55 | setState(() { 56 | print(onData); 57 | themeDef = Color(onData.color); 58 | }); 59 | } 60 | 61 | void _onItemTapped(int index) { 62 | if (mounted) { 63 | setState(() { 64 | _currentIndex = index; 65 | appBarTitle = tabData[index]['text']; 66 | _pageController.animateToPage(index, 67 | duration: Duration(milliseconds: 1), curve: Curves.bounceIn); 68 | }); 69 | } 70 | } 71 | 72 | @override 73 | void dispose() { 74 | super.dispose(); 75 | if (stream != null) { 76 | stream.cancel(); 77 | stream = null; 78 | } 79 | } 80 | 81 | /// 单击提示退出 82 | Future _dialogExitApp(BuildContext context) { 83 | return showDialog( 84 | context: context, 85 | builder: (context) => new AlertDialog( 86 | content: new Text('确定要退出应用?'), 87 | actions: [ 88 | new FlatButton( 89 | onPressed: () => Navigator.of(context).pop(false), 90 | child: new Text( 91 | '取消', 92 | style: TextStyle(color: Colors.black54), 93 | )), 94 | new FlatButton( 95 | onPressed: () { 96 | Navigator.of(context).pop(true); 97 | }, 98 | child: 99 | new Text('确定', style: TextStyle(color: Colors.black54))) 100 | ], 101 | )); 102 | } 103 | static Future _scanQR() async { 104 | try { 105 | String qrResult = await BarcodeScanner.scan(); 106 | print(qrResult); 107 | } on PlatformException catch(ex) { 108 | if (ex.code == BarcodeScanner.CameraAccessDenied) { 109 | print(ex.code); 110 | } else { 111 | print(ex.code); 112 | } 113 | } on FormatException { 114 | print("pressed ths back button before scanning anyting"); 115 | } catch(ex){ 116 | print(ex); 117 | } 118 | } 119 | static _buildPopupMenuItem(IconData icon, String title) { 120 | return Row(children: [ 121 | Icon( 122 | icon, 123 | color: Color(0xFFFFFFFF), 124 | ), 125 | Container(width: 12.0), 126 | Text( 127 | title, 128 | style: TextStyle(color: Color(0xFFFFFFFF)), 129 | ) 130 | ]); 131 | } 132 | 133 | Widget defaultAppBar = AppBar( 134 | backgroundColor: themeDef, 135 | title: Text(appBarTitle), 136 | elevation: 0.0, 137 | actions: [ 138 | IconButton(icon: Icon(Icons.search), onPressed: () {}), 139 | Container(width: 14.0), 140 | PopupMenuButton( 141 | itemBuilder: (BuildContext context) { 142 | return >[ 143 | PopupMenuItem( 144 | child: _buildPopupMenuItem(ICons.HOME_CHECKED, '发起群聊'), 145 | value: "1", 146 | ), 147 | PopupMenuDivider(height: 1.0,), 148 | PopupMenuItem( 149 | child: _buildPopupMenuItem(ICons.ADDRESS_BOOK_CHECKED, '添加朋友'), 150 | value: "2", 151 | ), 152 | PopupMenuDivider(height: 1.0,), 153 | PopupMenuItem( 154 | child: _buildPopupMenuItem(Icons.camera_alt, '扫一扫'), 155 | value: "3", 156 | ), 157 | PopupMenuDivider(height: 1.0,), 158 | PopupMenuItem( 159 | child: _buildPopupMenuItem(Icons.playlist_add_check, '收付款'), 160 | value: "4", 161 | ), 162 | PopupMenuDivider(height: 1.0,), 163 | PopupMenuItem( 164 | child: _buildPopupMenuItem(Icons.message, '帮助与反馈'), 165 | value: "5", 166 | ) 167 | ]; 168 | }, 169 | padding: EdgeInsets.only(top: 0.0), 170 | elevation: 5.0, 171 | icon: Icon(Icons.add_circle_outline), 172 | onSelected: (String selected) { 173 | print(selected); 174 | switch (selected) { 175 | case '3' : _scanQR();break; 176 | } 177 | }, 178 | ), 179 | ], 180 | ); 181 | 182 | @override 183 | Widget build(BuildContext context) { 184 | // TODO: implement build 185 | return WillPopScope( 186 | child: Scaffold( 187 | appBar: _currentIndex != 3 ? defaultAppBar : null, 188 | body: PageView.builder( 189 | itemBuilder: (BuildContext context, int index) { 190 | return pages[index]; 191 | }, 192 | controller: _pageController, 193 | itemCount: pages.length, 194 | onPageChanged: (index) { 195 | setState(() { 196 | appBarTitle = tabData[index]['text']; 197 | _currentIndex = index; 198 | if (index == 3) { 199 | ThemeChangeHandle.themeChangeHandle(0xffFFFFFF); 200 | } else { 201 | ThemeChangeHandle.themeChangeHandle(0xffEDEDED); 202 | } 203 | }); 204 | }, 205 | ), 206 | bottomNavigationBar: BottomNavigationBar( 207 | type: BottomNavigationBarType.fixed, 208 | // BottomNavigationBarType 中定义的类型,有 fixed 和 shifting 两种类型 209 | iconSize: 24.0, 210 | // BottomNavigationBarItem 中 icon 的大小 211 | currentIndex: _currentIndex, 212 | // 当前所高亮的按钮index 213 | onTap: _onItemTapped, 214 | // 点击里面的按钮的回调函数,参数为当前点击的按钮 index 215 | fixedColor: Colors.green, 216 | // 如果 type 类型为 fixed,则通过 fixedColor 设置选中 item 的颜色 217 | items: [ 218 | BottomNavigationBarItem( 219 | title: Text("微信"), 220 | icon: Icon(ICons.HOME), 221 | activeIcon: Icon(ICons.HOME_CHECKED)), 222 | BottomNavigationBarItem( 223 | title: Text("通讯录"), 224 | icon: Icon(ICons.ADDRESS_BOOK), 225 | activeIcon: Icon(ICons.ADDRESS_BOOK_CHECKED)), 226 | BottomNavigationBarItem( 227 | title: Text("发现"), 228 | icon: Icon(ICons.FOUND), 229 | activeIcon: Icon(ICons.FOUND_CHECKED)), 230 | BottomNavigationBarItem( 231 | title: Text("我"), 232 | icon: Icon(ICons.WO), 233 | activeIcon: Icon(ICons.WO_CHECKED)), 234 | ], 235 | )), 236 | onWillPop: () { 237 | return _dialogExitApp(context); 238 | }); 239 | } 240 | 241 | /* errorHandleFunction(int code, message) { 242 | switch (code) { 243 | case Code.NETWORK_ERROR: 244 | Fluttertoast.showToast(msg: '网络错误'); 245 | break; 246 | case 401: 247 | Fluttertoast.showToast(msg: '[401错误可能: 未授权 \\ 授权登录失败 \\ 登录过期]'); 248 | break; 249 | case 403: 250 | Fluttertoast.showToast(msg: '403权限错误'); 251 | break; 252 | case 404: 253 | Fluttertoast.showToast(msg: '404错误'); 254 | break; 255 | case Code.NETWORK_TIMEOUT: 256 | //超时 257 | Fluttertoast.showToast(msg: '请求超时'); 258 | break; 259 | default: 260 | Fluttertoast.showToast(msg: '其他异常'); 261 | break; 262 | } 263 | }*/ 264 | } 265 | -------------------------------------------------------------------------------- /lib/components/BottomNavigationBar/demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart'; 3 | 4 | /* 5 | * BottomNavigationBar 默认的实例 6 | * */ 7 | class BottomNavigationBarFullDefault extends StatefulWidget { 8 | const BottomNavigationBarFullDefault() : super(); 9 | 10 | @override 11 | State createState() => _BottomNavigationBarFullDefault(); 12 | } 13 | 14 | /* 15 | * BottomNavigationBar 默认的实例,有状态 16 | * */ 17 | class _BottomNavigationBarFullDefault extends State { 18 | int _currentIndex = 1; 19 | 20 | void _onItemTapped(int index) { 21 | if (mounted) { 22 | setState(() { 23 | _currentIndex = index; 24 | }); 25 | } 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return BottomNavigationBar( 31 | type: BottomNavigationBarType.fixed, 32 | // BottomNavigationBarType 中定义的类型,有 fixed 和 shifting 两种类型 33 | iconSize: 24.0, 34 | // BottomNavigationBarItem 中 icon 的大小 35 | currentIndex: _currentIndex, 36 | // 当前所高亮的按钮index 37 | onTap: _onItemTapped, 38 | // 点击里面的按钮的回调函数,参数为当前点击的按钮 index 39 | fixedColor: Colors.green, 40 | // 如果 type 类型为 fixed,则通过 fixedColor 设置选中 item 的颜色 41 | items: [ 42 | BottomNavigationBarItem( 43 | title: Text("微信"), 44 | icon: Icon(ICons.HOME), 45 | activeIcon: Icon(ICons.HOME_CHECKED)), 46 | BottomNavigationBarItem( 47 | title: Text("通讯录"), 48 | icon: Icon(ICons.ADDRESS_BOOK), 49 | activeIcon: Icon(ICons.ADDRESS_BOOK_CHECKED)), 50 | BottomNavigationBarItem( 51 | title: Text("发现"), 52 | icon: Icon(ICons.FOUND), 53 | activeIcon: Icon(ICons.FOUND_CHECKED)), 54 | BottomNavigationBarItem( 55 | title: Text("我"), 56 | icon: Icon(ICons.WO), 57 | activeIcon: Icon(ICons.WO_CHECKED)), 58 | ], 59 | ); 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /lib/components/ListState.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_weixin/components/PullLoadWidget.dart'; 4 | 5 | /** 6 | * 上下拉刷新列表的通用State 7 | */ 8 | mixin ListState 9 | < 10 | T extends StatefulWidget> 11 | 12 | on State 13 | < 14 | T>, 15 | AutomaticKeepAliveClientMixin 16 | { 17 | bool isShow = false; 18 | 19 | bool isLoading = false; 20 | 21 | int page = 1; 22 | 23 | final List dataList = new List(); 24 | 25 | final PullLoadWidgetControl pullLoadWidgetControl = new PullLoadWidgetControl(); 26 | 27 | final GlobalKey refreshIndicatorKey = new GlobalKey(); 28 | 29 | showRefreshLoading() { 30 | new Future.delayed(const Duration(seconds: 0), () { 31 | refreshIndicatorKey.currentState.show ().then((e) {}); 32 | return true; 33 | }); 34 | } 35 | 36 | @protected 37 | resolveRefreshResult(res) { 38 | if (res != null && res.result) { 39 | pullLoadWidgetControl.dataList.clear(); 40 | if (isShow) { 41 | setState(() { 42 | pullLoadWidgetControl.dataList.addAll(res.data); 43 | }); 44 | } 45 | } 46 | } 47 | 48 | @protected 49 | Future handleRefresh() async { 50 | if (isLoading) { 51 | return null; 52 | } 53 | isLoading = true; 54 | page = 1; 55 | var res = await requestRefresh(); 56 | resolveRefreshResult(res); 57 | resolveDataResult(res); 58 | if (res.next != null) { 59 | var resNext = await res.next; 60 | resolveRefreshResult(resNext); 61 | resolveDataResult(resNext); 62 | } 63 | isLoading = false; 64 | return null; 65 | } 66 | 67 | @protected 68 | Future onLoadMore() async { 69 | if (isLoading) { 70 | return null; 71 | } 72 | isLoading = true; 73 | page++; 74 | var res = await requestLoadMore(); 75 | if (res != null && res.result) { 76 | if (isShow) { 77 | setState(() { 78 | pullLoadWidgetControl.dataList.addAll(res.data); 79 | }); 80 | } 81 | } 82 | resolveDataResult(res); 83 | isLoading = false; 84 | return null; 85 | } 86 | 87 | @protected 88 | resolveDataResult(res) { 89 | if (isShow) { 90 | setState(() { 91 | pullLoadWidgetControl.needLoadMore = (res != null && res.data != null && res.data.length == 20); 92 | }); 93 | } 94 | } 95 | 96 | @protected 97 | clearData() { 98 | if (isShow) { 99 | setState(() { 100 | pullLoadWidgetControl.dataList.clear(); 101 | }); 102 | } 103 | } 104 | 105 | ///下拉刷新数据 106 | @protected 107 | requestRefresh() async {} 108 | 109 | ///上拉更多请求数据 110 | @protected 111 | requestLoadMore() async {} 112 | 113 | ///是否需要第一次进入自动刷新 114 | @protected 115 | bool get isRefreshFirst; 116 | 117 | ///是否需要头部 118 | @protected 119 | bool get needHeader => false; 120 | 121 | ///是否需要保持 122 | @override 123 | bool get wantKeepAlive => true; 124 | 125 | List get getDataList => dataList; 126 | 127 | @override 128 | void initState() { 129 | isShow = true; 130 | super.initState(); 131 | pullLoadWidgetControl.needHeader = needHeader; 132 | pullLoadWidgetControl.dataList = getDataList; 133 | if (pullLoadWidgetControl.dataList.length == 0 && isRefreshFirst) { 134 | showRefreshLoading(); 135 | } 136 | } 137 | 138 | @override 139 | void dispose() { 140 | isShow = false; 141 | isLoading = false; 142 | super.dispose(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/components/PullLoadWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | 4 | ///通用下上刷新控件 5 | class PullLoadWidget extends StatefulWidget { 6 | ///item渲染 7 | final IndexedWidgetBuilder itemBuilder; 8 | 9 | ///加载更多回调 10 | final RefreshCallback onLoadMore; 11 | 12 | ///下拉刷新回调 13 | final RefreshCallback onRefresh; 14 | 15 | ///控制器,比如数据和一些配置 16 | final PullLoadWidgetControl control; 17 | 18 | final Key refreshKey; 19 | 20 | PullLoadWidget(this.control, this.itemBuilder, this.onRefresh, 21 | this.onLoadMore, 22 | {this.refreshKey}); 23 | 24 | @override 25 | _PullLoadWidgetState createState() => 26 | _PullLoadWidgetState(this.control, 27 | this.itemBuilder, this.onRefresh, this.onLoadMore, this.refreshKey); 28 | } 29 | 30 | class _PullLoadWidgetState extends State { 31 | final IndexedWidgetBuilder itemBuilder; 32 | 33 | final RefreshCallback onLoadMore; 34 | 35 | final RefreshCallback onRefresh; 36 | 37 | final Key refreshKey; 38 | 39 | PullLoadWidgetControl control; 40 | 41 | _PullLoadWidgetState(this.control, this.itemBuilder, this.onRefresh, 42 | this.onLoadMore, this.refreshKey); 43 | 44 | final ScrollController _scrollController = new ScrollController(); 45 | 46 | @override 47 | void initState() { 48 | ///增加滑动监听 49 | _scrollController.addListener(() { 50 | ///判断当前滑动位置是不是到达底部,触发加载更多回调 51 | if (_scrollController.position.pixels == 52 | _scrollController.position.maxScrollExtent) { 53 | if (this.control.needLoadMore) { 54 | this.onLoadMore?.call(); 55 | } 56 | } 57 | }); 58 | super.initState(); 59 | } 60 | 61 | ///根据配置状态返回实际列表数量 62 | ///实际上这里可以根据你的需要做更多的处理 63 | ///比如多个头部,是否需要空页面,是否需要显示加载更多。 64 | _getListCount() { 65 | ///是否需要头部 66 | if (control.needHeader) { 67 | ///如果需要头部,用Item 0 的 Widget 作为ListView的头部 68 | ///列表数量大于0时,因为头部和底部加载更多选项,需要对列表数据总数+2 69 | return (control.dataList.length > 0) 70 | ? control.dataList.length + 2 71 | : control.dataList.length + 1; 72 | } else { 73 | ///如果不需要头部,在没有数据时,固定返回数量1用于空页面呈现 74 | if (control.dataList.length == 0) { 75 | return 1; 76 | } 77 | 78 | ///如果有数据,因为部加载更多选项,需要对列表数据总数+1 79 | return (control.dataList.length > 0) 80 | ? control.dataList.length + 1 81 | : control.dataList.length; 82 | } 83 | } 84 | 85 | ///根据配置状态返回实际列表渲染Item 86 | _getItem(int index) { 87 | if (!control.needHeader && 88 | index == control.dataList.length && 89 | control.dataList.length != 0) { 90 | ///如果不需要头部,并且数据不为0,当index等于数据长度时,渲染加载更多Item(因为index是从0开始) 91 | return _buildProgressIndicator(); 92 | } else if (control.needHeader && 93 | index == _getListCount() - 1 && 94 | control.dataList.length != 0) { 95 | ///如果需要头部,并且数据不为0,当index等于实际渲染长度 - 1时,渲染加载更多Item(因为index是从0开始) 96 | return _buildProgressIndicator(); 97 | } else if (!control.needHeader && control.dataList.length == 0) { 98 | ///如果不需要头部,并且数据为0,渲染空页面 99 | return _buildEmpty(); 100 | } else { 101 | ///回调外部正常渲染Item,如果这里有需要,可以直接返回相对位置的index 102 | return itemBuilder(context, index); 103 | } 104 | } 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | return new RefreshIndicator( 109 | 110 | ///GlobalKey,用户外部获取RefreshIndicator的State,做显示刷新 111 | key: refreshKey, 112 | 113 | ///下拉刷新触发,返回的是一个Future 114 | onRefresh: onRefresh, 115 | child: new ListView.builder( 116 | 117 | ///保持ListView任何情况都能滚动,解决在RefreshIndicator的兼容问题。 118 | physics: const AlwaysScrollableScrollPhysics(), 119 | 120 | ///根据状态返回子孔健 121 | itemBuilder: (context, index) { 122 | return _getItem(index); 123 | }, 124 | 125 | ///根据状态返回数量 126 | itemCount: _getListCount(), 127 | 128 | ///滑动监听 129 | controller: _scrollController, 130 | ), 131 | ); 132 | } 133 | 134 | ///空页面 135 | Widget _buildEmpty() { 136 | return new Container( 137 | height: MediaQuery 138 | .of(context) 139 | .size 140 | .height - 100, 141 | child: new Column( 142 | mainAxisAlignment: MainAxisAlignment.center, 143 | children: [ 144 | FlatButton( 145 | onPressed: () {}, 146 | child: new Image( 147 | image: new AssetImage('static/images/default_img.png'), 148 | width: 70.0, 149 | height: 70.0), 150 | ), 151 | Container( 152 | child: Text('暂无数据', style: TextStyle(color: Color(0xFF121917))), 153 | ), 154 | ], 155 | ), 156 | ); 157 | } 158 | 159 | ///上拉加载更多 160 | Widget _buildProgressIndicator() { 161 | ///是否需要显示上拉加载更多的loading 162 | Widget bottomWidget = (control.needLoadMore) 163 | ? new Row( 164 | mainAxisAlignment: MainAxisAlignment.center, 165 | children: [ 166 | 167 | ///loading框 168 | new SpinKitRotatingCircle( 169 | color: Theme 170 | .of(context) 171 | .primaryColor, 172 | size: 25.0, 173 | ), 174 | new Container( 175 | width: 5.0, 176 | ), 177 | 178 | ///加载中文本 179 | new Text( 180 | '正在加载中', 181 | style: TextStyle( 182 | color: Color(0xFF121917), 183 | fontSize: 14.0, 184 | fontWeight: FontWeight.bold, 185 | ), 186 | ) 187 | ]) 188 | 189 | /// 不需要加载 190 | : new Container( 191 | child: new Text( 192 | '已经到底了', 193 | style: TextStyle( 194 | color: Colors.grey, 195 | fontSize: 12.0, 196 | ), 197 | ), 198 | ); 199 | return new Padding( 200 | padding: const EdgeInsets.all(20.0), 201 | child: new Center( 202 | child: bottomWidget, 203 | ), 204 | ); 205 | } 206 | } 207 | 208 | class PullLoadWidgetControl { 209 | ///数据,对齐增减,不能替换 210 | List dataList = new List(); 211 | 212 | ///是否需要加载更多 213 | bool needLoadMore = true; 214 | 215 | ///是否需要头部 216 | bool needHeader = false; 217 | } 218 | -------------------------------------------------------------------------------- /lib/components/UserIconWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /** 4 | * 头像Icon 5 | */ 6 | 7 | class UserIconWidget extends StatelessWidget { 8 | final bool isNetwork; 9 | final String image; 10 | final VoidCallback onPressed; 11 | final double width; 12 | final double height; 13 | final EdgeInsetsGeometry padding; 14 | 15 | UserIconWidget( 16 | {this.isNetwork, this.image, this.onPressed, this.width = 30.0, this.height = 30.0, this.padding}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new RawMaterialButton( 21 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 22 | padding: padding ?? 23 | const EdgeInsets.only(top: 4.0, right: 5.0, left: 5.0), 24 | constraints: const BoxConstraints(minWidth: 0.0, minHeight: 0.0), 25 | child: 26 | new ClipRRect( 27 | borderRadius: new BorderRadius.all( 28 | new Radius.circular(5.0)), 29 | child: this.isNetwork ? new FadeInImage.assetNetwork( 30 | placeholder: 'static/images/default_nor_avatar.png', 31 | //预览图 32 | fit: BoxFit.fitWidth, 33 | image: image, 34 | width: width, 35 | height: height, 36 | ) : new Image.asset( 37 | image, 38 | fit: BoxFit.cover, 39 | width: width, 40 | height: height, 41 | ), 42 | ), 43 | onPressed: onPressed); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/components/list_refresh.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | class ListRefresh extends StatefulWidget { 5 | final renderItem; 6 | final requestApi; 7 | final headerView; 8 | 9 | const ListRefresh([this.requestApi, this.renderItem, this.headerView]) 10 | : super(); 11 | 12 | @override 13 | State createState() => _ListRefreshState(); 14 | } 15 | 16 | class _ListRefreshState extends State { 17 | bool isLoading = false; // 是否正在请求数据中 18 | bool _hasMore = true; // 是否还有更多数据可加载 19 | int _pageIndex = 0; // 页面的索引 20 | int _pageTotal = 0; // 页面的索引 21 | List items = new List(); 22 | ScrollController _scrollController = new ScrollController(); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _getMoreData(); 28 | _scrollController.addListener(() { 29 | // 如果下拉的当前位置到scroll的最下面 30 | if (_scrollController.position.pixels == 31 | _scrollController.position.maxScrollExtent) { 32 | _getMoreData(); 33 | } 34 | }); 35 | } 36 | 37 | /* 38 | * 回弹效果 39 | * */ 40 | backElasticEffect() { 41 | // double edge = 50.0; 42 | // double offsetFromBottom = _scrollController.position.maxScrollExtent - _scrollController.position.pixels; 43 | // if (offsetFromBottom < edge) { // 添加一个动画没有更多数据的时候 ListView 向下移动覆盖正在加载更多数据的标志 44 | // _scrollController.animateTo( 45 | // _scrollController.offset - (edge -offsetFromBottom), 46 | // duration: new Duration(milliseconds: 1000), 47 | // curve: Curves.easeOut); 48 | // } 49 | } 50 | 51 | /* 52 | * list探底,执行的具体事件 53 | * */ 54 | Future _getMoreData() async { 55 | if (!isLoading && _hasMore) { 56 | // 如果上一次异步请求数据完成 同时有数据可以加载 57 | if (mounted) { 58 | setState(() => isLoading = true); 59 | } 60 | //if(_hasMore){ // 还有数据可以拉新 61 | List newEntries = await mokeHttpRequest(); 62 | //if (newEntries.isEmpty) { 63 | _hasMore = (_pageIndex <= _pageTotal); 64 | if (this.mounted) { 65 | setState(() { 66 | items.addAll(newEntries); 67 | isLoading = false; 68 | }); 69 | } 70 | backElasticEffect(); 71 | } else if (!isLoading && !_hasMore) { 72 | // 这样判断,减少以后的绘制 73 | _pageIndex = 0; 74 | backElasticEffect(); 75 | } 76 | } 77 | 78 | /* 79 | * 伪装吐出新数据 80 | * */ 81 | Future mokeHttpRequest() async { 82 | if (widget.requestApi is Function) { 83 | final listObj = await widget.requestApi({'pageIndex': _pageIndex}); 84 | _pageIndex = listObj['pageIndex']; 85 | _pageTotal = listObj['total']; 86 | return listObj['list']; 87 | } else { 88 | return Future.delayed(Duration(seconds: 2), () { 89 | return []; 90 | }); 91 | } 92 | } 93 | 94 | /* 95 | * 下拉加载的事件,清空之前list内容,取前X个 96 | * 其实就是列表重置 97 | * */ 98 | Future _handleRefresh() async { 99 | List newEntries = await mokeHttpRequest(); 100 | if (this.mounted) { 101 | setState(() { 102 | items.clear(); 103 | items.addAll(newEntries); 104 | isLoading = false; 105 | _hasMore = true; 106 | return null; 107 | }); 108 | } 109 | } 110 | 111 | /* 112 | * 加载中的提示 113 | * */ 114 | Widget _buildLoadText() { 115 | return Container( 116 | child: Padding( 117 | padding: const EdgeInsets.all(18.0), 118 | child: Center( 119 | child: Text("数据没有更多了!!!"), 120 | ), 121 | )); 122 | } 123 | 124 | /* 125 | * 上提加载loading的widget,如果数据到达极限,显示没有更多 126 | * */ 127 | Widget _buildProgressIndicator() { 128 | if (_hasMore) { 129 | return new Padding( 130 | padding: const EdgeInsets.all(8.0), 131 | child: new Center( 132 | child: Column( 133 | children: [ 134 | new Opacity( 135 | opacity: isLoading ? 1.0 : 0.0, 136 | child: new CircularProgressIndicator( 137 | valueColor: AlwaysStoppedAnimation(Colors.blue)), 138 | ), 139 | SizedBox(height: 20.0), 140 | Text( 141 | '稍等片刻更精彩...', 142 | style: TextStyle(fontSize: 14.0), 143 | ) 144 | ], 145 | ) 146 | //child: 147 | ), 148 | ); 149 | } else { 150 | return _buildLoadText(); 151 | } 152 | } 153 | 154 | @override 155 | void dispose() { 156 | super.dispose(); 157 | _scrollController.dispose(); 158 | } 159 | 160 | @override 161 | Widget build(BuildContext context) { 162 | return new RefreshIndicator( 163 | child: ListView.builder( 164 | itemCount: items.length + 1, 165 | itemBuilder: (context, index) { 166 | if (index == 0 && index != items.length) { 167 | if (widget.headerView is Function) { 168 | return widget.headerView(); 169 | } else { 170 | return Container(height: 0); 171 | } 172 | } 173 | if (index == items.length) { 174 | //return _buildLoadText(); 175 | return _buildProgressIndicator(); 176 | } else { 177 | //print('itemsitemsitemsitems:${items[index].title}'); 178 | //return ListTile(title: Text("Index${index}:${items[index].title}")); 179 | if (widget.renderItem is Function) { 180 | return widget.renderItem(index, items[index]); 181 | } 182 | } 183 | }, 184 | controller: _scrollController, 185 | ), 186 | onRefresh: _handleRefresh, 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'components/BottomNavigationBar/bar.dart'; 3 | import 'routers/routers.dart'; 4 | import 'package:fluro/fluro.dart'; 5 | import 'routers/application.dart'; 6 | import 'package:flutter_weixin/utils/provider.dart'; 7 | import 'package:flutter_weixin/utils/shared_preferences.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'dart:io' show Platform; 10 | 11 | 12 | SpUtil sp; 13 | var db; 14 | 15 | void main() async { 16 | final provider = new Provider(); 17 | await provider.init(true); 18 | sp = await SpUtil.getInstance(); 19 | db = Provider.db; 20 | if (Platform.isAndroid) { 21 | // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。 22 | SystemUiOverlayStyle systemUiOverlayStyle = 23 | SystemUiOverlayStyle(statusBarColor: Colors.transparent); 24 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 25 | } 26 | runApp(new MyApp()); 27 | } 28 | 29 | Color themeDef = Color(0xffEDEDED); 30 | 31 | class MyApp extends StatelessWidget { 32 | MyApp() { 33 | final router = new Router(); 34 | Routes.configureRoutes(router); 35 | Application.router = router; 36 | } 37 | 38 | // This widget is the root of your application. 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | title: 'Flutter WeiXin Demo', 43 | theme: ThemeData(primaryColor: themeDef, cardColor: Color(0xff4C4C4C)), 44 | home: new Scaffold(body: Bar()), 45 | onGenerateRoute: Application.router.generator, 46 | ); 47 | } 48 | } 49 | 50 | /*class MyHomePage extends StatefulWidget { 51 | MyHomePage({Key key, this.title}) : super(key: key); 52 | 53 | final String title; 54 | 55 | @override 56 | _MyHomePageState createState() => _MyHomePageState(); 57 | }*/ 58 | 59 | /*class _MyHomePageState extends State { 60 | 61 | 62 | @override 63 | void initState() { 64 | // TODO: implement initState 65 | super.initState(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return BottomNavigationBarFullDefault(); 71 | } 72 | 73 | errorHandleFunction(int code, message) { 74 | switch (code) { 75 | case Code.NETWORK_ERROR: 76 | Fluttertoast.showToast(msg: '网络错误'); 77 | break; 78 | case 401: 79 | Fluttertoast.showToast(msg: '[401错误可能: 未授权 \\ 授权登录失败 \\ 登录过期]'); 80 | break; 81 | case 403: 82 | Fluttertoast.showToast(msg: '403权限错误'); 83 | break; 84 | case 404: 85 | Fluttertoast.showToast(msg: '404错误'); 86 | break; 87 | case Code.NETWORK_TIMEOUT: 88 | //超时 89 | Fluttertoast.showToast(msg: '请求超时'); 90 | break; 91 | default: 92 | Fluttertoast.showToast(msg: '其他异常'); 93 | break; 94 | } 95 | } 96 | }*/ 97 | -------------------------------------------------------------------------------- /lib/model/base.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | 3 | 4 | class BaseModel { 5 | Database db; 6 | final String table = ''; 7 | var query; 8 | 9 | BaseModel(this.db) { 10 | query = db.query; 11 | } 12 | } -------------------------------------------------------------------------------- /lib/model/contact.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Contact { 4 | const Contact({ 5 | @required this.avatar, 6 | @required this.name, 7 | this.nameIndex, 8 | this.isNetwork = false 9 | }) 10 | : assert(avatar != null), 11 | assert(name != null); 12 | final String avatar; 13 | final String name; 14 | final String nameIndex; 15 | final bool isNetwork; 16 | 17 | factory Contact.fromJson(Map json) { 18 | return Contact( 19 | avatar: json['picture']['thumbnail'], 20 | name: json['name']['first'] + ' ' + json['name']['last'], 21 | nameIndex: json['name']['first'].toString().substring(0, 1), 22 | isNetwork: true 23 | ); 24 | } 25 | } 26 | 27 | List mockContact = []; 28 | List preContact = [ 29 | const Contact(avatar: 'static/images/contact_new_friend.png', name: '新的朋友'), 30 | const Contact(avatar: 'static/images/contact_group.png', name: '群聊'), 31 | const Contact(avatar: 'static/images/contact_tag.png', name: '标签'), 32 | const Contact(avatar: 'static/images/contact_public_number.png', name: '公众号') 33 | ]; 34 | -------------------------------------------------------------------------------- /lib/model/conversation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/utils/util.dart'; 3 | import 'package:flutter_weixin/utils/sql.dart'; 4 | 5 | class Conversation { 6 | const Conversation({@required this.avatar, 7 | @required this.title, 8 | @required this.createAt, 9 | this.isMute: false, 10 | this.titleColor: 0xff353535, 11 | this.describtion, 12 | this.unReadMsgCount: 0, 13 | this.displayDot: false, 14 | this.isNetwork: false}) 15 | : assert(avatar != null), 16 | assert(title != null), 17 | assert(createAt != null); 18 | final String avatar; 19 | final String title; 20 | final String describtion; 21 | final String createAt; 22 | final bool isMute; 23 | final int titleColor; 24 | final int unReadMsgCount; 25 | final bool displayDot; 26 | final bool isNetwork; 27 | 28 | factory Conversation.fromJson(Map json) { 29 | return Conversation( 30 | avatar: json['picture']['thumbnail'], 31 | createAt: Util.getTimeDuration(json['registered']['date']), 32 | title: json['name']['first'] + ' ' + json['name']['last'], 33 | describtion: json['location']['timezone']['description'], 34 | unReadMsgCount: json['unReadMsgCount'], 35 | isNetwork: true); 36 | } 37 | } 38 | 39 | class ConversationControlModel { 40 | 41 | final String table = 'conversation'; 42 | Sql sql; 43 | 44 | ConversationControlModel() { 45 | sql = Sql.setTable(table); 46 | } 47 | 48 | Future clear() { 49 | return sql.clearTable(table); 50 | } 51 | 52 | Future insert(Conversation conversation) async { 53 | var response = await sql.insert( 54 | {'avatar': conversation.avatar, 'name': conversation.title}); 55 | return response; 56 | } 57 | 58 | Future> getAllConversation() async { 59 | List list = await sql.getByCondition(); 60 | List resultList = []; 61 | list.forEach((item) { 62 | resultList.add(Conversation.fromJson(item)); 63 | }); 64 | return resultList; 65 | } 66 | } 67 | 68 | List mockConversation = []; 69 | List preConversation = [ 70 | const Conversation( 71 | avatar: '', 72 | title: '', 73 | createAt: '', 74 | describtion: ''), 75 | const Conversation( 76 | avatar: 'static/images/zushou.jpg', 77 | title: '文件传输助手', 78 | createAt: '08:12', 79 | describtion: ''), 80 | const Conversation( 81 | avatar: 'static/images/xinwen.jpg', 82 | title: '腾讯新闻', 83 | createAt: '12:09', 84 | describtion: '微视频:人民代表xxx履职记', 85 | displayDot: true), 86 | const Conversation( 87 | avatar: 'static/images/dianxin.jpg', 88 | title: '中国电信', 89 | createAt: '14:01', 90 | describtion: '月度账单提醒', 91 | unReadMsgCount: 1), 92 | ]; 93 | 94 | class Manager { 95 | // 工厂模式 96 | factory Manager() => _getInstance(); 97 | 98 | static Manager get instance => _getInstance(); 99 | static Manager _instance; 100 | bool _hasNewData = false; 101 | 102 | Manager._internal() { 103 | // 初始化 104 | } 105 | 106 | static Manager _getInstance() { 107 | if (_instance == null) { 108 | _instance = new Manager._internal(); 109 | } 110 | return _instance; 111 | } 112 | 113 | setSate(bool hasNewData) { 114 | this._hasNewData = hasNewData; 115 | } 116 | 117 | getState() { 118 | return this._hasNewData; 119 | } 120 | } -------------------------------------------------------------------------------- /lib/model/find.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Find { 4 | const Find( 5 | {@required this.avatar, @required this.name, this.isNetwork = false, this.isWhite = false}) 6 | : assert(avatar != null), 7 | assert(name != null); 8 | final String avatar; 9 | final String name; 10 | final bool isNetwork; 11 | final bool isWhite; 12 | } 13 | 14 | List mockContact = [ 15 | const Find(avatar: 'static/images/find_friend_circle.png', name: '朋友圈'), 16 | const Find(avatar: 'static/images/find_scan.png', name: '扫一扫', isWhite: true), 17 | const Find(avatar: 'static/images/find_shake.png', name: '摇一摇'), 18 | const Find(avatar: 'static/images/find_show.png', name: '看一看', isWhite: true), 19 | const Find(avatar: 'static/images/find_sou.png', name: '搜一搜'), 20 | const Find(avatar: 'static/images/find_nearby.png', name: '附近的人', isWhite: true), 21 | const Find(avatar: 'static/images/find_bottle.png', name: '漂流瓶'), 22 | const Find(avatar: 'static/images/find_shop.png', name: '购物', isWhite: true), 23 | const Find(avatar: 'static/images/find_game.png', name: '游戏'), 24 | const Find(avatar: 'static/images/find_xcx.png', name: '小程序', isWhite: true) 25 | ]; 26 | -------------------------------------------------------------------------------- /lib/resources/shared_preferences_keys.dart: -------------------------------------------------------------------------------- 1 | class sharedPreferencesKeys { 2 | /// boolean 3 | /// 用于欢迎页面. 只有第一次访问才会显示. 或者手动将这个值设为false 4 | static String showWelcome = 'loginWelcone'; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /lib/routers/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_weixin/utils/shared_preferences.dart'; 4 | 5 | class Application { 6 | static Router router; 7 | static TabController controller; 8 | static SpUtil sharePeferences; 9 | 10 | static Map github = { 11 | }; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /lib/routers/router_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluro/fluro.dart'; 3 | import 'package:flutter_weixin/main.dart'; 4 | 5 | // app的首页 6 | var homeHandler = new Handler( 7 | handlerFunc: (BuildContext context, Map> params) { 8 | return null; 9 | }, 10 | ); 11 | 12 | var categoryHandler = new Handler( 13 | handlerFunc: (BuildContext context, Map> params) { 14 | String name = params["type"]?.first; 15 | 16 | return null; 17 | }, 18 | ); 19 | 20 | var widgetNotFoundHandler = new Handler( 21 | handlerFunc: (BuildContext context, Map> params) { 22 | return null; 23 | }); 24 | 25 | var fullScreenCodeDialog = new Handler( 26 | handlerFunc: (BuildContext context, Map> params) { 27 | String path = params['filePath']?.first; 28 | return null; 29 | }); 30 | 31 | var webViewPageHand = new Handler( 32 | handlerFunc: (BuildContext context, Map> params) { 33 | String title = params['title']?.first; 34 | String url = params['url']?.first; 35 | return null; 36 | }); 37 | -------------------------------------------------------------------------------- /lib/routers/routers.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import './router_handler.dart'; 4 | 5 | class Routes { 6 | static String root = "/"; 7 | static String home = "/home"; 8 | 9 | static void configureRoutes(Router router) { 10 | router.notFoundHandler = new Handler( 11 | handlerFunc: (BuildContext context, 12 | Map> params) {}); 13 | router.define(home, handler: homeHandler); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/utils/net_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'dart:async'; 3 | 4 | var dio = new Dio(); 5 | 6 | class NetUtils { 7 | 8 | static Future get(String url, {Map params}) async { 9 | var response = await dio.get(url, queryParameters: params); 10 | return response.data; 11 | } 12 | 13 | static Future post(String url, Map params) async { 14 | var response = await dio.post(url, data: params); 15 | return response.data; 16 | } 17 | } -------------------------------------------------------------------------------- /lib/utils/provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'package:path/path.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | import 'package:flutter/services.dart' show rootBundle; 7 | 8 | //const createSql = { 9 | // 'cat': """ 10 | // CREATE TABLE "cat" ( 11 | // `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, 12 | // `name` TEXT NOT NULL UNIQUE, 13 | // `depth` INTEGER NOT NULL DEFAULT 1, 14 | // `parentId` INTEGER NOT NULL, 15 | // `desc` TEXT 16 | // ); 17 | // """, 18 | // 'collectio': """ 19 | // CREATE TABLE collection (id INTEGER PRIMARY KEY NOT NULL UNIQUE, name TEXT NOT NULL, router TEXT); 20 | // """, 21 | // 'widget': """ 22 | // CREATE TABLE widget (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, name TEXT NOT NULL, cnName TEXT NOT NULL, image TEXT NOT NULL, doc TEXT, demo TEXT, catId INTEGER NOT NULL REFERENCES cat (id), owner TEXT); 23 | // """; 24 | //}; 25 | 26 | class Provider { 27 | static Database db; 28 | 29 | // 获取数据库中所有的表 30 | Future getTables() async { 31 | if (db == null) { 32 | return Future.value([]); 33 | } 34 | List tables = await db.rawQuery( 35 | 'SELECT name FROM sqlite_master WHERE type = "table"'); 36 | List targetList = []; 37 | tables.forEach((item) { 38 | targetList.add(item['name']); 39 | }); 40 | return targetList; 41 | } 42 | 43 | // 检查数据库中, 表是否完整, 在部份android中, 会出现表丢失的情况 44 | Future checkTableIsRight() async { 45 | List expectTables = ['conversation']; 46 | 47 | List tables = await getTables(); 48 | 49 | for (int i = 0; i < expectTables.length; i++) { 50 | if (!tables.contains(expectTables[i])) { 51 | return false; 52 | } 53 | } 54 | return true; 55 | } 56 | 57 | //初始化数据库 58 | 59 | Future init(bool isCreate) async { 60 | //Get a location using getDatabasesPath 61 | String databasesPath = await getDatabasesPath(); 62 | String path = join(databasesPath, 'flutter.db'); 63 | print(path); 64 | try { 65 | db = await openDatabase(path); 66 | } catch (e) { 67 | print("Error $e"); 68 | } 69 | bool tableIsRight = await this.checkTableIsRight(); 70 | 71 | if (!tableIsRight) { 72 | // 关闭上面打开的db,否则无法执行open 73 | db.close(); 74 | // Delete the database 75 | await deleteDatabase(path); 76 | ByteData data = await rootBundle.load(join("static", "app.db")); 77 | List bytes = 78 | data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); 79 | await new File(path).writeAsBytes(bytes); 80 | 81 | db = await openDatabase(path, version: 1, 82 | onCreate: (Database db, int version) async { 83 | print('db created version is $version'); 84 | }, onOpen: (Database db) async { 85 | print('new db opened'); 86 | }); 87 | } else { 88 | print("Opening existing database"); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /lib/utils/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | export 'package:flutter_weixin/resources/shared_preferences_keys.dart'; 4 | 5 | /// 用来做shared_preferences的存储 6 | class SpUtil { 7 | static SpUtil _instance; 8 | 9 | static Future get instance async { 10 | return await getInstance(); 11 | } 12 | 13 | static SharedPreferences _spf; 14 | 15 | 16 | SpUtil._(); 17 | 18 | Future _init() async { 19 | _spf = await SharedPreferences.getInstance(); 20 | } 21 | 22 | static Future getInstance() async { 23 | if (_instance == null) { 24 | _instance = new SpUtil._(); 25 | await _instance._init(); 26 | } 27 | return _instance; 28 | } 29 | 30 | static bool _beforCheck() { 31 | if (_spf == null) { 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | // 判断是否存在数据 38 | bool hasKey(String key) { 39 | Set keys = getKeys(); 40 | return keys.contains(key); 41 | } 42 | 43 | Set getKeys() { 44 | if (_beforCheck()) return null; 45 | return _spf.getKeys(); 46 | } 47 | 48 | get(String key) { 49 | if (_beforCheck()) return null; 50 | return _spf.get(key); 51 | } 52 | 53 | getString(String key) { 54 | if (_beforCheck()) return null; 55 | return _spf.getString(key); 56 | } 57 | 58 | Future putString(String key, String value) { 59 | if (_beforCheck()) return null; 60 | return _spf.setString(key, value); 61 | } 62 | 63 | bool getBool(String key) { 64 | if (_beforCheck()) return null; 65 | return _spf.getBool(key); 66 | } 67 | 68 | Future putBool(String key, bool value) { 69 | if (_beforCheck()) return null; 70 | return _spf.setBool(key, value); 71 | } 72 | 73 | int getInt(String key) { 74 | if (_beforCheck()) return null; 75 | return _spf.getInt(key); 76 | } 77 | 78 | Future putInt(String key, int value) { 79 | if (_beforCheck()) return null; 80 | return _spf.setInt(key, value); 81 | } 82 | 83 | double getDouble(String key) { 84 | if (_beforCheck()) return null; 85 | return _spf.getDouble(key); 86 | } 87 | 88 | Future putDouble(String key, double value) { 89 | if (_beforCheck()) return null; 90 | return _spf.setDouble(key, value); 91 | } 92 | 93 | List getStringList(String key) { 94 | return _spf.getStringList(key); 95 | } 96 | 97 | Future putStringList(String key, List value) { 98 | if (_beforCheck()) return null; 99 | return _spf.setStringList(key, value); 100 | } 101 | 102 | dynamic getDynamic(String key) { 103 | if (_beforCheck()) return null; 104 | return _spf.get(key); 105 | } 106 | 107 | 108 | Future remove(String key) { 109 | if (_beforCheck()) return null; 110 | return _spf.remove(key); 111 | } 112 | 113 | Future clear() { 114 | if (_beforCheck()) return null; 115 | return _spf.clear(); 116 | } 117 | } -------------------------------------------------------------------------------- /lib/utils/sql.dart: -------------------------------------------------------------------------------- 1 | import './provider.dart'; 2 | import 'dart:async'; 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | 6 | class BaseModel { 7 | Database db; 8 | final String table = ''; 9 | var query; 10 | 11 | BaseModel(this.db) { 12 | query = db.query; 13 | } 14 | } 15 | 16 | class Sql extends BaseModel { 17 | final String tableName; 18 | 19 | Sql.setTable(String name) 20 | : tableName = name, 21 | super(Provider.db); 22 | 23 | 24 | Future get() async { 25 | return await this.query(tableName); 26 | } 27 | 28 | Future getAll() async { 29 | var result = await this.query(tableName); 30 | return result.toList(); 31 | } 32 | 33 | String getTableName() { 34 | return tableName; 35 | } 36 | 37 | Future delete(String value, String key) async { 38 | return await this.db.delete( 39 | tableName, where: '$key = ?', whereArgs: [value]); 40 | } 41 | 42 | Future clearTable(String tableName) async { 43 | return await this.db.delete(tableName); 44 | } 45 | 46 | Future getByCondition({Map conditions}) async { 47 | if (conditions == null || conditions.isEmpty) { 48 | return this.get(); 49 | } 50 | String stringConditions = ''; 51 | 52 | int index = 0; 53 | conditions.forEach((key, value) { 54 | if (value == null) { 55 | return; 56 | } 57 | if (value.runtimeType == String) { 58 | stringConditions = '$stringConditions $key = "$value"'; 59 | } 60 | if (value.runtimeType == int) { 61 | stringConditions = '$stringConditions $key = $value'; 62 | } 63 | 64 | if (index >= 0 && index < conditions.length - 1) { 65 | stringConditions = '$stringConditions and'; 66 | } 67 | index++; 68 | }); 69 | // print("this is string condition for sql > $stringConditions"); 70 | return await this.query(tableName, where: stringConditions); 71 | } 72 | 73 | Future> insert(Map json) async { 74 | var id = await this.db.insert(tableName, json); 75 | json['id'] = id; 76 | return json; 77 | } 78 | 79 | /// 80 | /// 搜索 81 | /// @param Object condition 82 | /// @mods [And, Or] default is Or 83 | /// search({'name': "hanxu', 'id': 1}; 84 | /// 85 | Future search( 86 | {Map conditions, String mods = 'Or'}) async { 87 | if (conditions == null || conditions.isEmpty) { 88 | return this.get(); 89 | } 90 | String stringConditions = ''; 91 | int index = 0; 92 | conditions.forEach((key, value) { 93 | if (value == null) { 94 | return; 95 | } 96 | 97 | if (value.runtimeType == String) { 98 | stringConditions = '$stringConditions $key like "%$value%"'; 99 | } 100 | if (value.runtimeType == int) { 101 | stringConditions = '$stringConditions $key = "%$value%"'; 102 | } 103 | 104 | if (index >= 0 && index < conditions.length - 1) { 105 | stringConditions = '$stringConditions $mods'; 106 | } 107 | index++; 108 | }); 109 | 110 | return await this.query(tableName, where: stringConditions); 111 | } 112 | } -------------------------------------------------------------------------------- /lib/utils/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //颜色配置 4 | class AppColor { 5 | static const int white = 0xFFFFFFFF; 6 | static const int mainTextColor = 0xFF121917; 7 | static const int subTextColor = 0xff959595; 8 | } 9 | 10 | //文本设置 11 | class AppText { 12 | static const middleSize = 16.0; 13 | 14 | static const middleText = TextStyle( 15 | color: Color(AppColor.mainTextColor), 16 | fontSize: middleSize, 17 | ); 18 | 19 | static const middleSubText = TextStyle( 20 | color: Color(AppColor.subTextColor), 21 | fontSize: middleSize, 22 | ); 23 | } 24 | 25 | class WidgetDemoColor { 26 | static const int fontColor = 0xFF607173; 27 | static const int iconColor = 0xFF607173; 28 | static const int borderColor = 0xFFEFEFEF; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const Map emumMap = const { 4 | "Objective-C": Color(0xFF438EFF), 5 | "Perl": Color(0xFF0298C3), 6 | "Python": Color(0xFF0298C3), 7 | "JavaScript": Color(0xFFF1E05A), 8 | "PHP": Color(0xFF4F5D95), 9 | "R": Color(0xFF188CE7), 10 | "Lua": Color(0xFFC22D40), 11 | "Scala": Color(0xFF020080), 12 | "Swift": Color(0xFFFFAC45), 13 | "Kotlin": Color(0xFFF18E33), 14 | "Vue": Colors.black, 15 | "Ruby": Color(0xFF701617), 16 | "Shell": Color(0xFF89E051), 17 | "TypeScript": Color(0xFF2B7489), 18 | "C++": Color(0xFFF34B7D), 19 | "CSS": Color(0xFF563C7C), 20 | "Java": Color(0xFFB07219), 21 | "C#": Color(0xFF178600), 22 | "Go": Color(0xFF375EAB), 23 | "Erlang": Color(0xFFB83998), 24 | "C": Color(0xFF555555), 25 | }; 26 | 27 | class Util { 28 | static String getTimeDuration(String comTime) { 29 | var nowTime = DateTime.now(); 30 | var compareTime = DateTime.parse(comTime); 31 | if (nowTime.isAfter(compareTime)) { 32 | if (nowTime.year == compareTime.year) { 33 | if (nowTime.month == compareTime.month) { 34 | if (nowTime.day == compareTime.day) { 35 | if (nowTime.hour == compareTime.hour) { 36 | if (nowTime.minute == compareTime.minute) { 37 | return '片刻之间'; 38 | } 39 | return (nowTime.minute - compareTime.minute).toString() + '分钟前'; 40 | } 41 | return (nowTime.hour - compareTime.hour).toString() + '小时前'; 42 | } 43 | return (nowTime.day - compareTime.day).toString() + '天前'; 44 | } 45 | return (nowTime.month - compareTime.month).toString() + '月前'; 46 | } 47 | return (nowTime.year - compareTime.year).toString() + '年前'; 48 | } 49 | return 'time error'; 50 | } 51 | 52 | static double setPercentage(percentage, context) { 53 | return MediaQuery 54 | .of(context) 55 | .size 56 | .width * percentage; 57 | } 58 | 59 | static Color getLangColor(String language) { 60 | if (emumMap.containsKey(language)) { 61 | return emumMap[language]; 62 | } 63 | return Colors.black26; 64 | } 65 | 66 | static String getTimeDate(String comTime) { 67 | var compareTime = DateTime.parse(comTime); 68 | String weekDay = ''; 69 | switch (compareTime.weekday) { 70 | case 2: 71 | weekDay = '周二'; 72 | break; 73 | case 3: 74 | weekDay = '周三'; 75 | break; 76 | case 4: 77 | weekDay = '周四'; 78 | break; 79 | case 5: 80 | weekDay = '周五'; 81 | break; 82 | case 6: 83 | weekDay = '周六'; 84 | break; 85 | case 7: 86 | weekDay = '周日'; 87 | break; 88 | default: 89 | weekDay = '周一'; 90 | } 91 | return '${compareTime.month}-${compareTime.day} $weekDay'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/views/404.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class WidgetNotFound extends StatelessWidget { 5 | 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | appBar: AppBar( 9 | elevation: 0.0, 10 | title: Text("widget not found"), 11 | ), 12 | body: Center( 13 | child: Text("widget not found") 14 | ) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/views/contacts_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/model/contact.dart' show Contact; 3 | import 'package:flutter_weixin/model/conversation.dart' show Conversation; 4 | import 'package:flutter_weixin/common/style/Style.dart' show Style; 5 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 6 | import 'package:flutter_weixin/views/home_chat_page.dart'; 7 | 8 | class ContactsDetail extends StatefulWidget { 9 | final Contact contact; 10 | 11 | ContactsDetail({Key key, this.contact}) 12 | : assert(contact != null), 13 | super(key: key); 14 | 15 | @override 16 | _ContactsDetailState createState() => _ContactsDetailState(this.contact); 17 | } 18 | 19 | class _ContactsDetailState extends State { 20 | Contact _contact; 21 | Conversation _conversation; 22 | 23 | _ContactsDetailState(this._contact); 24 | 25 | @override 26 | void initState() { 27 | // TODO: implement initState 28 | super.initState(); 29 | _conversation = new Conversation( 30 | avatar: this._contact.avatar, 31 | title: this._contact.name, 32 | createAt: '', 33 | isNetwork: true); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | // TODO: implement dispose 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | // 头像组件 45 | Widget userImage = new UserIconWidget( 46 | padding: const EdgeInsets.only(top: 0.0, right: 18.0, left: 15.0), 47 | width: 55.0, 48 | height: 55.0, 49 | image: _contact.avatar, 50 | isNetwork: true, 51 | onPressed: () {}); 52 | Widget buildMainRow() { 53 | return Row( 54 | mainAxisAlignment: MainAxisAlignment.center, 55 | children: [ 56 | userImage, 57 | Expanded( 58 | child: Container( 59 | padding: EdgeInsets.only(top: 10.0), 60 | child: Column( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | children: [ 63 | Text( 64 | _contact.name, 65 | style: TextStyle(fontSize: 22.5, fontWeight: FontWeight.bold), 66 | ), 67 | Container( 68 | height: 2.0, 69 | ), 70 | Text( 71 | '微信号:xxx', 72 | maxLines: 1, 73 | style: TextStyle(color: Colors.grey, fontSize: 13.0), 74 | ), 75 | Container( 76 | height: 2.0, 77 | ), 78 | Text( 79 | '地区:xxxxxxx', 80 | maxLines: 1, 81 | style: TextStyle(color: Colors.grey, fontSize: 13.0), 82 | ) 83 | ], 84 | ), 85 | )), 86 | Row( 87 | children: [ 88 | Container( 89 | padding: EdgeInsets.only(top: 0.0, bottom: 15.0, right: 15.0), 90 | child: Icon( 91 | Icons.camera, 92 | color: Colors.grey, 93 | size: 18.0, 94 | ), 95 | ) 96 | ], 97 | ) 98 | ], 99 | ); 100 | } 101 | 102 | Widget buildRow(title, isEnd, showImage) { 103 | return Row( 104 | // mainAxisAlignment: MainAxisAlignment.spaceEvenly, 105 | children: [ 106 | Expanded( 107 | child: Container( 108 | height: 50.0, 109 | decoration: !isEnd 110 | ? BoxDecoration( 111 | border: Border( 112 | bottom: 113 | BorderSide(color: Color(0xffd9d9d9), width: .3))) 114 | : null, 115 | margin: EdgeInsets.only(left: 15.0), 116 | child: Row( 117 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 118 | children: [ 119 | Container( 120 | child: title, 121 | ), 122 | Container( 123 | child: Row( 124 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 125 | children: [ 126 | showImage 127 | ? Row( 128 | children: [ 129 | Container( 130 | child: Image.asset( 131 | 'static/images/default_nor_avatar.png', fit: BoxFit.cover, 132 | width: 40.0, 133 | height: 40.0,), 134 | margin: EdgeInsets.only(right: 5.0), 135 | ), 136 | Container( 137 | child: Image.asset( 138 | 'static/images/default_nor_avatar.png', fit: BoxFit.cover, 139 | width: 40.0, 140 | height: 40.0,), 141 | margin: EdgeInsets.only(right: 5.0), 142 | ), 143 | Container( 144 | child: Image.asset( 145 | 'static/images/default_nor_avatar.png', fit: BoxFit.cover, 146 | width: 40.0, 147 | height: 40.0,), 148 | margin: EdgeInsets.only(right: 5.0), 149 | ) 150 | ], 151 | ) 152 | : Container(), 153 | Container( 154 | padding: EdgeInsets.only(right: 10.0), 155 | child: Icon( 156 | Icons.chevron_right, 157 | color: Colors.grey, 158 | ), 159 | ) 160 | ], 161 | ), 162 | ), 163 | ], 164 | ), 165 | ), 166 | ), 167 | ], 168 | ); 169 | } 170 | 171 | return Scaffold( 172 | appBar: AppBar( 173 | backgroundColor: Color(0xffFFFFFF), 174 | elevation: 0.0, 175 | actions: [ 176 | IconButton( 177 | icon: Icon( 178 | Icons.more_horiz, 179 | color: Colors.black, 180 | ), 181 | onPressed: null) 182 | ], 183 | ), 184 | body: Container( 185 | color: Color(Style.primaryValue), 186 | child: Column( 187 | children: [ 188 | Container( 189 | decoration: BoxDecoration( 190 | color: Colors.white, 191 | border: Border( 192 | bottom: 193 | BorderSide(color: Color(0xffd9d9d9), width: .3))), 194 | padding: EdgeInsets.only(bottom: 28.0), 195 | child: buildMainRow(), 196 | ), 197 | Container( 198 | color: Colors.white, 199 | child: buildRow( 200 | Text( 201 | '设置备注和标签', 202 | style: TextStyle(color: Colors.black, fontSize: 16.0), 203 | ), 204 | true, false), 205 | ), 206 | Container( 207 | decoration: BoxDecoration( 208 | color: Colors.white, 209 | border: Border( 210 | bottom: 211 | BorderSide(color: Color(0xffd9d9d9), width: .3))), 212 | child: Column( 213 | mainAxisAlignment: MainAxisAlignment.spaceAround, 214 | children: [ 215 | Container( 216 | color: Color(0xffEDEDED), 217 | height: 10.0, 218 | ), 219 | buildRow( 220 | Text( 221 | '朋友圈', 222 | style: TextStyle(color: Colors.black, fontSize: 16.0), 223 | ), 224 | false, true), 225 | buildRow( 226 | Text( 227 | '更多信息', 228 | style: TextStyle(color: Colors.black, fontSize: 16.0), 229 | ), 230 | true, false), 231 | ], 232 | ), 233 | ), 234 | Container( 235 | decoration: BoxDecoration( 236 | color: Colors.white, 237 | border: Border( 238 | bottom: 239 | BorderSide(color: Color(0xffd9d9d9), width: .3))), 240 | child: Column( 241 | mainAxisAlignment: MainAxisAlignment.center, 242 | children: [ 243 | Container( 244 | color: Color(0xffEDEDED), 245 | height: 10.0, 246 | ), 247 | RawMaterialButton( 248 | onPressed: () { 249 | Navigator.push(context, MaterialPageRoute(builder: (c) { 250 | return new HomeChatPage(conversation: _conversation); 251 | })); 252 | }, 253 | child: Container( 254 | decoration: BoxDecoration( 255 | color: Colors.white, 256 | border: Border( 257 | bottom: BorderSide( 258 | color: Color(0xffd9d9d9), width: .3))), 259 | alignment: Alignment.center, 260 | height: 50.0, 261 | child: Text( 262 | '发消息', 263 | style: TextStyle(color: Colors.blue), 264 | ), 265 | ), 266 | ), 267 | Container( 268 | alignment: Alignment.center, 269 | height: 50.0, 270 | child: Text( 271 | '音视频通话', 272 | style: TextStyle(color: Colors.blue), 273 | ), 274 | ) 275 | ], 276 | ), 277 | ), 278 | ], 279 | )), 280 | ); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /lib/views/contacts_group_chat_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart' show Style; 3 | 4 | class ContactsGroupChat extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | appBar: AppBar( 9 | title: Text('群聊'), 10 | elevation: 0.0, 11 | actions: [ 12 | IconButton(icon: Icon(Icons.add), onPressed: () {}), 13 | ], 14 | ), 15 | body: Container( 16 | color: Color(Style.primaryValue), 17 | child: Center( 18 | child: Text('暂无群聊', style: TextStyle(color: Colors.grey,fontSize: 18.0),), 19 | ), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/views/contacts_new_friend_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart' show Style; 3 | import 'package:flutter_weixin/model/contact.dart'; 4 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 5 | import 'package:flutter_weixin/model/conversation.dart' 6 | show ConversationControlModel; 7 | 8 | class ContactsNewFriend extends StatefulWidget { 9 | @override 10 | _ContactsNewFriendState createState() => _ContactsNewFriendState(); 11 | } 12 | 13 | class _ContactsNewFriendState extends State { 14 | ConversationControlModel _conversationControlModel = 15 | new ConversationControlModel(); 16 | List mockContacts = []; 17 | @override 18 | void initState() { 19 | // TODO: implement initState 20 | super.initState(); 21 | _conversationControlModel.sql.getAll().then((result) { 22 | List arr = []; 23 | result.forEach((item) { 24 | arr.add(Contact( 25 | avatar: item['avatar'], 26 | name: item['name'], 27 | nameIndex: item['name'].toString().substring(0, 1).toUpperCase(), 28 | isNetwork: true)); 29 | }); 30 | arr.sort((Contact a, Contact b) => a.nameIndex.compareTo(b.nameIndex)); 31 | setState(() { 32 | mockContacts.addAll(arr); 33 | }); 34 | }); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | appBar: AppBar( 41 | title: Text( 42 | '新的朋友', 43 | style: TextStyle(fontSize: 18.0, color: Colors.black), 44 | ), 45 | elevation: 0.0, 46 | actions: [ 47 | RawMaterialButton( 48 | child: Text( 49 | '添加朋友', 50 | style: TextStyle(fontSize: 16.0, color: Colors.black), 51 | ), 52 | onPressed: () {}) 53 | ], 54 | ), 55 | body: Container( 56 | color: Color(Style.primaryValue), 57 | child: Column( 58 | children: [ 59 | Container( 60 | margin: EdgeInsets.only(top: 10.0), 61 | color: Colors.white, 62 | height: 50.0, 63 | child: Row( 64 | children: [ 65 | Container( 66 | alignment: Alignment.centerLeft, 67 | padding: EdgeInsets.only(left: 5.0), 68 | margin: EdgeInsets.only(left: 10.0, right: 5.0), 69 | height: MediaQuery.of(context).padding.top * 1.3, 70 | child: Icon( 71 | Icons.search, 72 | color: Colors.black26, 73 | ), 74 | ), 75 | Text( 76 | '微信号/QQ号/手机号', 77 | style: TextStyle(color: Colors.grey), 78 | ) 79 | ], 80 | ), 81 | ), 82 | Container( 83 | padding: EdgeInsets.only(left: 12.0, bottom: 0.0), 84 | margin: EdgeInsets.only(top: 10.0), 85 | color: Color(0xffEDEDED), 86 | height: 30.0, 87 | alignment: Alignment.centerLeft, 88 | child: Text( 89 | '新的朋友', 90 | style: TextStyle(color: Colors.grey), 91 | ), 92 | ), 93 | Expanded( 94 | child: ListView.builder( 95 | itemBuilder: (BuildContext context, int index) { 96 | return _ContactItem(contact: mockContacts[index]); 97 | }, 98 | itemCount: mockContacts.length, 99 | )), 100 | ], 101 | ), 102 | ), 103 | ); 104 | } 105 | } 106 | 107 | class _ContactItem extends StatelessWidget { 108 | const _ContactItem({Key key, this.contact}) 109 | : assert(contact != null), 110 | super(key: key); 111 | final Contact contact; 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | // 头像组件 116 | Widget userImage = new UserIconWidget( 117 | padding: const EdgeInsets.only(top: 0.0, right: 12.0, left: 12.0), 118 | width: 40.0, 119 | height: 40.0, 120 | image: contact.avatar, 121 | isNetwork: contact.isNetwork, 122 | onPressed: () { 123 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 124 | }); 125 | Widget itemRow = Container( 126 | color: Colors.white, 127 | child: Row( 128 | children: [ 129 | userImage, 130 | Expanded( 131 | child: Container( 132 | decoration: BoxDecoration( 133 | border: Border( 134 | bottom: BorderSide(color: Color(0xffd9d9d9), width: .3))), 135 | padding: EdgeInsets.only(top: 8.0), 136 | child: Row( 137 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 138 | children: [ 139 | Container( 140 | // alignment: Alignment.centerLeft, 141 | margin: EdgeInsets.only(bottom: 12.0), 142 | child: Text( 143 | contact.name, 144 | style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), 145 | ), 146 | ), 147 | Container( 148 | width: 60.0, 149 | height: 30.0, 150 | margin: EdgeInsets.only(right: 10.0,bottom: 8.0), 151 | child: RaisedButton( 152 | onPressed: () {}, 153 | textColor: Colors.white, 154 | color: Colors.green, 155 | elevation: 0.0, 156 | child: Text('接受'), 157 | ), 158 | ) 159 | ], 160 | ) 161 | ), 162 | ), 163 | ], 164 | ), 165 | ); 166 | 167 | return Container( 168 | height: 64.0, 169 | child: itemRow, 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/views/contacts_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/model/contact.dart' 3 | show preContact, mockContact, Contact; 4 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 5 | import 'package:flutter_weixin/model/conversation.dart' 6 | show ConversationControlModel, Manager; 7 | import 'package:flutter_weixin/views/404.dart'; 8 | import 'package:flutter_weixin/views/contacts_new_friend_page.dart'; 9 | import 'package:flutter_weixin/views/contacts_group_chat_page.dart'; 10 | import 'package:flutter_weixin/views/contacts_tags_page.dart'; 11 | import 'package:flutter_weixin/views/contacts_detail_page.dart'; 12 | 13 | class ContactsPage extends StatefulWidget { 14 | 15 | @override 16 | _ContactsPageState createState() => _ContactsPageState(); 17 | } 18 | 19 | const INDEX_BAR_WORDS = [ 20 | "↑", 21 | "☆", 22 | "A", 23 | "B", 24 | "C", 25 | "D", 26 | "E", 27 | "F", 28 | "G", 29 | "H", 30 | "I", 31 | "J", 32 | "K", 33 | "L", 34 | "M", 35 | "N", 36 | "O", 37 | "P", 38 | "Q", 39 | "R", 40 | "S", 41 | "T", 42 | "U", 43 | "V", 44 | "W", 45 | "X", 46 | "Y", 47 | "Z" 48 | ]; 49 | 50 | class _ContactsPageState extends State { 51 | Color _indexBarBg = Colors.transparent; 52 | ConversationControlModel _conversationControlModel = new ConversationControlModel(); 53 | ScrollController _scrollController; 54 | static Map _letterPosMap = { 55 | INDEX_BAR_WORDS[0]: 0.0, 56 | INDEX_BAR_WORDS[1]: 230.0 57 | }; 58 | static String currentLetter = ''; 59 | @override 60 | void initState() { 61 | // TODO: implement initState 62 | super.initState(); 63 | 64 | _scrollController = new ScrollController(); 65 | if (Manager().getState()) { 66 | mockContact.clear(); 67 | _letterPosMap = {INDEX_BAR_WORDS[0]: 0.0, INDEX_BAR_WORDS[1]: 230.0}; 68 | _conversationControlModel.sql.getAll().then((result) { 69 | Manager().setSate(false); 70 | List arr = []; 71 | result.forEach((item) { 72 | arr.add(Contact( 73 | avatar: item['avatar'], 74 | name: item['name'], 75 | nameIndex: item['name'].toString().substring(0, 1).toUpperCase(), 76 | isNetwork: true)); 77 | }); 78 | arr.sort((Contact a, Contact b) => a.nameIndex.compareTo(b.nameIndex)); 79 | arr.insertAll(0, preContact); 80 | var totalIndex = 0; 81 | for (int i = 0; i < arr.length; i++) { 82 | if (i >= preContact.length) { 83 | if (arr[i].nameIndex != arr[i - 1].nameIndex) { 84 | _letterPosMap[arr[i].nameIndex] = 85 | (totalIndex * 30 + (i - 4) * 56 + 230).toDouble(); 86 | totalIndex++; 87 | } 88 | } 89 | } 90 | setState(() { 91 | mockContact.addAll(arr); 92 | }); 93 | }); 94 | } 95 | } 96 | 97 | @override 98 | void dispose() { 99 | // TODO: implement dispose 100 | _scrollController.dispose(); 101 | super.dispose(); 102 | } 103 | 104 | String getLetter(BuildContext context, Offset globalPos, int tileHeight) { 105 | RenderBox renderBox = context.findRenderObject(); 106 | var local = renderBox.globalToLocal(globalPos); 107 | int index = 108 | (local.dy ~/ tileHeight).clamp(0, INDEX_BAR_WORDS.length - 1); // 防止数组越界 109 | print(INDEX_BAR_WORDS[index]); 110 | return INDEX_BAR_WORDS[index]; 111 | } 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | final List _letters = INDEX_BAR_WORDS.map((String word) { 116 | return Expanded(child: Text(word)); 117 | }).toList(); 118 | 119 | return Stack( 120 | children: [ 121 | ListView.builder( 122 | controller: _scrollController, 123 | itemBuilder: (BuildContext context, int index) { 124 | bool isNameIndex = true; 125 | if (index >= preContact.length) { 126 | if (mockContact[index].nameIndex == 127 | mockContact[index - 1].nameIndex) { 128 | isNameIndex = false; 129 | } 130 | } 131 | return _ContactItem( 132 | contact: mockContact[index], isNameIndex: isNameIndex); 133 | }, 134 | itemCount: mockContact.length, 135 | ), 136 | Positioned( 137 | width: 24.0, 138 | right: 0.0, 139 | top: 0.0, 140 | bottom: 0.0, 141 | child: Container( 142 | color: _indexBarBg, 143 | child: LayoutBuilder( 144 | builder: (BuildContext context, BoxConstraints constraints) { 145 | final _totalHeight = constraints.biggest.height; 146 | final _tileHeight = 147 | _totalHeight ~/ _letters.length; // 每个字母的高度取整 148 | return GestureDetector( 149 | onVerticalDragDown: (DragDownDetails details) { 150 | print(details); 151 | setState(() { 152 | _indexBarBg = Colors.black26; 153 | currentLetter = getLetter( 154 | context, details.globalPosition, _tileHeight); 155 | if (_letterPosMap[currentLetter] != null) { 156 | _scrollController.animateTo(_letterPosMap[currentLetter], 157 | duration: Duration(milliseconds: 100), 158 | curve: Curves.decelerate); 159 | } 160 | }); 161 | }, 162 | onVerticalDragUpdate: (DragUpdateDetails details) { 163 | setState(() { 164 | currentLetter = getLetter( 165 | context, details.globalPosition, _tileHeight); 166 | if (_letterPosMap[currentLetter] != null) { 167 | _scrollController.animateTo(_letterPosMap[currentLetter], 168 | duration: Duration(milliseconds: 100), 169 | curve: Curves.fastOutSlowIn); 170 | } 171 | }); 172 | }, 173 | onVerticalDragEnd: (DragEndDetails details) { 174 | print('end'); 175 | setState(() { 176 | _indexBarBg = Colors.transparent; 177 | }); 178 | }, 179 | onVerticalDragCancel: () { 180 | print('cancel'); 181 | setState(() { 182 | _indexBarBg = Colors.transparent; 183 | }); 184 | }, 185 | child: Column( 186 | children: _letters, 187 | ), 188 | ); 189 | }), 190 | )), 191 | _indexBarBg == Colors.black26 ? Center( 192 | child: Container( 193 | width: 114.0, 194 | height: 114.0, 195 | alignment: Alignment.center, 196 | decoration: BoxDecoration( 197 | color: Colors.black45, 198 | borderRadius: BorderRadius.all(Radius.circular(5.0))), 199 | child: Text( 200 | currentLetter, 201 | style: TextStyle(color: Colors.white, fontSize: 64.0), 202 | ), 203 | ), 204 | ): Container() 205 | ], 206 | ); 207 | } 208 | } 209 | 210 | class _ContactItem extends StatelessWidget { 211 | const _ContactItem({Key key, this.contact, this.isNameIndex}) 212 | : assert(contact != null), 213 | super(key: key); 214 | final Contact contact; 215 | final bool isNameIndex; 216 | 217 | @override 218 | Widget build(BuildContext context) { 219 | // 头像组件 220 | Widget userImage = new UserIconWidget( 221 | padding: const EdgeInsets.only(top: 0.0, right: 12.0, left: 12.0), 222 | width: 35.0, 223 | height: 35.0, 224 | image: contact.avatar, 225 | isNetwork: contact.isNetwork, 226 | onPressed: () { 227 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 228 | }); 229 | Widget itemRow = RawMaterialButton( 230 | onPressed: () { 231 | Navigator.push(context, MaterialPageRoute(builder: (c) { 232 | Widget page = new WidgetNotFound(); 233 | if (!contact.isNetwork) { 234 | switch (contact.name) { 235 | case '新的朋友': page = new ContactsNewFriend(); break; 236 | case '群聊': page = new ContactsGroupChat(); break; 237 | case '标签': page = new ContactsTagsChat(); break; 238 | default: break; 239 | } 240 | }else { 241 | page = new ContactsDetail(contact: contact); 242 | } 243 | return page; 244 | })); 245 | }, 246 | child: Row( 247 | children: [ 248 | userImage, 249 | Expanded( 250 | child: Container( 251 | decoration: BoxDecoration( 252 | border: Border( 253 | bottom: BorderSide(color: Color(0xffd9d9d9), width: .3))), 254 | padding: EdgeInsets.only(top: 8.0), 255 | child: Container( 256 | // alignment: Alignment.centerLeft, 257 | margin: EdgeInsets.only(bottom: 12.0), 258 | child: Text( 259 | contact.name, 260 | style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), 261 | ), 262 | ), 263 | ), 264 | ) 265 | ], 266 | ), 267 | ); 268 | Widget contactItem = contact.isNetwork 269 | ? (isNameIndex 270 | ? Column( 271 | mainAxisAlignment: MainAxisAlignment.spaceAround, 272 | children: [ 273 | Container( 274 | padding: EdgeInsets.only(left: 12.0, bottom: 0.0), 275 | color: Color(0xffEDEDED), 276 | height: 30.0, 277 | alignment: Alignment.centerLeft, 278 | child: Text( 279 | contact.nameIndex, 280 | style: TextStyle(color: Colors.grey), 281 | ), 282 | ), 283 | itemRow 284 | ], 285 | ) 286 | : itemRow) 287 | : itemRow; 288 | 289 | return Container( 290 | height: contact.isNetwork && isNameIndex ? 86 : 56, 291 | child: contactItem, 292 | ); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /lib/views/contacts_tags_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart' show Style; 3 | 4 | class ContactsTagsChat extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | appBar: AppBar( 9 | title: Text('所有标签'), 10 | elevation: 0.0, 11 | ), 12 | body: Container( 13 | color: Color(Style.primaryValue), 14 | child: Center( 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 17 | children: [ 18 | Text('暂无标签', style: TextStyle(color: Colors.grey,fontSize: 18.0),), 19 | Text('你可以通过给朋友添加标签来进行分类', style: TextStyle(color: Colors.grey,fontSize: 14.0),), 20 | Container( 21 | width: 300.0, 22 | height: 45.0, 23 | child: RaisedButton( 24 | onPressed: () {}, 25 | textColor: Colors.white, 26 | color: Colors.green, 27 | elevation: 0.0, 28 | child: Text('新建标签', style: TextStyle(fontSize: 16.0),), 29 | ), 30 | ) 31 | ], 32 | ) 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/views/find_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/model/find.dart' show Find, mockContact; 3 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 4 | 5 | class FindPage extends StatefulWidget { 6 | @override 7 | _FindPageState createState() => _FindPageState(); 8 | } 9 | 10 | class _FindPageState extends State { 11 | @override 12 | void setState(fn) { 13 | // TODO: implement setState 14 | super.setState(fn); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | child: ListView.builder( 21 | itemCount: mockContact.length, 22 | itemBuilder: (BuildContext context, int index) { 23 | return _FindItem( 24 | find: mockContact[index], 25 | ); 26 | }), 27 | ); 28 | } 29 | } 30 | 31 | class _FindItem extends StatelessWidget { 32 | final Find find; 33 | 34 | _FindItem({Key key, this.find}) 35 | : assert(find != null), 36 | super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | // 头像组件 41 | Widget userImage = new UserIconWidget( 42 | padding: const EdgeInsets.only(top: 0.0, right: 14.0, left: 14.0), 43 | width: 25.0, 44 | height: 25.0, 45 | image: find.avatar, 46 | isNetwork: find.isNetwork, 47 | onPressed: () { 48 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 49 | }); 50 | Widget itemRow = Row( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | children: [ 53 | userImage, 54 | Expanded( 55 | child: Container( 56 | decoration: find.isWhite 57 | ? BoxDecoration( 58 | border: Border( 59 | bottom: 60 | BorderSide(color: Color(0xffd9d9d9), width: .3))) 61 | : null, 62 | padding: EdgeInsets.only(top: 16.0), 63 | child: Row( 64 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 65 | children: [ 66 | Container( 67 | child: Container( 68 | margin: EdgeInsets.only(bottom: 15.0), 69 | child: Text( 70 | find.name, 71 | style: TextStyle(fontSize: 15.0), 72 | ), 73 | ), 74 | ), 75 | Container( 76 | // padding: EdgeInsets.only(top: 16.0, right: 10.0), 77 | child: Container( 78 | margin: EdgeInsets.only(bottom: 15.0, right: 10.0), 79 | child: Icon( 80 | Icons.chevron_right, 81 | color: Colors.grey, 82 | // size: 22.0, 83 | ), 84 | ), 85 | ), 86 | ], 87 | ), 88 | ), 89 | ), 90 | ], 91 | ); 92 | return Container( 93 | height: find.isWhite ? 85 : 55, 94 | child: find.isWhite 95 | ? Column( 96 | mainAxisAlignment: MainAxisAlignment.spaceAround, 97 | children: [ 98 | Container( 99 | color: Color(0xffEDEDED), 100 | height: 20.0, 101 | ), 102 | itemRow, 103 | ], 104 | ) 105 | : itemRow, 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/views/home_chat_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/model/conversation.dart' show Conversation; 3 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 4 | 5 | class HomeChatPage extends StatefulWidget { 6 | final Conversation conversation; 7 | 8 | HomeChatPage({Key key, this.conversation}) : super(key: key); 9 | 10 | @override 11 | _HomeChatPageState createState() => _HomeChatPageState(conversation); 12 | } 13 | 14 | class _HomeChatPageState extends State 15 | with TickerProviderStateMixin { 16 | Conversation _conversation; 17 | 18 | _HomeChatPageState(this._conversation); 19 | 20 | List items = new List(); 21 | 22 | @override 23 | void initState() { 24 | // TODO: implement initState 25 | super.initState(); 26 | ChatItem message = new ChatItem( 27 | 'Hello, nice to meet you', 28 | 1, 29 | new AnimationController( 30 | vsync: this, duration: Duration(milliseconds: 500))); 31 | items.add(message); 32 | message.animationController.forward(); 33 | } 34 | 35 | @override 36 | void dispose() { 37 | for (ChatItem message in items) 38 | message.animationController.dispose(); // 释放动效 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | final controller = TextEditingController(); 45 | //定义发送文本事件的处理函数 46 | void _handleSubmitted(String text) { 47 | if (controller.text.length > 0) { 48 | controller.clear(); //清空输入框 49 | ChatItem message = new ChatItem( 50 | text, 51 | 0, 52 | new AnimationController( 53 | vsync: this, duration: Duration(milliseconds: 500))); 54 | //状态变更,向聊天记录中插入新记录 55 | setState(() { 56 | items.add(message); 57 | }); 58 | message.animationController.forward(); 59 | } 60 | } 61 | 62 | return new Scaffold( 63 | appBar: AppBar( 64 | title: Text(_conversation.title), 65 | elevation: 0.0, 66 | ), 67 | body: new Container( 68 | child: Column( 69 | children: [ 70 | Expanded( 71 | child: ListView.builder( 72 | itemBuilder: (BuildContext context, int index) { 73 | return ChatContentView( 74 | chatItem: items[index], conversation: _conversation); 75 | }, 76 | itemCount: items.length, 77 | )), 78 | Divider(height: 1.0, color: Color(0xFFF7F8F8),), 79 | Container( 80 | padding: 81 | EdgeInsets.only(top: 5.0, bottom: 15.0, right: 8.0, left: 15.0), 82 | color: Color(0xFFF7F7F7), 83 | child: Row( 84 | children: [ 85 | Expanded( 86 | child: Container( 87 | padding: EdgeInsets.only(top: 8.0, bottom: 8.0, left: 5.0), 88 | decoration: BoxDecoration( 89 | borderRadius: BorderRadius.all( 90 | Radius.circular(5.0), 91 | ), 92 | color: Colors.white), 93 | child: TextField( 94 | controller: controller, 95 | decoration: InputDecoration.collapsed(hintText: null), 96 | autocorrect: true, 97 | //是否自动更正 98 | autofocus: false, 99 | textAlign: TextAlign.start, 100 | style: TextStyle(color: Colors.black), 101 | cursorColor: Colors.green, 102 | onChanged: (text) { 103 | //内容改变的回调 104 | print('change=================== $text'); 105 | }, 106 | onSubmitted: _handleSubmitted, 107 | enabled: true, //是否禁用 108 | ), 109 | )), 110 | new Container( 111 | margin: new EdgeInsets.symmetric(horizontal: 4.0), 112 | child: new IconButton( 113 | //发送按钮 114 | icon: new Icon(Icons.send), //发送按钮图标 115 | onPressed: () => _handleSubmitted( 116 | controller.text)), //触发发送消息事件执行的函数_handleSubmitted 117 | ) 118 | ], 119 | ), 120 | ) 121 | ], 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | 128 | class ChatContentView extends StatelessWidget { 129 | final ChatItem chatItem; 130 | final Conversation conversation; 131 | 132 | ChatContentView({Key key, this.chatItem, this.conversation}) 133 | : super(key: key); 134 | 135 | @override 136 | Widget build(BuildContext context) { 137 | // 头像组件 138 | Widget userImage = new UserIconWidget( 139 | padding: EdgeInsets.only( 140 | top: 0.0, 141 | right: (chatItem.type == 0 ? 0.0 : 5.0), 142 | left: (chatItem.type == 0 ? 5.0 : 0.0)), 143 | width: 35.0, 144 | height: 35.0, 145 | image: chatItem.type == 0 146 | ? 'static/images/default_nor_avatar.png' 147 | : conversation.avatar, 148 | isNetwork: (chatItem.type == 1 && conversation.isNetwork), 149 | onPressed: () { 150 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 151 | }); 152 | return SizeTransition( 153 | sizeFactor: CurvedAnimation( 154 | parent: this.chatItem.animationController, curve: Curves.easeOutCirc), 155 | axisAlignment: 0.0, 156 | child: chatItem.type == 0 157 | ? Container( 158 | margin: EdgeInsets.only(top: 8.0, left: 8.0), 159 | padding: EdgeInsets.all(8.0), 160 | child: Row( 161 | children: [ 162 | Expanded( 163 | child: Column( 164 | // Column被Expanded包裹起来,使其内部文本可自动换行 165 | crossAxisAlignment: CrossAxisAlignment.end, 166 | children: [ 167 | Container( 168 | padding: EdgeInsets.symmetric( 169 | vertical: 8.0, horizontal: 5.0), 170 | decoration: BoxDecoration( 171 | //image: DecorationImage(image: AssetImage('static/images/chat_bg.png'), fit: BoxFit.fill), 172 | borderRadius: BorderRadius.all( 173 | Radius.circular(5.0), 174 | ), 175 | color: Color(0xFF9EEA6A), 176 | ), 177 | child: Text( 178 | chatItem.msg, 179 | textAlign: TextAlign.right, 180 | style: TextStyle(color: Colors.black, fontSize: 16.0), 181 | ), 182 | ) 183 | ], 184 | )), 185 | userImage 186 | ], 187 | ), 188 | ) 189 | : Container( 190 | margin: EdgeInsets.only(top: 8.0, right: 8.0), 191 | padding: EdgeInsets.all(8.0), 192 | child: Row( 193 | children: [ 194 | userImage, 195 | Expanded( 196 | child: Column( 197 | crossAxisAlignment: CrossAxisAlignment.start, 198 | children: [ 199 | Container( 200 | padding: EdgeInsets.symmetric( 201 | vertical: 8.0, horizontal: 5.0), 202 | decoration: BoxDecoration( 203 | borderRadius: BorderRadius.all( 204 | Radius.circular(5.0), 205 | ), 206 | color: Colors.white), 207 | child: Text( 208 | chatItem.msg, 209 | textAlign: TextAlign.left, 210 | style: TextStyle(color: Colors.black, fontSize: 16.0), 211 | ), 212 | ) 213 | ], 214 | )), 215 | ], 216 | ), 217 | ), 218 | ); 219 | } 220 | } 221 | 222 | class ChatItem { 223 | var msg; 224 | int type; 225 | AnimationController animationController; 226 | 227 | ChatItem(this.msg, this.type, this.animationController); 228 | 229 | String getMsg() { 230 | return msg; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /lib/views/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/model/conversation.dart' 3 | show 4 | Conversation, 5 | mockConversation, 6 | preConversation, 7 | ConversationControlModel, 8 | Manager; 9 | import 'package:flutter_weixin/utils/net_utils.dart'; 10 | import 'package:flutter_weixin/components/PullLoadWidget.dart'; 11 | import 'package:flutter_weixin/components/ListState.dart'; 12 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 13 | import 'package:flutter_weixin/common/style/Style.dart'; 14 | import 'package:flutter_weixin/views/home_chat_page.dart'; 15 | import 'dart:math'; 16 | 17 | class HomePage extends StatefulWidget { 18 | @override 19 | _HomePageState createState() => _HomePageState(); 20 | } 21 | 22 | class _HomePageState extends State 23 | with 24 | AutomaticKeepAliveClientMixin, 25 | ListState, 26 | WidgetsBindingObserver { 27 | ConversationControlModel _conversationControlModel = 28 | new ConversationControlModel(); 29 | Manager manager = new Manager(); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | super.build(context); // 如果不加这句,从子页面回来会重新加载didChangeDependencies()方法 34 | return PullLoadWidget( 35 | pullLoadWidgetControl, 36 | (BuildContext context, int index) { 37 | if (index == 0) { 38 | return _DeviceInfoItem(); 39 | } 40 | return _ConversationItem( 41 | conversation: pullLoadWidgetControl.dataList[index]); 42 | }, 43 | handleRefresh, 44 | onLoadMore, 45 | refreshKey: refreshIndicatorKey, 46 | ); 47 | } 48 | 49 | @override 50 | bool get isRefreshFirst => false; 51 | 52 | // 只会执行一次initState() 53 | @override 54 | bool get wantKeepAlive => true; 55 | 56 | @override 57 | Future handleRefresh() async { 58 | if (isLoading) { 59 | return null; 60 | } 61 | isLoading = true; 62 | page = 1; 63 | mockConversation.clear(); 64 | mockConversation.addAll(preConversation); 65 | _conversationControlModel.clear(); 66 | await getIndexListData(page); 67 | setState(() { 68 | pullLoadWidgetControl.needLoadMore = 69 | (mockConversation != null && mockConversation.length == 14); 70 | }); 71 | isLoading = false; 72 | return null; 73 | } 74 | 75 | // 紧跟在initState之后调用 76 | @override 77 | void didChangeDependencies() { 78 | mockConversation.addAll(preConversation); 79 | pullLoadWidgetControl.dataList = mockConversation; 80 | _conversationControlModel.clear(); 81 | getIndexListData(1); 82 | setState(() => {pullLoadWidgetControl.needLoadMore = true}); 83 | super.didChangeDependencies(); 84 | } 85 | 86 | @override 87 | Future onLoadMore() async { 88 | if (isLoading) { 89 | return null; 90 | } 91 | isLoading = true; 92 | page++; 93 | await getIndexListData(page); 94 | setState(() { 95 | // 3次加载数据 96 | pullLoadWidgetControl.needLoadMore = 97 | (mockConversation != null && mockConversation.length < 25); 98 | }); 99 | isLoading = false; 100 | return null; 101 | } 102 | 103 | @override 104 | void initState() { 105 | // TODO: implement initState 106 | super.initState(); 107 | WidgetsBinding.instance.addObserver(this); 108 | // getIndexListData(1); 109 | } 110 | 111 | @override 112 | void dispose() { 113 | WidgetsBinding.instance.removeObserver(this); 114 | super.dispose(); 115 | } 116 | 117 | @override 118 | void setState(fn) { 119 | // TODO: implement setState 120 | super.setState(fn); 121 | } 122 | 123 | getIndexListData(page) async { 124 | try { 125 | var response = 126 | await NetUtils.get('https://randomuser.me/api/?results=10'); 127 | List arr= []; 128 | for (int i = 0; i < response['results'].length; i++) { 129 | response['results'][i]['unReadMsgCount'] = 130 | i == Random().nextInt(10) ? Random().nextInt(20) : 0; 131 | arr.add(Conversation.fromJson(response['results'][i])); 132 | await _conversationControlModel.insert(Conversation.fromJson(response['results'][i])); 133 | } 134 | manager.setSate(true); 135 | setState(() { 136 | mockConversation.addAll(arr); 137 | }); 138 | } catch (e) { 139 | print(e); 140 | } 141 | } 142 | } 143 | 144 | class _DeviceInfoItem extends StatelessWidget { 145 | @override 146 | Widget build(BuildContext context) { 147 | return Container( 148 | padding: EdgeInsets.only(top: 12.0, bottom: 12.0), 149 | decoration: BoxDecoration( 150 | border: Border( 151 | top: BorderSide(color: Color(0xffd9d9d9), width: .4), 152 | bottom: BorderSide(color: Color(0xffd9d9d9), width: .5)), 153 | color: Color(0xffEDEDED)), 154 | child: Row( 155 | mainAxisAlignment: MainAxisAlignment.start, 156 | crossAxisAlignment: CrossAxisAlignment.center, 157 | children: [ 158 | Padding( 159 | padding: EdgeInsets.only(left: 22.0, right: 25.0), 160 | child: Icon(ICons.XIANSHIQI), 161 | ), 162 | Text( 163 | 'Windows 微信已登录,手机通知已关闭', 164 | style: TextStyle( 165 | fontSize: 13.5, 166 | color: Colors.black54, 167 | fontWeight: FontWeight.w500), 168 | ) 169 | ], 170 | ), 171 | ); 172 | } 173 | } 174 | 175 | class _ConversationItem extends StatelessWidget { 176 | const _ConversationItem({Key key, this.conversation}) 177 | : assert(conversation != null), 178 | super(key: key); 179 | final Conversation conversation; 180 | 181 | @override 182 | Widget build(BuildContext context) { 183 | // 头像组件 184 | Widget userImage = new UserIconWidget( 185 | padding: const EdgeInsets.only(top: 0.0, right: 8.0, left: 10.0), 186 | width: 50.0, 187 | height: 50.0, 188 | image: conversation.avatar, 189 | isNetwork: conversation.isNetwork, 190 | onPressed: () { 191 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 192 | }); 193 | 194 | // 未读消息角标 195 | Widget unReadMsgCountText; 196 | if (conversation.unReadMsgCount > 0) { 197 | unReadMsgCountText = Positioned( 198 | child: Container( 199 | width: 18.0, 200 | height: 18.0, 201 | alignment: Alignment.center, 202 | decoration: BoxDecoration( 203 | borderRadius: BorderRadius.circular(20 / 2.0), 204 | color: Color(0xffff3e3e)), 205 | child: Text( 206 | conversation.unReadMsgCount.toString(), 207 | style: TextStyle( 208 | fontSize: 12.0, 209 | fontWeight: FontWeight.bold, 210 | color: Color(0xffffffff)), 211 | ), 212 | ), 213 | right: 0.0, 214 | top: -5.0, 215 | ); 216 | } else if (conversation.displayDot) { 217 | unReadMsgCountText = Positioned( 218 | child: Container( 219 | width: 10.0, 220 | height: 10.0, 221 | decoration: BoxDecoration( 222 | borderRadius: BorderRadius.circular(20 / 2.0), 223 | color: Color(0xffff3e3e)), 224 | ), 225 | right: 2.0, 226 | top: -5.0, 227 | ); 228 | } else { 229 | unReadMsgCountText = Positioned( 230 | child: Container(), 231 | right: 0.0, 232 | top: -5.0, 233 | ); 234 | } 235 | 236 | return Container( 237 | height: 75, 238 | child: 239 | RawMaterialButton(onPressed: (){ 240 | Navigator.push(context, MaterialPageRoute(builder: (c) { 241 | return new HomeChatPage(conversation: conversation); 242 | })); 243 | }, 244 | child: Row( 245 | mainAxisAlignment: MainAxisAlignment.center, 246 | children: [ 247 | Stack( 248 | overflow: Overflow.visible, 249 | children: [ 250 | userImage, 251 | unReadMsgCountText, 252 | ], 253 | ), 254 | Expanded( 255 | child: Container( 256 | decoration: BoxDecoration( 257 | border: Border( 258 | bottom: BorderSide(color: Color(0xffd9d9d9), width: .5))), 259 | padding: EdgeInsets.only(top: 14.0), 260 | child: Column( 261 | crossAxisAlignment: CrossAxisAlignment.start, 262 | children: [ 263 | Text( 264 | conversation.title, 265 | style: TextStyle(fontSize: 17.5), 266 | ), 267 | Container( 268 | height: 2.0, 269 | ), 270 | Text( 271 | conversation.describtion, 272 | maxLines: 1, 273 | style: TextStyle(color: Colors.grey, fontSize: 13.0), 274 | ) 275 | ], 276 | ), 277 | )), 278 | Container( 279 | decoration: BoxDecoration( 280 | border: Border( 281 | bottom: BorderSide(color: Color(0xffd9d9d9), width: .5))), 282 | padding: EdgeInsets.only(top: 12.0, right: 10.0), 283 | child: Column( 284 | children: [ 285 | Text( 286 | conversation.createAt, 287 | style: TextStyle(color: Color(0xffBEBEBE), fontSize: 13.0), 288 | ), 289 | ], 290 | )) 291 | ], 292 | ),), 293 | ); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /lib/views/my_info_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/common/style/Style.dart' show ICons; 3 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 4 | 5 | class MyInfoPage extends StatefulWidget { 6 | @override 7 | _MyInfoPageState createState() => _MyInfoPageState(); 8 | } 9 | 10 | class _MyInfoPageState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | Widget buildRow(child, title, isEnd) { 14 | return Row( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | Expanded( 18 | child: Container( 19 | decoration: !isEnd 20 | ? BoxDecoration( 21 | border: Border( 22 | bottom: 23 | BorderSide(color: Color(0xffd9d9d9), width: .3))) 24 | : null, 25 | padding: EdgeInsets.only(top: 16.0), 26 | margin: EdgeInsets.only(left: 15.0), 27 | child: Row( 28 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 | children: [ 30 | Container( 31 | margin: EdgeInsets.only(bottom: 15.0), 32 | child: Text( 33 | title, 34 | style: TextStyle(fontSize: 15.0), 35 | ), 36 | ), 37 | Container( 38 | child: Row( 39 | children: [ 40 | Container( 41 | padding: EdgeInsets.only(bottom: 15.0, right: 5.0), 42 | child: child, 43 | ), 44 | Container( 45 | padding: EdgeInsets.only(bottom: 15.0, right: 10.0), 46 | child: Icon( 47 | Icons.chevron_right, 48 | color: Colors.grey, 49 | ), 50 | ) 51 | ], 52 | ), 53 | ), 54 | ], 55 | ), 56 | ), 57 | ), 58 | ], 59 | ); 60 | } 61 | // 头像组件 62 | Widget userImage = new UserIconWidget( 63 | padding: const EdgeInsets.only(right: 0.0), 64 | width: 55.0, 65 | height: 55.0, 66 | image: 'static/images/default_nor_avatar.png', 67 | isNetwork: false, 68 | onPressed: () { 69 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 70 | }); 71 | return new Scaffold( 72 | appBar: AppBar( 73 | title: Text('个人信息'), 74 | elevation: 0.0, 75 | ), 76 | body: new SingleChildScrollView( 77 | child: new Container( 78 | child: new Column( 79 | crossAxisAlignment: CrossAxisAlignment.start, 80 | children: [ 81 | buildRow(userImage, '头像', false), 82 | buildRow(Text('leeo', style: TextStyle(color: Colors.grey, fontSize: 18.0),), '昵称', false), 83 | buildRow(Text('zwleee', style: TextStyle(color: Colors.grey, fontSize: 18.0),), '微信号', false), 84 | buildRow( 85 | Icon( 86 | ICons.QR, 87 | color: Colors.grey, 88 | size: 20.0, 89 | ), 90 | '二维码名片', 91 | false), 92 | buildRow(Text(''), '更多', true), 93 | ], 94 | ), 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/views/my_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_weixin/components/UserIconWidget.dart'; 3 | import 'package:flutter_weixin/common/style/Style.dart' show ICons; 4 | import 'package:flutter_weixin/views/my_info_page.dart'; 5 | 6 | class MyPage extends StatefulWidget { 7 | @override 8 | _MyPageState createState() => _MyPageState(); 9 | } 10 | 11 | class _MyPageState extends State { 12 | @override 13 | void initState() { 14 | // TODO: implement initState 15 | super.initState(); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | // 头像组件 21 | Widget userImage = new UserIconWidget( 22 | padding: const EdgeInsets.only(top: 28.0, right: 18.0, left: 25.0), 23 | width: 55.0, 24 | height: 55.0, 25 | image: 'static/images/default_nor_avatar.png', 26 | isNetwork: false, 27 | onPressed: () { 28 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 29 | }); 30 | Widget buildRow(icon, title, isEnd) { 31 | return Row( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | new UserIconWidget( 35 | padding: const EdgeInsets.only(top: 0.0, right: 14.0, left: 14.0), 36 | width: 22.0, 37 | height: 22.0, 38 | image: icon, 39 | isNetwork: false, 40 | onPressed: () { 41 | // NavigatorUtils.goPerson(context, eventViewModel.actionUser); 42 | }), 43 | Expanded( 44 | child: Container( 45 | decoration: !isEnd 46 | ? BoxDecoration( 47 | border: Border( 48 | bottom: 49 | BorderSide(color: Color(0xffd9d9d9), width: .3))) 50 | : null, 51 | padding: EdgeInsets.only(top: 16.0), 52 | child: Row( 53 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 54 | children: [ 55 | Container( 56 | margin: EdgeInsets.only(bottom: 15.0), 57 | child: Text( 58 | title, 59 | style: TextStyle(fontSize: 15.0), 60 | ), 61 | ), 62 | Container( 63 | child: Container( 64 | margin: EdgeInsets.only(bottom: 15.0, right: 10.0), 65 | child: Icon( 66 | Icons.chevron_right, 67 | color: Colors.grey, 68 | ), 69 | ), 70 | ), 71 | ], 72 | ), 73 | ), 74 | ), 75 | ], 76 | ); 77 | } 78 | 79 | return Stack( 80 | children: [ 81 | ListView( 82 | children: [ 83 | Container( 84 | height: 180.0, 85 | child: 86 | RawMaterialButton( 87 | onPressed: () { 88 | Navigator.push(context, 89 | new MaterialPageRoute( 90 | builder: (c) { 91 | return new MyInfoPage(); 92 | }, 93 | ), 94 | ); 95 | }, 96 | child: Row( 97 | mainAxisAlignment: MainAxisAlignment.center, 98 | children: [ 99 | userImage, 100 | Expanded( 101 | child: Container( 102 | padding: EdgeInsets.only(top: 83.0), 103 | child: Column( 104 | crossAxisAlignment: CrossAxisAlignment.start, 105 | children: [ 106 | Text( 107 | 'leeo', 108 | style: TextStyle( 109 | fontSize: 22.5, fontWeight: FontWeight.bold), 110 | ), 111 | Container( 112 | height: 2.0, 113 | ), 114 | Text( 115 | '微信号:zwleee', 116 | maxLines: 1, 117 | style: TextStyle(color: Colors.grey, fontSize: 13.0), 118 | ) 119 | ], 120 | ), 121 | )), 122 | Row( 123 | children: [ 124 | Container( 125 | padding: EdgeInsets.only( 126 | top: 65.0, bottom: 15.0, right: 10.0), 127 | child: Icon( 128 | ICons.QR, 129 | color: Colors.grey, 130 | size: 15.0, 131 | ), 132 | ), 133 | Container( 134 | padding: EdgeInsets.only( 135 | top: 65.0, bottom: 15.0, right: 10.0), 136 | child: Icon( 137 | Icons.chevron_right, 138 | color: Colors.grey, 139 | ), 140 | ) 141 | ], 142 | ) 143 | ], 144 | ), 145 | ), 146 | ), 147 | Column( 148 | mainAxisAlignment: MainAxisAlignment.spaceAround, 149 | children: [ 150 | Container( 151 | color: Color(0xffEDEDED), 152 | height: 10.0, 153 | ), 154 | buildRow('static/images/me_pay.png', '支付', true), 155 | ], 156 | ), 157 | Container( 158 | child: Column( 159 | mainAxisAlignment: MainAxisAlignment.spaceAround, 160 | children: [ 161 | Container( 162 | color: Color(0xffEDEDED), 163 | height: 10.0, 164 | ), 165 | buildRow('static/images/me_college.png', '收藏', false), 166 | buildRow('static/images/me_gallary.png', '相册', false), 167 | buildRow('static/images/me_wallet.png', '卡包', false), 168 | buildRow('static/images/me_face.png', '表情', true), 169 | ], 170 | ), 171 | ), 172 | Column( 173 | mainAxisAlignment: MainAxisAlignment.spaceAround, 174 | children: [ 175 | Container( 176 | color: Color(0xffEDEDED), 177 | height: 10.0, 178 | ), 179 | buildRow('static/images/me_setting.png', '设置', true), 180 | ], 181 | ), 182 | ], 183 | ) 184 | ], 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "0.36.4" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.2.0" 25 | barcode_scan: 26 | dependency: "direct main" 27 | description: 28 | name: barcode_scan 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.0.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.0.4" 39 | build: 40 | dependency: transitive 41 | description: 42 | name: build 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.1.5" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.4.1" 53 | charcode: 54 | dependency: transitive 55 | description: 56 | name: charcode 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.1.2" 60 | checked_yaml: 61 | dependency: transitive 62 | description: 63 | name: checked_yaml 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "1.0.1" 67 | collection: 68 | dependency: transitive 69 | description: 70 | name: collection 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "1.14.11" 74 | convert: 75 | dependency: transitive 76 | description: 77 | name: convert 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "2.1.1" 81 | cookie_jar: 82 | dependency: transitive 83 | description: 84 | name: cookie_jar 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "1.0.1" 88 | crypto: 89 | dependency: transitive 90 | description: 91 | name: crypto 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "2.0.6" 95 | csslib: 96 | dependency: transitive 97 | description: 98 | name: csslib 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "0.16.1" 102 | cupertino_icons: 103 | dependency: "direct main" 104 | description: 105 | name: cupertino_icons 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "0.1.2" 109 | dart_style: 110 | dependency: transitive 111 | description: 112 | name: dart_style 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "1.2.9" 116 | dio: 117 | dependency: "direct main" 118 | description: 119 | name: dio 120 | url: "https://pub.flutter-io.cn" 121 | source: hosted 122 | version: "2.1.13" 123 | event_bus: 124 | dependency: "direct main" 125 | description: 126 | name: event_bus 127 | url: "https://pub.flutter-io.cn" 128 | source: hosted 129 | version: "1.1.0" 130 | fluro: 131 | dependency: "direct main" 132 | description: 133 | name: fluro 134 | url: "https://pub.flutter-io.cn" 135 | source: hosted 136 | version: "1.5.0" 137 | flutter: 138 | dependency: "direct main" 139 | description: flutter 140 | source: sdk 141 | version: "0.0.0" 142 | flutter_spinkit: 143 | dependency: "direct main" 144 | description: 145 | name: flutter_spinkit 146 | url: "https://pub.flutter-io.cn" 147 | source: hosted 148 | version: "3.1.0" 149 | flutter_test: 150 | dependency: "direct dev" 151 | description: flutter 152 | source: sdk 153 | version: "0.0.0" 154 | fluttertoast: 155 | dependency: "direct main" 156 | description: 157 | name: fluttertoast 158 | url: "https://pub.flutter-io.cn" 159 | source: hosted 160 | version: "3.1.0" 161 | front_end: 162 | dependency: transitive 163 | description: 164 | name: front_end 165 | url: "https://pub.flutter-io.cn" 166 | source: hosted 167 | version: "0.1.19" 168 | glob: 169 | dependency: transitive 170 | description: 171 | name: glob 172 | url: "https://pub.flutter-io.cn" 173 | source: hosted 174 | version: "1.1.7" 175 | html: 176 | dependency: transitive 177 | description: 178 | name: html 179 | url: "https://pub.flutter-io.cn" 180 | source: hosted 181 | version: "0.14.0+2" 182 | image_picker: 183 | dependency: "direct main" 184 | description: 185 | name: image_picker 186 | url: "https://pub.flutter-io.cn" 187 | source: hosted 188 | version: "0.5.0+9" 189 | json_annotation: 190 | dependency: "direct main" 191 | description: 192 | name: json_annotation 193 | url: "https://pub.flutter-io.cn" 194 | source: hosted 195 | version: "2.3.0" 196 | json_serializable: 197 | dependency: "direct dev" 198 | description: 199 | name: json_serializable 200 | url: "https://pub.flutter-io.cn" 201 | source: hosted 202 | version: "2.3.0" 203 | kernel: 204 | dependency: transitive 205 | description: 206 | name: kernel 207 | url: "https://pub.flutter-io.cn" 208 | source: hosted 209 | version: "0.3.19" 210 | logging: 211 | dependency: transitive 212 | description: 213 | name: logging 214 | url: "https://pub.flutter-io.cn" 215 | source: hosted 216 | version: "0.11.3+2" 217 | matcher: 218 | dependency: transitive 219 | description: 220 | name: matcher 221 | url: "https://pub.flutter-io.cn" 222 | source: hosted 223 | version: "0.12.5" 224 | meta: 225 | dependency: transitive 226 | description: 227 | name: meta 228 | url: "https://pub.flutter-io.cn" 229 | source: hosted 230 | version: "1.1.6" 231 | package_config: 232 | dependency: transitive 233 | description: 234 | name: package_config 235 | url: "https://pub.flutter-io.cn" 236 | source: hosted 237 | version: "1.0.5" 238 | path: 239 | dependency: transitive 240 | description: 241 | name: path 242 | url: "https://pub.flutter-io.cn" 243 | source: hosted 244 | version: "1.6.2" 245 | pedantic: 246 | dependency: transitive 247 | description: 248 | name: pedantic 249 | url: "https://pub.flutter-io.cn" 250 | source: hosted 251 | version: "1.7.0" 252 | pub_semver: 253 | dependency: transitive 254 | description: 255 | name: pub_semver 256 | url: "https://pub.flutter-io.cn" 257 | source: hosted 258 | version: "1.4.2" 259 | pubspec_parse: 260 | dependency: transitive 261 | description: 262 | name: pubspec_parse 263 | url: "https://pub.flutter-io.cn" 264 | source: hosted 265 | version: "0.1.4" 266 | quiver: 267 | dependency: transitive 268 | description: 269 | name: quiver 270 | url: "https://pub.flutter-io.cn" 271 | source: hosted 272 | version: "2.0.3" 273 | shared_preferences: 274 | dependency: "direct main" 275 | description: 276 | name: shared_preferences 277 | url: "https://pub.flutter-io.cn" 278 | source: hosted 279 | version: "0.5.3+4" 280 | sky_engine: 281 | dependency: transitive 282 | description: flutter 283 | source: sdk 284 | version: "0.0.99" 285 | source_gen: 286 | dependency: transitive 287 | description: 288 | name: source_gen 289 | url: "https://pub.flutter-io.cn" 290 | source: hosted 291 | version: "0.9.4+2" 292 | source_span: 293 | dependency: transitive 294 | description: 295 | name: source_span 296 | url: "https://pub.flutter-io.cn" 297 | source: hosted 298 | version: "1.5.5" 299 | sqflite: 300 | dependency: "direct main" 301 | description: 302 | name: sqflite 303 | url: "https://pub.flutter-io.cn" 304 | source: hosted 305 | version: "1.1.6+1" 306 | stack_trace: 307 | dependency: transitive 308 | description: 309 | name: stack_trace 310 | url: "https://pub.flutter-io.cn" 311 | source: hosted 312 | version: "1.9.3" 313 | stream_channel: 314 | dependency: transitive 315 | description: 316 | name: stream_channel 317 | url: "https://pub.flutter-io.cn" 318 | source: hosted 319 | version: "2.0.0" 320 | string_scanner: 321 | dependency: transitive 322 | description: 323 | name: string_scanner 324 | url: "https://pub.flutter-io.cn" 325 | source: hosted 326 | version: "1.0.4" 327 | synchronized: 328 | dependency: transitive 329 | description: 330 | name: synchronized 331 | url: "https://pub.flutter-io.cn" 332 | source: hosted 333 | version: "2.1.0+1" 334 | term_glyph: 335 | dependency: transitive 336 | description: 337 | name: term_glyph 338 | url: "https://pub.flutter-io.cn" 339 | source: hosted 340 | version: "1.1.0" 341 | test_api: 342 | dependency: transitive 343 | description: 344 | name: test_api 345 | url: "https://pub.flutter-io.cn" 346 | source: hosted 347 | version: "0.2.5" 348 | typed_data: 349 | dependency: transitive 350 | description: 351 | name: typed_data 352 | url: "https://pub.flutter-io.cn" 353 | source: hosted 354 | version: "1.1.6" 355 | url_launcher: 356 | dependency: "direct main" 357 | description: 358 | name: url_launcher 359 | url: "https://pub.flutter-io.cn" 360 | source: hosted 361 | version: "5.1.0" 362 | vector_math: 363 | dependency: transitive 364 | description: 365 | name: vector_math 366 | url: "https://pub.flutter-io.cn" 367 | source: hosted 368 | version: "2.0.8" 369 | watcher: 370 | dependency: transitive 371 | description: 372 | name: watcher 373 | url: "https://pub.flutter-io.cn" 374 | source: hosted 375 | version: "0.9.7+12" 376 | yaml: 377 | dependency: transitive 378 | description: 379 | name: yaml 380 | url: "https://pub.flutter-io.cn" 381 | source: hosted 382 | version: "2.1.16" 383 | sdks: 384 | dart: ">=2.3.0 <3.0.0" 385 | flutter: ">=1.5.0 <2.0.0" 386 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_weixin 2 | description: A copy of WeiXin Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | sqflite: ^1.1.0 23 | fluro: ^1.3.4 24 | dio: ^2.0.11 25 | # 本地存储、收藏功能 26 | shared_preferences: ^0.5.1 27 | url_launcher: ^5.0.1 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^0.1.2 31 | flutter_spinkit: ^3.1.0 32 | json_annotation: ^2.0.0 33 | event_bus: ^1.0.3 34 | fluttertoast: ^3.0.3 35 | image_picker: 0.5.0+9 36 | barcode_scan: 1.0.0 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | json_serializable: ^2.0.2 41 | 42 | 43 | # For information on the generic Dart part of this file, see the 44 | # following page: https://www.dartlang.org/tools/pub/pubspec 45 | 46 | # The following section is specific to Flutter. 47 | flutter: 48 | 49 | # The following line ensures that the Material Icons font is 50 | # included with your application, so that you can use the icons in 51 | # the material Icons class. 52 | uses-material-design: true 53 | 54 | # To add assets to your application, add an assets section, like this: 55 | # assets: 56 | # - images/a_dot_burr.jpeg 57 | # - images/a_dot_ham.jpeg 58 | 59 | # An image asset can refer to one or more resolution-specific "variants", see 60 | # https://flutter.io/assets-and-images/#resolution-aware. 61 | 62 | # For details regarding adding assets from package dependencies, see 63 | # https://flutter.io/assets-and-images/#from-packages 64 | 65 | # To add custom fonts to your application, add a fonts section here, 66 | # in this "flutter" section. Each entry in this list should have a 67 | # "family" key with the font family name, and a "fonts" key with a 68 | # list giving the asset and other descriptors for the font. For 69 | # example: 70 | # fonts: 71 | # - family: Schyler 72 | # fonts: 73 | # - asset: fonts/Schyler-Regular.ttf 74 | # - asset: fonts/Schyler-Italic.ttf 75 | # style: italic 76 | # - family: Trajan Pro 77 | # fonts: 78 | # - asset: fonts/TrajanPro.ttf 79 | # - asset: fonts/TrajanPro_Bold.ttf 80 | # weight: 700 81 | # 82 | # For details regarding fonts from package dependencies, 83 | # see https://flutter.io/custom-fonts/#from-packages 84 | assets: 85 | - static/app.db 86 | - static/images/default_img.png 87 | - static/images/dianxin.jpg 88 | - static/images/xinwen.jpg 89 | - static/images/zushou.jpg 90 | - static/images/default_nor_avatar.png 91 | - static/images/contact_group.png 92 | - static/images/contact_new_friend.png 93 | - static/images/contact_public_number.png 94 | - static/images/contact_tag.png 95 | - static/images/find_friend_circle.png 96 | - static/images/find_scan.png 97 | - static/images/find_shake.png 98 | - static/images/find_show.png 99 | - static/images/find_sou.png 100 | - static/images/find_nearby.png 101 | - static/images/find_bottle.png 102 | - static/images/find_shop.png 103 | - static/images/find_game.png 104 | - static/images/find_xcx.png 105 | - static/images/me_wallet.png 106 | - static/images/me_college.png 107 | - static/images/me_gallary.png 108 | - static/images/me_setting.png 109 | - static/images/me_face.png 110 | - static/images/me_pay.png 111 | - static/images/chat_bg.png 112 | fonts: 113 | - family: wxIconFont 114 | fonts: 115 | - asset: static/font/iconfont.ttf -------------------------------------------------------------------------------- /static/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/app.db -------------------------------------------------------------------------------- /static/font/demo.css: -------------------------------------------------------------------------------- 1 | /* Logo 字体 */ 2 | @font-face { 3 | font-family: "iconfont logo"; 4 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); 5 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), 6 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), 7 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), 8 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); 9 | } 10 | 11 | .logo { 12 | font-family: "iconfont logo"; 13 | font-size: 160px; 14 | font-style: normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | /* tabs */ 20 | .nav-tabs { 21 | position: relative; 22 | } 23 | 24 | .nav-tabs .nav-more { 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 42px; 29 | line-height: 42px; 30 | color: #666; 31 | } 32 | 33 | #tabs { 34 | border-bottom: 1px solid #eee; 35 | } 36 | 37 | #tabs li { 38 | cursor: pointer; 39 | width: 100px; 40 | height: 40px; 41 | line-height: 40px; 42 | text-align: center; 43 | font-size: 16px; 44 | border-bottom: 2px solid transparent; 45 | position: relative; 46 | z-index: 1; 47 | margin-bottom: -1px; 48 | color: #666; 49 | } 50 | 51 | 52 | #tabs .active { 53 | border-bottom-color: #f00; 54 | color: #222; 55 | } 56 | 57 | .tab-container .content { 58 | display: none; 59 | } 60 | 61 | /* 页面布局 */ 62 | .main { 63 | padding: 30px 100px; 64 | width: 960px; 65 | margin: 0 auto; 66 | } 67 | 68 | .main .logo { 69 | color: #333; 70 | text-align: left; 71 | margin-bottom: 30px; 72 | line-height: 1; 73 | height: 110px; 74 | margin-top: -50px; 75 | overflow: hidden; 76 | *zoom: 1; 77 | } 78 | 79 | .main .logo a { 80 | font-size: 160px; 81 | color: #333; 82 | } 83 | 84 | .helps { 85 | margin-top: 40px; 86 | } 87 | 88 | .helps pre { 89 | padding: 20px; 90 | margin: 10px 0; 91 | border: solid 1px #e7e1cd; 92 | background-color: #fffdef; 93 | overflow: auto; 94 | } 95 | 96 | .icon_lists { 97 | width: 100% !important; 98 | overflow: hidden; 99 | *zoom: 1; 100 | } 101 | 102 | .icon_lists li { 103 | width: 100px; 104 | margin-bottom: 10px; 105 | margin-right: 20px; 106 | text-align: center; 107 | list-style: none !important; 108 | cursor: default; 109 | } 110 | 111 | .icon_lists li .code-name { 112 | line-height: 1.2; 113 | } 114 | 115 | .icon_lists .icon { 116 | display: block; 117 | height: 100px; 118 | line-height: 100px; 119 | font-size: 42px; 120 | margin: 10px auto; 121 | color: #333; 122 | -webkit-transition: font-size 0.25s linear, width 0.25s linear; 123 | -moz-transition: font-size 0.25s linear, width 0.25s linear; 124 | transition: font-size 0.25s linear, width 0.25s linear; 125 | } 126 | 127 | .icon_lists .icon:hover { 128 | font-size: 100px; 129 | } 130 | 131 | .icon_lists .svg-icon { 132 | /* 通过设置 font-size 来改变图标大小 */ 133 | width: 1em; 134 | /* 图标和文字相邻时,垂直对齐 */ 135 | vertical-align: -0.15em; 136 | /* 通过设置 color 来改变 SVG 的颜色/fill */ 137 | fill: currentColor; 138 | /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 139 | normalize.css 中也包含这行 */ 140 | overflow: hidden; 141 | } 142 | 143 | .icon_lists li .name, 144 | .icon_lists li .code-name { 145 | color: #666; 146 | } 147 | 148 | /* markdown 样式 */ 149 | .markdown { 150 | color: #666; 151 | font-size: 14px; 152 | line-height: 1.8; 153 | } 154 | 155 | .highlight { 156 | line-height: 1.5; 157 | } 158 | 159 | .markdown img { 160 | vertical-align: middle; 161 | max-width: 100%; 162 | } 163 | 164 | .markdown h1 { 165 | color: #404040; 166 | font-weight: 500; 167 | line-height: 40px; 168 | margin-bottom: 24px; 169 | } 170 | 171 | .markdown h2, 172 | .markdown h3, 173 | .markdown h4, 174 | .markdown h5, 175 | .markdown h6 { 176 | color: #404040; 177 | margin: 1.6em 0 0.6em 0; 178 | font-weight: 500; 179 | clear: both; 180 | } 181 | 182 | .markdown h1 { 183 | font-size: 28px; 184 | } 185 | 186 | .markdown h2 { 187 | font-size: 22px; 188 | } 189 | 190 | .markdown h3 { 191 | font-size: 16px; 192 | } 193 | 194 | .markdown h4 { 195 | font-size: 14px; 196 | } 197 | 198 | .markdown h5 { 199 | font-size: 12px; 200 | } 201 | 202 | .markdown h6 { 203 | font-size: 12px; 204 | } 205 | 206 | .markdown hr { 207 | height: 1px; 208 | border: 0; 209 | background: #e9e9e9; 210 | margin: 16px 0; 211 | clear: both; 212 | } 213 | 214 | .markdown p { 215 | margin: 1em 0; 216 | } 217 | 218 | .markdown>p, 219 | .markdown>blockquote, 220 | .markdown>.highlight, 221 | .markdown>ol, 222 | .markdown>ul { 223 | width: 80%; 224 | } 225 | 226 | .markdown ul>li { 227 | list-style: circle; 228 | } 229 | 230 | .markdown>ul li, 231 | .markdown blockquote ul>li { 232 | margin-left: 20px; 233 | padding-left: 4px; 234 | } 235 | 236 | .markdown>ul li p, 237 | .markdown>ol li p { 238 | margin: 0.6em 0; 239 | } 240 | 241 | .markdown ol>li { 242 | list-style: decimal; 243 | } 244 | 245 | .markdown>ol li, 246 | .markdown blockquote ol>li { 247 | margin-left: 20px; 248 | padding-left: 4px; 249 | } 250 | 251 | .markdown code { 252 | margin: 0 3px; 253 | padding: 0 5px; 254 | background: #eee; 255 | border-radius: 3px; 256 | } 257 | 258 | .markdown strong, 259 | .markdown b { 260 | font-weight: 600; 261 | } 262 | 263 | .markdown>table { 264 | border-collapse: collapse; 265 | border-spacing: 0px; 266 | empty-cells: show; 267 | border: 1px solid #e9e9e9; 268 | width: 95%; 269 | margin-bottom: 24px; 270 | } 271 | 272 | .markdown>table th { 273 | white-space: nowrap; 274 | color: #333; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown>table th, 279 | .markdown>table td { 280 | border: 1px solid #e9e9e9; 281 | padding: 8px 16px; 282 | text-align: left; 283 | } 284 | 285 | .markdown>table th { 286 | background: #F7F7F7; 287 | } 288 | 289 | .markdown blockquote { 290 | font-size: 90%; 291 | color: #999; 292 | border-left: 4px solid #e9e9e9; 293 | padding-left: 0.8em; 294 | margin: 1em 0; 295 | } 296 | 297 | .markdown blockquote p { 298 | margin: 0; 299 | } 300 | 301 | .markdown .anchor { 302 | opacity: 0; 303 | transition: opacity 0.3s ease; 304 | margin-left: 8px; 305 | } 306 | 307 | .markdown .waiting { 308 | color: #ccc; 309 | } 310 | 311 | .markdown h1:hover .anchor, 312 | .markdown h2:hover .anchor, 313 | .markdown h3:hover .anchor, 314 | .markdown h4:hover .anchor, 315 | .markdown h5:hover .anchor, 316 | .markdown h6:hover .anchor { 317 | opacity: 1; 318 | display: inline-block; 319 | } 320 | 321 | .markdown>br, 322 | .markdown>p>br { 323 | clear: both; 324 | } 325 | 326 | 327 | .hljs { 328 | display: block; 329 | background: white; 330 | padding: 0.5em; 331 | color: #333333; 332 | overflow-x: auto; 333 | } 334 | 335 | .hljs-comment, 336 | .hljs-meta { 337 | color: #969896; 338 | } 339 | 340 | .hljs-string, 341 | .hljs-variable, 342 | .hljs-template-variable, 343 | .hljs-strong, 344 | .hljs-emphasis, 345 | .hljs-quote { 346 | color: #df5000; 347 | } 348 | 349 | .hljs-keyword, 350 | .hljs-selector-tag, 351 | .hljs-type { 352 | color: #a71d5d; 353 | } 354 | 355 | .hljs-literal, 356 | .hljs-symbol, 357 | .hljs-bullet, 358 | .hljs-attribute { 359 | color: #0086b3; 360 | } 361 | 362 | .hljs-section, 363 | .hljs-name { 364 | color: #63a35c; 365 | } 366 | 367 | .hljs-tag { 368 | color: #333333; 369 | } 370 | 371 | .hljs-title, 372 | .hljs-attr, 373 | .hljs-selector-id, 374 | .hljs-selector-class, 375 | .hljs-selector-attr, 376 | .hljs-selector-pseudo { 377 | color: #795da3; 378 | } 379 | 380 | .hljs-addition { 381 | color: #55a532; 382 | background-color: #eaffea; 383 | } 384 | 385 | .hljs-deletion { 386 | color: #bd2c00; 387 | background-color: #ffecec; 388 | } 389 | 390 | .hljs-link { 391 | text-decoration: underline; 392 | } 393 | 394 | /* 代码高亮 */ 395 | /* PrismJS 1.15.0 396 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ 397 | /** 398 | * prism.js default theme for JavaScript, CSS and HTML 399 | * Based on dabblet (http://dabblet.com) 400 | * @author Lea Verou 401 | */ 402 | code[class*="language-"], 403 | pre[class*="language-"] { 404 | color: black; 405 | background: none; 406 | text-shadow: 0 1px white; 407 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 408 | text-align: left; 409 | white-space: pre; 410 | word-spacing: normal; 411 | word-break: normal; 412 | word-wrap: normal; 413 | line-height: 1.5; 414 | 415 | -moz-tab-size: 4; 416 | -o-tab-size: 4; 417 | tab-size: 4; 418 | 419 | -webkit-hyphens: none; 420 | -moz-hyphens: none; 421 | -ms-hyphens: none; 422 | hyphens: none; 423 | } 424 | 425 | pre[class*="language-"]::-moz-selection, 426 | pre[class*="language-"] ::-moz-selection, 427 | code[class*="language-"]::-moz-selection, 428 | code[class*="language-"] ::-moz-selection { 429 | text-shadow: none; 430 | background: #b3d4fc; 431 | } 432 | 433 | pre[class*="language-"]::selection, 434 | pre[class*="language-"] ::selection, 435 | code[class*="language-"]::selection, 436 | code[class*="language-"] ::selection { 437 | text-shadow: none; 438 | background: #b3d4fc; 439 | } 440 | 441 | @media print { 442 | 443 | code[class*="language-"], 444 | pre[class*="language-"] { 445 | text-shadow: none; 446 | } 447 | } 448 | 449 | /* Code blocks */ 450 | pre[class*="language-"] { 451 | padding: 1em; 452 | margin: .5em 0; 453 | overflow: auto; 454 | } 455 | 456 | :not(pre)>code[class*="language-"], 457 | pre[class*="language-"] { 458 | background: #f5f2f0; 459 | } 460 | 461 | /* Inline code */ 462 | :not(pre)>code[class*="language-"] { 463 | padding: .1em; 464 | border-radius: .3em; 465 | white-space: normal; 466 | } 467 | 468 | .token.comment, 469 | .token.prolog, 470 | .token.doctype, 471 | .token.cdata { 472 | color: slategray; 473 | } 474 | 475 | .token.punctuation { 476 | color: #999; 477 | } 478 | 479 | .namespace { 480 | opacity: .7; 481 | } 482 | 483 | .token.property, 484 | .token.tag, 485 | .token.boolean, 486 | .token.number, 487 | .token.constant, 488 | .token.symbol, 489 | .token.deleted { 490 | color: #905; 491 | } 492 | 493 | .token.selector, 494 | .token.attr-name, 495 | .token.string, 496 | .token.char, 497 | .token.builtin, 498 | .token.inserted { 499 | color: #690; 500 | } 501 | 502 | .token.operator, 503 | .token.entity, 504 | .token.url, 505 | .language-css .token.string, 506 | .style .token.string { 507 | color: #9a6e3a; 508 | background: hsla(0, 0%, 100%, .5); 509 | } 510 | 511 | .token.atrule, 512 | .token.attr-value, 513 | .token.keyword { 514 | color: #07a; 515 | } 516 | 517 | .token.function, 518 | .token.class-name { 519 | color: #DD4A68; 520 | } 521 | 522 | .token.regex, 523 | .token.important, 524 | .token.variable { 525 | color: #e90; 526 | } 527 | 528 | .token.important, 529 | .token.bold { 530 | font-weight: bold; 531 | } 532 | 533 | .token.italic { 534 | font-style: italic; 535 | } 536 | 537 | .token.entity { 538 | cursor: help; 539 | } 540 | -------------------------------------------------------------------------------- /static/font/demo_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IconFont Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 27 |
28 |
29 |
    30 | 31 |
  • 32 | 33 |
    34 |
    &#xe626;
    35 |
  • 36 | 37 |
  • 38 | 39 |
    我cur
    40 |
    &#xe627;
    41 |
  • 42 | 43 |
  • 44 | 45 |
    发现1
    46 |
    &#xe746;
    47 |
  • 48 | 49 |
  • 50 | 51 |
    通讯录
    52 |
    &#xe687;
    53 |
  • 54 | 55 |
  • 56 | 57 |
    消息4
    58 |
    &#xe619;
    59 |
  • 60 | 61 |
  • 62 | 63 |
    消息
    64 |
    &#xe65d;
    65 |
  • 66 | 67 |
  • 68 | 69 |
    发现,指南针
    70 |
    &#xe60f;
    71 |
  • 72 | 73 |
  • 74 | 75 |
    电脑显示器-01
    76 |
    &#xe61d;
    77 |
  • 78 | 79 |
  • 80 | 81 |
    二维码
    82 |
    &#xe646;
    83 |
  • 84 | 85 |
  • 86 | 87 |
    通讯录
    88 |
    &#xe711;
    89 |
  • 90 | 91 |
92 |
93 |

Unicode 引用

94 |
95 | 96 |

Unicode 是字体在网页端最原始的应用方式,特点是:

97 |
    98 |
  • 兼容性最好,支持 IE6+,及所有现代浏览器。
  • 99 |
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • 100 |
  • 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。
  • 101 |
102 |
103 |

注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式

104 |
105 |

Unicode 使用步骤如下:

106 |

第一步:拷贝项目下面生成的 @font-face

107 |
@font-face {
109 |   font-family: 'iconfont';
110 |   src: url('iconfont.eot');
111 |   src: url('iconfont.eot?#iefix') format('embedded-opentype'),
112 |       url('iconfont.woff2') format('woff2'),
113 |       url('iconfont.woff') format('woff'),
114 |       url('iconfont.ttf') format('truetype'),
115 |       url('iconfont.svg#iconfont') format('svg');
116 | }
117 | 
118 |

第二步:定义使用 iconfont 的样式

119 |
.iconfont {
121 |   font-family: "iconfont" !important;
122 |   font-size: 16px;
123 |   font-style: normal;
124 |   -webkit-font-smoothing: antialiased;
125 |   -moz-osx-font-smoothing: grayscale;
126 | }
127 | 
128 |

第三步:挑选相应图标并获取字体编码,应用于页面

129 |
130 | <span class="iconfont">&#x33;</span>
132 | 
133 |
134 |

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

135 |
136 |
137 |
138 |
139 |
    140 | 141 |
  • 142 | 143 |
    144 | 我 145 |
    146 |
    .icon-wo 147 |
    148 |
  • 149 | 150 |
  • 151 | 152 |
    153 | 我cur 154 |
    155 |
    .icon-wocur 156 |
    157 |
  • 158 | 159 |
  • 160 | 161 |
    162 | 发现1 163 |
    164 |
    .icon-faxian1 165 |
    166 |
  • 167 | 168 |
  • 169 | 170 |
    171 | 通讯录 172 |
    173 |
    .icon-tongxunlu 174 |
    175 |
  • 176 | 177 |
  • 178 | 179 |
    180 | 消息4 181 |
    182 |
    .icon-xiaoxi 183 |
    184 |
  • 185 | 186 |
  • 187 | 188 |
    189 | 消息 190 |
    191 |
    .icon-xiaoxi1 192 |
    193 |
  • 194 | 195 |
  • 196 | 197 |
    198 | 发现,指南针 199 |
    200 |
    .icon-faxianzhinanzhen 201 |
    202 |
  • 203 | 204 |
  • 205 | 206 |
    207 | 电脑显示器-01 208 |
    209 |
    .icon-diannaoxianshiqi- 210 |
    211 |
  • 212 | 213 |
  • 214 | 215 |
    216 | 二维码 217 |
    218 |
    .icon-ico 219 |
    220 |
  • 221 | 222 |
  • 223 | 224 |
    225 | 通讯录 226 |
    227 |
    .icon-cedaohang-tongxunlu 228 |
    229 |
  • 230 | 231 |
232 |
233 |

font-class 引用

234 |
235 | 236 |

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

237 |

与 Unicode 使用方式相比,具有如下特点:

238 |
    239 |
  • 兼容性良好,支持 IE8+,及所有现代浏览器。
  • 240 |
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • 241 |
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • 242 |
  • 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
  • 243 |
244 |

使用步骤如下:

245 |

第一步:引入项目下面生成的 fontclass 代码:

246 |
<link rel="stylesheet" href="./iconfont.css">
247 | 
248 |

第二步:挑选相应图标并获取类名,应用于页面:

249 |
<span class="iconfont icon-xxx"></span>
250 | 
251 |
252 |

" 253 | iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

254 |
255 |
256 |
257 |
258 |
    259 | 260 |
  • 261 | 264 |
    265 |
    #icon-wo
    266 |
  • 267 | 268 |
  • 269 | 272 |
    我cur
    273 |
    #icon-wocur
    274 |
  • 275 | 276 |
  • 277 | 280 |
    发现1
    281 |
    #icon-faxian1
    282 |
  • 283 | 284 |
  • 285 | 288 |
    通讯录
    289 |
    #icon-tongxunlu
    290 |
  • 291 | 292 |
  • 293 | 296 |
    消息4
    297 |
    #icon-xiaoxi
    298 |
  • 299 | 300 |
  • 301 | 304 |
    消息
    305 |
    #icon-xiaoxi1
    306 |
  • 307 | 308 |
  • 309 | 312 |
    发现,指南针
    313 |
    #icon-faxianzhinanzhen
    314 |
  • 315 | 316 |
  • 317 | 320 |
    电脑显示器-01
    321 |
    #icon-diannaoxianshiqi-
    322 |
  • 323 | 324 |
  • 325 | 328 |
    二维码
    329 |
    #icon-ico
    330 |
  • 331 | 332 |
  • 333 | 336 |
    通讯录
    337 |
    #icon-cedaohang-tongxunlu
    338 |
  • 339 | 340 |
341 |
342 |

Symbol 引用

343 |
344 | 345 |

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 346 | 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

347 |
    348 |
  • 支持多色图标了,不再受单色限制。
  • 349 |
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • 350 |
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • 351 |
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • 352 |
353 |

使用步骤如下:

354 |

第一步:引入项目下面生成的 symbol 代码:

355 |
<script src="./iconfont.js"></script>
356 | 
357 |

第二步:加入通用 CSS 代码(引入一次就行):

358 |
<style>
359 | .icon {
360 |   width: 1em;
361 |   height: 1em;
362 |   vertical-align: -0.15em;
363 |   fill: currentColor;
364 |   overflow: hidden;
365 | }
366 | </style>
367 | 
368 |

第三步:挑选相应图标并获取类名,应用于页面:

369 |
<svg class="icon" aria-hidden="true">
370 |   <use xlink:href="#icon-xxx"></use>
371 | </svg>
372 | 
373 |
374 |
375 | 376 |
377 |
378 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /static/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1552457679899'); /* IE9 */ 3 | src: url('iconfont.eot?t=1552457679899#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAsUAAsAAAAAE9QAAArGAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCELgqYfJN0ATYCJAMsCxgABCAFhG0HgSAbbBCjooZyUqBkf5FgG9N6+GAglkUTY9NsfEekoGkG63mdLYaJsCWsPyUg81BKgmjZqp6di1EYUlRRI6NRKDz2JCiyR6HlHP8v/N/Ps83e538R0DGwFoWr1HW57UovWy+Kq+Y6yquu/522f2Mqts7z3fIUa6k5culPmraX33QxPE6jFEKCcAjJpC6VWpYSFoRhLdDY+6mjocDOViZ5Ss9eBU/lfy7yRCMabi/FwLz7tSqiUYdpKFLT305cwXeISIIISUMipDNHvPFIPNJBiFBCImegzcXp+odGx9r0MCoZYfV3PkwAbYEcncv90wsYIzxmwIwHrTiMayKEUBwYLo2JmqFXKkx6TmcAeEl/P/6BKBiQqCpe0Lv7vRg2S/syUykrAQ2ThDyeAkosUAH7QAbxg1V9wzljP2VxuPlEN2BqSXwsKe2ltwyW6ZIpe8risnOZWa2wBpja2NMEg5+5oQSNnf2HB6GwaAQHg6hDrC6kPzHbZ0wO22iA0ttGDZTBNpqgTLURoEy3kUDJQEsNZQ8wgLgsQhsZLDsF2ik7AyKVmNqZPwdOQL4Azn9Iv2Kl18tS3SKD2EnGOTuSpDUibf++7XGuIOgI6XabOQ8FglrrugSzIT5Elyx68K1BrzcYBgQGaLW9Na25908UtVXu8sIj7qbzpsA/JkMwTV0cxzYQ0RaVo3XbVg961W3HCT4OwW8pRHeTDPoUjYA78UZQfI+sWpG2m5veSRaT/Dmn0eAsfZPPDmuXoj9W/NdO/7vJvx3ZyIqiCUkCpnkhddN02tQXkKiHTejnK0Ep60LNkAQExYbDvRG1cRQu0bqfagtqPQCHSisRyjMJbN8kV59srzbhcr+B4G5JSAvT7gqhXzC8Om1eyg2DutF3s//WeoMhBiF9MZgbyGYJlFTRstxPKpUDBIGXosg6F4X7Ah+RLEnquZ9tbHfibKDbts1wDNjpLYo8b9d+e9ZydzGRGPaeJ7m+0KZ93q7EQQyQGxq8jkpjlS6b053lOsiidlHKZFDRTmO6Qmt2vhN0cCRUaKBfKV3a2usqpNQjgWxvblJhHPJXw5pDH7L8lEbyiF5sh1QB2QkUaA2hNhQ7AQpIiWTBAixoDtQDr/ZzSYtrxsIDVunL98NbmTdtsKu4PP4CBNNxvqza7Ic+yzcGJWZe5XWPIEC5pwf7oykEglhuJNNwjaB1HA4K7xP1EExVJEfFQ1qu967FUJuQaA34nO7zmdqxIlY4NlQGK+jLVmtxgh5ioB0xOMG9GQr6fFiY/oo1pryVQcmsEK9HzQ0c2nHi5ZoSer6OS9lj4MqxafwVL5s8wySIFdPE7K1iDpcSJVZpRhIDC8cWI+ylUw4GTJ2W+1ZoRh/L/JBqr9QDcuFy3LhaEiCoUmaecNrSmkgJiEll+M5MqajCJlVICoNuEdVxW2sUgIWqAF+vjm3uSc1QAJGEwSURQEi40MEzzGUxvR/QUrA1GpJIBkB7Yy+YogDxEfRTDvtwaHytHRPKLqkZPLo7Gptg/ij1dZtm29U4o8Y0P3d15p6EDa8UPsqXK3HrMnZG7m1w7Kp54ZXi49eCl1xvhbZdDCBzLyTz2y8FIiWaUuJ5IkS1uJQ/CsChBZfTUg6qazgfVxj4y1KDWEfEhZd/2ZuS7tpaqGr/3pWoYBsa1BM5aoWn9Hqv/vProCOiQax0mTdU5DdzO/Eh8028xD5xNaKb2hVfewtHVA2jrffZAPMhfNoJgjBdAOh14nRjQzXiJtVC3Y0/HgIxNVOHWurh/J4tL/kv7u2qWs+3yuB/kSgDJKzcUEmS6oaJwIhW3iaTTuzw0uaw2153CJWaaVrt6VRYz65e7QUhX4f7UUmmM7FnTPzxcb7jjsOvhoRVJkzkuvnECQoDMxmaLYCpKjAytWzWeMP/teNFF4pysbvZ6swe1jXBxe5hqoup5BTkJApcMB6keF+3XxSG+xUWdgvqVj1xt4CZwnPd4/9+CxtSkW6CfZrkZmPvzHbQSES0Y/adsc2Kf3+AjfSKIWHf/sZ3P1fIYGE3QNDjwFX4wVE8Bc6qjS1O2LSuTUn4SodPppjIrSHoc/xe/PsWWQqPDefbU5dMar4/H/aRlnwJWcqN990pg3x9rODuak9oOBVu8NSnR9fidVhVeCqeMhHMi6RZs6QNVli4unN2dmefZfvwn7/kgwV//kR00qRy7l/N79Ll5O5dUvQVVbUcr8AQ6MuZB0my1HGhaWGIrzvsvdn7wCbkI29dpDBTH6qfKaS+khY75d1sKLtbjhT+eJIH7W7t3XpLkjd313CX4/v1g/iuagvuAl9XtfPl3KTFymmTuVvazbWd2PoCUO7655M4QaxHNYmgC5ZoHoLlCb01g77FvpYfNY/KyuIv6fc+8m7dmo+znua97/OHr83Ns2Jn7c0et9hJ01e2IG42ut2qL2IvkCixk6Tbr1Nk6AQXLodl4XtspGU6ZVdLunqi1LdpnWKif2kOf6y9ZFffph2LtQ+1Wxz0Q6VHskrhG/Ahcif+UnE7Y6dOanVaLmewmPWS2o6mWyK5e1RW2BFPubOPuF9HzILtjiSd89gc1gA/UJ/vgkSLtTmIzytKCCW9sox/PnmiIFLUCD3u2BGBnC5P9GYfgLjx4IEop+vCpeC79033We/+7tR8wbwxYKLCDdp2skDCuCTzO3jWJAG6nvd+YGgQuuXgZZAoc/z7aK++kihRRVAIB+mxvJ4Uu03cpOfrc5HqnoNRxAm/Jk5+Pt7LO6hgulCds+K6Ga2lIqn+uOcg32TXY2S6/uk6t0LTx5URvrPppJlcX4NOfsH5ZV4W6nJ9J8k/0R6eGURafKgzKeqSHhOZ1fU92O8UGa42eXEaH4pqEoISiGdczCZCoQBAkcwOI4UqI6CTajHlKssWbRneyGVuJgj8AeSmJ2gElRGlCiUUWY94KKhNqzmhSUcsUR+nKagECIJYjnrYMNQL7q6553VPk8F3Xu+4fZVS03uOP3MxKA9uerew+8J7Ds+IDKqkugV5fHzBS3QZaV8tp6TwollWVNnprKQi1NIZxDq6erh0ynKsmd1mMxm3id1eZlPR7+0UQanFHGRqKU0VJ2NLzkmTKzWl19BNK8lpOclljFMBegAA8P+HjxlkwK2p//W6G6MwVzwOBw5e2o8TUv5BMwog+T7EGQHwGBYImkU4/zPiDZZufzKgiWf2Vwb/tZy6sf7g+dT9f6UVAngBNFE8dYCPBplxT7apImmMHQlJPes6qc44mIC2fwh8LHRjv+NzZszk7xPHWIJELQBUGKHJOT0W1GgkgzpGHmiLcRC7Q6/Ug3IL2PUpAETwBSi8AwATfBMQpP8AFZM/QBMCA07t0N9QIzzaTItK5DXGbP7OtCAjR1pPs5lnqJqcl1VqWn2FZaWtLIy3NMcO0GA5RoiqVVFdSyZLWrB94XGY58SKklIUtZfUdTHzE1y4kJ6gBUwVKSGujpvHmLn33ppADHlybdr6+TNIaeS4krCh1fsKKlX01ZmQJ2BIPmAZpg3XMr/SUiI1UkgsXF4iC8w+UqDcHyZMgfeUQkLNk/QYLMz46CDJyvLm5yye9yVo817UiRRV5KhFPRrRjFa0oxNd1CMN5paiKUcu/qa5CSfdyKi3xuTNsE6nZY8Scbj00/iRaBMb0CzHVabBz+emSrTVbu9KA60KjDkl3Ch3FCEAAA==') format('woff2'), 5 | url('iconfont.woff?t=1552457679899') format('woff'), 6 | url('iconfont.ttf?t=1552457679899') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1552457679899#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-wo:before { 19 | content: "\e626"; 20 | } 21 | 22 | .icon-wocur:before { 23 | content: "\e627"; 24 | } 25 | 26 | .icon-faxian1:before { 27 | content: "\e746"; 28 | } 29 | 30 | .icon-tongxunlu:before { 31 | content: "\e687"; 32 | } 33 | 34 | .icon-xiaoxi:before { 35 | content: "\e619"; 36 | } 37 | 38 | .icon-xiaoxi1:before { 39 | content: "\e65d"; 40 | } 41 | 42 | .icon-faxianzhinanzhen:before { 43 | content: "\e60f"; 44 | } 45 | 46 | .icon-diannaoxianshiqi-:before { 47 | content: "\e61d"; 48 | } 49 | 50 | .icon-ico:before { 51 | content: "\e646"; 52 | } 53 | 54 | .icon-cedaohang-tongxunlu:before { 55 | content: "\e711"; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /static/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/font/iconfont.eot -------------------------------------------------------------------------------- /static/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/font/iconfont.ttf -------------------------------------------------------------------------------- /static/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/font/iconfont.woff -------------------------------------------------------------------------------- /static/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/font/iconfont.woff2 -------------------------------------------------------------------------------- /static/images/chat_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/chat_bg.png -------------------------------------------------------------------------------- /static/images/contact_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/contact_group.png -------------------------------------------------------------------------------- /static/images/contact_new_friend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/contact_new_friend.png -------------------------------------------------------------------------------- /static/images/contact_public_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/contact_public_number.png -------------------------------------------------------------------------------- /static/images/contact_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/contact_tag.png -------------------------------------------------------------------------------- /static/images/default_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/default_img.png -------------------------------------------------------------------------------- /static/images/default_nor_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/default_nor_avatar.png -------------------------------------------------------------------------------- /static/images/dianxin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/dianxin.jpg -------------------------------------------------------------------------------- /static/images/find_bottle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_bottle.png -------------------------------------------------------------------------------- /static/images/find_friend_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_friend_circle.png -------------------------------------------------------------------------------- /static/images/find_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_game.png -------------------------------------------------------------------------------- /static/images/find_nearby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_nearby.png -------------------------------------------------------------------------------- /static/images/find_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_scan.png -------------------------------------------------------------------------------- /static/images/find_shake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_shake.png -------------------------------------------------------------------------------- /static/images/find_shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_shop.png -------------------------------------------------------------------------------- /static/images/find_show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_show.png -------------------------------------------------------------------------------- /static/images/find_sou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_sou.png -------------------------------------------------------------------------------- /static/images/find_xcx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/find_xcx.png -------------------------------------------------------------------------------- /static/images/home_chat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/home_chat.jpg -------------------------------------------------------------------------------- /static/images/main.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/main.gif -------------------------------------------------------------------------------- /static/images/main2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/main2.gif -------------------------------------------------------------------------------- /static/images/me_college.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_college.png -------------------------------------------------------------------------------- /static/images/me_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_face.png -------------------------------------------------------------------------------- /static/images/me_gallary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_gallary.png -------------------------------------------------------------------------------- /static/images/me_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_pay.png -------------------------------------------------------------------------------- /static/images/me_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_setting.png -------------------------------------------------------------------------------- /static/images/me_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/me_wallet.png -------------------------------------------------------------------------------- /static/images/wechat_contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_contact.jpg -------------------------------------------------------------------------------- /static/images/wechat_contacts_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_contacts_detail.jpg -------------------------------------------------------------------------------- /static/images/wechat_contacts_nf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_contacts_nf.jpg -------------------------------------------------------------------------------- /static/images/wechat_contacts_x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_contacts_x.jpg -------------------------------------------------------------------------------- /static/images/wechat_find.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_find.jpg -------------------------------------------------------------------------------- /static/images/wechat_home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_home.jpg -------------------------------------------------------------------------------- /static/images/wechat_me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/wechat_me.jpg -------------------------------------------------------------------------------- /static/images/xinwen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/xinwen.jpg -------------------------------------------------------------------------------- /static/images/xsG11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/xsG11.png -------------------------------------------------------------------------------- /static/images/zushou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeo-noder/flutter_weixin_practise/e8a1aedd3a8a45ecfd28403e9612eae680a51579/static/images/zushou.jpg -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_weixin/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------