├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── dy_flutter │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── dy.png │ │ │ ├── mipmap-mdpi │ │ │ └── dy.png │ │ │ ├── mipmap-xhdpi │ │ │ └── dy.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── dy.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── dy.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── dy_flutter.iml ├── images ├── back.webp ├── bar │ ├── boy.webp │ ├── chat-add.jpg │ ├── chat-share.jpg │ ├── chat-star-act.webp │ ├── chat-star.jpg │ ├── day-title.png │ ├── girl.webp │ ├── hot-chat.jpg │ ├── hot-title.png │ ├── pull-icon.webp │ ├── tab-0.png │ ├── tab-1.png │ ├── tab-2.png │ └── usefulSelect.webp ├── broadcast.webp ├── cate │ ├── tab-0.webp │ ├── tab-1.webp │ ├── tab-10.webp │ ├── tab-2.webp │ ├── tab-3.webp │ ├── tab-4.webp │ ├── tab-5.webp │ ├── tab-6.webp │ ├── tab-7.webp │ ├── tab-8.webp │ ├── tab-9.webp │ └── tab.webp ├── cfk.webp ├── change.png ├── cjf.webp ├── cqe.webp ├── default-avatar.webp ├── dg.webp ├── dg0.webp ├── float-icon.webp ├── fun_home_pull_down.png ├── gift-1.png ├── gift-banner.png ├── gift-x.png ├── gift.png ├── head │ ├── camera.webp │ ├── chat.webp │ ├── dylogo.png │ ├── game.webp │ ├── history.webp │ └── search.webp ├── hot.png ├── init_icon.png ├── init_logo.webp ├── login │ ├── close.webp │ ├── lock.webp │ ├── member.webp │ ├── qq.webp │ ├── safe.webp │ ├── syn.webp │ ├── weibo.webp │ └── wx.webp ├── lv │ ├── 3.png │ ├── 30.png │ ├── 50.png │ └── 80.png ├── member.png ├── nav │ ├── nav-11.jpg │ ├── nav-12.jpg │ ├── nav-21.jpg │ ├── nav-22.jpg │ ├── nav-31.jpg │ ├── nav-32.jpg │ ├── nav-41.jpg │ ├── nav-42.jpg │ ├── nav-51.jpg │ └── nav-52.jpg ├── num │ ├── 0.webp │ ├── 1.webp │ ├── 2.webp │ ├── 3.webp │ ├── 4.webp │ ├── 5.webp │ ├── 6.webp │ ├── 7.webp │ ├── 8.webp │ └── 9.webp ├── pic-default.jpg ├── play.png ├── shayuniang.png └── show-area.webp ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon20x20.png │ │ ├── icon20x20@2x.png │ │ ├── icon20x20@3x.png │ │ ├── icon29x29.png │ │ ├── icon29x29@2x.png │ │ ├── icon29x29@3x.png │ │ ├── icon40x40.png │ │ ├── icon40x40@2x.png │ │ ├── icon40x40@3x.png │ │ ├── icon60x60@2x.png │ │ ├── icon60x60@3x.png │ │ ├── icon76x76.png │ │ ├── icon76x76@2x.png │ │ └── icon83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── base.dart ├── bloc.dart ├── develop │ ├── develop.dart │ └── index.dart ├── dy_dialog │ ├── loading.dart │ └── login.dart ├── dy_index │ ├── commend │ │ ├── broadcast.dart │ │ ├── cate.dart │ │ ├── index.dart │ │ ├── list.dart │ │ └── swiper.dart │ ├── fishbar │ │ ├── cardList.dart │ │ ├── index.dart │ │ ├── myConcern.dart │ │ ├── photoGallery.dart │ │ └── picView.dart │ ├── focus │ │ └── index.dart │ ├── funny │ │ ├── index.dart │ │ └── lottery.dart │ ├── header.dart │ └── index.dart ├── dy_init │ ├── countdown.dart │ └── index.dart ├── dy_login │ ├── area.dart │ └── index.dart ├── dy_room │ ├── animate.dart │ ├── chat.dart │ ├── index.dart │ └── player.dart ├── httpUrl.dart ├── io.dart ├── main.dart ├── rx.dart └── service.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /.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: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart", 11 | "flutterMode": "debug" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

斗鱼APP

2 | 3 |

4 | flutter-1.22 5 | android✔ 6 | ios✔ 7 |

8 | 9 |

斗鱼Flutter

10 | 11 | flutter重构的斗鱼直播APP
12 | 首页、娱乐为Material组件;直播间、鱼吧为纯自定义编写。
13 | 另外整合各类优质的第三方开源库,打造出原生APP丝滑的用户体验
14 | 尽可能接入更多功能,方法附带注释,帮助你在使用flutter进行开发新的应用提供实用的借鉴案例
15 | 16 | #### APP截图: 17 | 18 | 19 | 22 | 25 | 28 | 29 | 30 | 33 | 36 | 39 | 40 | 41 | 44 | 47 | 50 | 51 | 52 | 55 | 58 | 61 | 62 |
20 | 21 | 23 | 24 | 26 | 27 |
31 | 32 | 34 | 35 | 37 | 38 |
42 | 43 | 45 | 46 | 48 | 49 |
53 | 54 | 56 | 57 | 59 | 60 |
63 | 64 | #### 包含功能: 65 | - 启动页广告位 66 | - 开播列表上拉加载、下拉刷新、返回顶部 67 | - 列表图片缓存加载优化 68 | - 渐进式头部动画 69 | - 底部导航切换保存页面状态 70 | - HTTP缓存、IO缓存 71 | - 直播间webSocket消息弹幕、礼物 72 | - 页面路由传值 73 | - RxDart全局消息通信封装 74 | - Bloc流式状态管理(启动页预加载首页数据) 75 | - 礼物横幅动画队列 76 | - 礼物特效全屏lottie 77 | - 弹幕消息滚动 78 | - 静态视频流 79 | - 九宫格抽奖游戏 80 | - 照片选择器 81 | - 全屏、半屏webView 82 | - 鱼吧头部手势动画 83 | - 仿微信朋友圈图片控件 84 | - 登录注册弹窗 85 | - 国家区号列表(仿微信通讯录滑动首字母定位) 86 | - 二维码扫码 87 | - 本地通知推送 88 | - ... 89 | - 持续增加中 90 | 91 | #### 本地调试: 92 | `flutter run --release`打包发布版本预览
93 | APP所有数据均来源Mock网络请求,服务端接口没有上云,可修改`lib/base.dart`中`DYBase.baseHost`为你的电脑IP,并确保手机与电脑在同一局域网且能访问内网`1236`端口
94 | 然后clone[服务端仓库](https://github.com/yukilzw/factory),Mock服务为`python tornado`,两种简单启动方式可选:
95 | 1. 在py 3.6~3.8下启动服务 96 | - 安装`python3.6`环境; 97 | - cmd切换运行环境`cd ./tornado`; 98 | - 加载依赖包 `pip install -r requirements.txt`; 99 | - 启动服务`python main.py` 100 | 2. 使用Docker镜像,具体方式参考该项目说明。 101 | 102 | 安卓打包可能因为国内无法加载gradle的问题,就算配了镜像也很慢,建议手动下载`grdle-6.4.1-all.zip`版本再构建,下载安装可见[此文章](https://www.cnblogs.com/yehuabin/p/10344713.html) 103 | 104 | #### 入门推荐: 105 | [Dart语法](https://www.dartcn.com/guides/get-started) - 语法中文教程
106 | [Flutter中文网](https://flutterchina.club/get-started/install/) - 简单易懂的入门教程
107 | [Flutter实战](https://book.flutterchina.club/) - 较为全面的进阶教程
108 | [Dart SDK(EN)](https://api.dartlang.org/stable/2.4.0/index.html) - flutter中可用的SDK
109 | [Flutter官网(EN)](https://flutter.dev/docs) - 可查阅全部的API与SDK相关
110 | [Bloc(EN)](https://felangel.github.io/bloc/#/gettingstarted) - 全局状态管理 111 | 112 | #### dy_flutter为个人开源项目,仅用作学习实践 113 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.dy_flutter" 42 | minSdkVersion 18 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | multiDexEnabled true 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | implementation 'com.android.support:multidex:1.0.3' 65 | } 66 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 20 | 27 | 31 | 35 | 40 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/dy_flutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.dy_flutter 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /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/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/android/app/src/main/res/mipmap-hdpi/dy.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/android/app/src/main/res/mipmap-mdpi/dy.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/android/app/src/main/res/mipmap-xhdpi/dy.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/android/app/src/main/res/mipmap-xxhdpi/dy.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/dy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/android/app/src/main/res/mipmap-xxxhdpi/dy.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | // google() 5 | // jcenter() 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | // maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 8 | maven {url 'http://download.flutter.io'} 9 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 10 | maven { url 'https://maven.aliyun.com/repository/public' } 11 | } 12 | 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:3.5.0' 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | // google() 22 | // jcenter() 23 | maven { url 'https://maven.aliyun.com/repository/google' } 24 | // maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 25 | maven {url 'http://download.flutter.io'} 26 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 27 | maven { url 'https://maven.aliyun.com/repository/public' } 28 | } 29 | } 30 | 31 | rootProject.buildDir = '../build' 32 | subprojects { 33 | project.buildDir = "${rootProject.buildDir}/${project.name}" 34 | } 35 | subprojects { 36 | project.evaluationDependsOn(':app') 37 | } 38 | 39 | task clean(type: Delete) { 40 | delete rootProject.buildDir 41 | } 42 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /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-6.4.1-all.zip -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /dy_flutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/back.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/back.webp -------------------------------------------------------------------------------- /images/bar/boy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/boy.webp -------------------------------------------------------------------------------- /images/bar/chat-add.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/chat-add.jpg -------------------------------------------------------------------------------- /images/bar/chat-share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/chat-share.jpg -------------------------------------------------------------------------------- /images/bar/chat-star-act.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/chat-star-act.webp -------------------------------------------------------------------------------- /images/bar/chat-star.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/chat-star.jpg -------------------------------------------------------------------------------- /images/bar/day-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/day-title.png -------------------------------------------------------------------------------- /images/bar/girl.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/girl.webp -------------------------------------------------------------------------------- /images/bar/hot-chat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/hot-chat.jpg -------------------------------------------------------------------------------- /images/bar/hot-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/hot-title.png -------------------------------------------------------------------------------- /images/bar/pull-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/pull-icon.webp -------------------------------------------------------------------------------- /images/bar/tab-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/tab-0.png -------------------------------------------------------------------------------- /images/bar/tab-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/tab-1.png -------------------------------------------------------------------------------- /images/bar/tab-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/tab-2.png -------------------------------------------------------------------------------- /images/bar/usefulSelect.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/bar/usefulSelect.webp -------------------------------------------------------------------------------- /images/broadcast.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/broadcast.webp -------------------------------------------------------------------------------- /images/cate/tab-0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-0.webp -------------------------------------------------------------------------------- /images/cate/tab-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-1.webp -------------------------------------------------------------------------------- /images/cate/tab-10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-10.webp -------------------------------------------------------------------------------- /images/cate/tab-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-2.webp -------------------------------------------------------------------------------- /images/cate/tab-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-3.webp -------------------------------------------------------------------------------- /images/cate/tab-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-4.webp -------------------------------------------------------------------------------- /images/cate/tab-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-5.webp -------------------------------------------------------------------------------- /images/cate/tab-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-6.webp -------------------------------------------------------------------------------- /images/cate/tab-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-7.webp -------------------------------------------------------------------------------- /images/cate/tab-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-8.webp -------------------------------------------------------------------------------- /images/cate/tab-9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab-9.webp -------------------------------------------------------------------------------- /images/cate/tab.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cate/tab.webp -------------------------------------------------------------------------------- /images/cfk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cfk.webp -------------------------------------------------------------------------------- /images/change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/change.png -------------------------------------------------------------------------------- /images/cjf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cjf.webp -------------------------------------------------------------------------------- /images/cqe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/cqe.webp -------------------------------------------------------------------------------- /images/default-avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/default-avatar.webp -------------------------------------------------------------------------------- /images/dg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/dg.webp -------------------------------------------------------------------------------- /images/dg0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/dg0.webp -------------------------------------------------------------------------------- /images/float-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/float-icon.webp -------------------------------------------------------------------------------- /images/fun_home_pull_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/fun_home_pull_down.png -------------------------------------------------------------------------------- /images/gift-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/gift-1.png -------------------------------------------------------------------------------- /images/gift-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/gift-banner.png -------------------------------------------------------------------------------- /images/gift-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/gift-x.png -------------------------------------------------------------------------------- /images/gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/gift.png -------------------------------------------------------------------------------- /images/head/camera.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/camera.webp -------------------------------------------------------------------------------- /images/head/chat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/chat.webp -------------------------------------------------------------------------------- /images/head/dylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/dylogo.png -------------------------------------------------------------------------------- /images/head/game.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/game.webp -------------------------------------------------------------------------------- /images/head/history.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/history.webp -------------------------------------------------------------------------------- /images/head/search.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/head/search.webp -------------------------------------------------------------------------------- /images/hot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/hot.png -------------------------------------------------------------------------------- /images/init_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/init_icon.png -------------------------------------------------------------------------------- /images/init_logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/init_logo.webp -------------------------------------------------------------------------------- /images/login/close.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/close.webp -------------------------------------------------------------------------------- /images/login/lock.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/lock.webp -------------------------------------------------------------------------------- /images/login/member.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/member.webp -------------------------------------------------------------------------------- /images/login/qq.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/qq.webp -------------------------------------------------------------------------------- /images/login/safe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/safe.webp -------------------------------------------------------------------------------- /images/login/syn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/syn.webp -------------------------------------------------------------------------------- /images/login/weibo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/weibo.webp -------------------------------------------------------------------------------- /images/login/wx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/login/wx.webp -------------------------------------------------------------------------------- /images/lv/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/lv/3.png -------------------------------------------------------------------------------- /images/lv/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/lv/30.png -------------------------------------------------------------------------------- /images/lv/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/lv/50.png -------------------------------------------------------------------------------- /images/lv/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/lv/80.png -------------------------------------------------------------------------------- /images/member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/member.png -------------------------------------------------------------------------------- /images/nav/nav-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-11.jpg -------------------------------------------------------------------------------- /images/nav/nav-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-12.jpg -------------------------------------------------------------------------------- /images/nav/nav-21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-21.jpg -------------------------------------------------------------------------------- /images/nav/nav-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-22.jpg -------------------------------------------------------------------------------- /images/nav/nav-31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-31.jpg -------------------------------------------------------------------------------- /images/nav/nav-32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-32.jpg -------------------------------------------------------------------------------- /images/nav/nav-41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-41.jpg -------------------------------------------------------------------------------- /images/nav/nav-42.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-42.jpg -------------------------------------------------------------------------------- /images/nav/nav-51.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-51.jpg -------------------------------------------------------------------------------- /images/nav/nav-52.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/nav/nav-52.jpg -------------------------------------------------------------------------------- /images/num/0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/0.webp -------------------------------------------------------------------------------- /images/num/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/1.webp -------------------------------------------------------------------------------- /images/num/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/2.webp -------------------------------------------------------------------------------- /images/num/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/3.webp -------------------------------------------------------------------------------- /images/num/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/4.webp -------------------------------------------------------------------------------- /images/num/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/5.webp -------------------------------------------------------------------------------- /images/num/6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/6.webp -------------------------------------------------------------------------------- /images/num/7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/7.webp -------------------------------------------------------------------------------- /images/num/8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/8.webp -------------------------------------------------------------------------------- /images/num/9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/num/9.webp -------------------------------------------------------------------------------- /images/pic-default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/pic-default.jpg -------------------------------------------------------------------------------- /images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/play.png -------------------------------------------------------------------------------- /images/shayuniang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/shayuniang.png -------------------------------------------------------------------------------- /images/show-area.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/images/show-area.webp -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/liuzhanwei/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/liuzhanwei/yukilzw/dy_flutter" 5 | export "FLUTTER_TARGET=/Users/liuzhanwei/yukilzw/dy_flutter/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "FLUTTER_BUILD_NAME=1.2.0" 9 | export "FLUTTER_BUILD_NUMBER=1.2.0" 10 | export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue" 11 | export "DART_OBFUSCATION=false" 12 | export "TRACK_WIDGET_CREATION=true" 13 | export "TREE_SHAKE_ICONS=false" 14 | export "PACKAGE_CONFIG=/Users/liuzhanwei/yukilzw/dy_flutter/.dart_tool/package_config.json" 15 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_local_notifications (0.0.1): 4 | - Flutter 5 | - flutter_webview_plugin (0.0.1): 6 | - Flutter 7 | - fluttertoast (0.0.2): 8 | - Flutter 9 | - FMDB (2.7.5): 10 | - FMDB/standard (= 2.7.5) 11 | - FMDB/standard (2.7.5) 12 | - image_picker (0.0.1): 13 | - Flutter 14 | - path_provider (0.0.1): 15 | - Flutter 16 | - shared_preferences (0.0.1): 17 | - Flutter 18 | - sqflite (0.0.2): 19 | - Flutter 20 | - FMDB (>= 2.7.5) 21 | - video_player (0.0.1): 22 | - Flutter 23 | 24 | DEPENDENCIES: 25 | - Flutter (from `Flutter`) 26 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) 27 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 28 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 29 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 30 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 31 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 32 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 33 | - video_player (from `.symlinks/plugins/video_player/ios`) 34 | 35 | SPEC REPOS: 36 | trunk: 37 | - FMDB 38 | 39 | EXTERNAL SOURCES: 40 | Flutter: 41 | :path: Flutter 42 | flutter_local_notifications: 43 | :path: ".symlinks/plugins/flutter_local_notifications/ios" 44 | flutter_webview_plugin: 45 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 46 | fluttertoast: 47 | :path: ".symlinks/plugins/fluttertoast/ios" 48 | image_picker: 49 | :path: ".symlinks/plugins/image_picker/ios" 50 | path_provider: 51 | :path: ".symlinks/plugins/path_provider/ios" 52 | shared_preferences: 53 | :path: ".symlinks/plugins/shared_preferences/ios" 54 | sqflite: 55 | :path: ".symlinks/plugins/sqflite/ios" 56 | video_player: 57 | :path: ".symlinks/plugins/video_player/ios" 58 | 59 | SPEC CHECKSUMS: 60 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 61 | flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 62 | flutter_webview_plugin: ed9e8a6a96baf0c867e90e1bce2673913eeac694 63 | fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b 64 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 65 | image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 66 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 67 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 68 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 69 | video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e 70 | 71 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 72 | 73 | COCOAPODS: 1.9.3 74 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | if #available(iOS 10.0, *) { 12 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 13 | } 14 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "images" : [ 4 | { 5 | "idiom" : "iphone", 6 | "size" : "20x20", 7 | "filename" : "icon20x20@2x.png", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "idiom" : "iphone", 12 | "size" : "20x20", 13 | "filename" : "icon20x20@3x.png", 14 | "scale" : "3x" 15 | }, 16 | { 17 | "size" : "29x29", 18 | "idiom" : "iphone", 19 | "filename" : "icon29x29.png", 20 | "scale" : "1x" 21 | }, 22 | { 23 | "size" : "29x29", 24 | "idiom" : "iphone", 25 | "filename" : "icon29x29@2x.png", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "size" : "29x29", 30 | "idiom" : "iphone", 31 | "filename" : "icon29x29@3x.png", 32 | "scale" : "3x" 33 | }, 34 | { 35 | "size" : "40x40", 36 | "idiom" : "iphone", 37 | "filename" : "icon40x40@2x.png", 38 | "scale" : "2x" 39 | }, 40 | { 41 | "size" : "40x40", 42 | "idiom" : "iphone", 43 | "filename" : "icon40x40@3x.png", 44 | "scale" : "3x" 45 | }, 46 | { 47 | "size" : "60x60", 48 | "idiom" : "iphone", 49 | "filename" : "icon60x60@2x.png", 50 | "scale" : "2x" 51 | }, 52 | { 53 | "size" : "60x60", 54 | "idiom" : "iphone", 55 | "filename" : "icon60x60@3x.png", 56 | "scale" : "3x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "20x20", 61 | "filename" : "icon20x20.png", 62 | "scale" : "1x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "20x20", 67 | "filename" : "icon20x20@2x.png", 68 | "scale" : "2x" 69 | }, 70 | { 71 | "size" : "29x29", 72 | "idiom" : "ipad", 73 | "filename" : "icon29x29.png", 74 | "scale" : "1x" 75 | }, 76 | { 77 | "size" : "29x29", 78 | "idiom" : "ipad", 79 | "filename" : "icon29x29@2x.png", 80 | "scale" : "2x" 81 | }, 82 | { 83 | "size" : "40x40", 84 | "idiom" : "ipad", 85 | "filename" : "icon40x40.png", 86 | "scale" : "1x" 87 | }, 88 | { 89 | "size" : "40x40", 90 | "idiom" : "ipad", 91 | "filename" : "icon40x40@2x.png", 92 | "scale" : "2x" 93 | }, 94 | { 95 | "size" : "76x76", 96 | "idiom" : "ipad", 97 | "filename" : "icon76x76.png", 98 | "scale" : "1x" 99 | }, 100 | { 101 | "size" : "76x76", 102 | "idiom" : "ipad", 103 | "filename" : "icon76x76@2x.png", 104 | "scale" : "2x" 105 | }, 106 | { 107 | "size" : "83.5x83.5", 108 | "idiom" : "ipad", 109 | "filename" : "icon83.5x83.5@2x.png", 110 | "scale" : "2x" 111 | } 112 | ], 113 | "info" : { 114 | "version" : 1, 115 | "author" : "xcode" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon76x76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon83.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/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yukilzw/dy_flutter/53e1a3398c64f362110cc086044803e498394d6d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | 斗鱼Flutter 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/base.dart: -------------------------------------------------------------------------------- 1 | library base; 2 | /* 3 | * @discripe: 全局公共类管理 4 | */ 5 | import 'dart:ui'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | 10 | export 'bloc.dart'; 11 | export 'rx.dart'; 12 | export 'httpUrl.dart'; 13 | export 'io.dart'; 14 | 15 | // 所有Widget继承的抽象类 16 | abstract class DYBase { 17 | static final baseSchema = 'http'; 18 | static final baseHost = '192.168.97.142'; 19 | static final basePort = '1236'; 20 | static final baseUrl = '${DYBase.baseSchema}://${DYBase.baseHost}:${DYBase.basePort}'; 21 | // 默认斗鱼主题色 22 | static final defaultColor = Color(0xffff5d23); 23 | // 初始化设计稿尺寸 24 | static final double dessignWidth = 375.0; 25 | static final double dessignHeight = 1335.0; 26 | 27 | static final double statusBarHeight = MediaQueryData.fromWindow(window).padding.top; 28 | 29 | // flutter_screenutil px转dp 30 | num dp(double dessignValue) => ScreenUtil.getInstance().setWidth(dessignValue); 31 | } -------------------------------------------------------------------------------- /lib/bloc.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: bloc全局状态管理 3 | */ 4 | import 'package:bloc/bloc.dart'; 5 | 6 | abstract class BlocObj { 7 | static final counter = CounterBloc(); 8 | static final index = IndexBloc(); 9 | } 10 | 11 | // 直播列表页码 12 | enum CounterEvent { increment, reset } 13 | class CounterBloc extends Bloc { 14 | CounterBloc() : super(1); 15 | 16 | @override 17 | Stream mapEventToState(CounterEvent event) async* { 18 | switch (event) { 19 | case CounterEvent.reset: 20 | yield 1; 21 | break; 22 | case CounterEvent.increment: 23 | yield state + 1; 24 | break; 25 | } 26 | } 27 | } 28 | 29 | // 启动页预加载首页信息 30 | abstract class IndexEvent {} 31 | 32 | class UpdateTab implements IndexEvent { 33 | final List tab; 34 | UpdateTab(this.tab); 35 | } 36 | 37 | class UpdateLiveData implements IndexEvent { 38 | final List liveData; 39 | UpdateLiveData(this.liveData); 40 | } 41 | 42 | class UpdateSwiper implements IndexEvent { 43 | final List swiper; 44 | UpdateSwiper(this.swiper); 45 | } 46 | 47 | class IndexBloc extends Bloc { 48 | IndexBloc() : super({ 49 | 'nav': [], 50 | 'liveData': [], 51 | 'swiper': [] 52 | }); 53 | 54 | @override 55 | Stream mapEventToState(IndexEvent event) async* { 56 | if (event is UpdateTab) { 57 | yield { ...state, 'nav': event.tab }; 58 | } else if (event is UpdateLiveData) { 59 | yield { ...state, 'liveData': event.liveData }; 60 | } else if (event is UpdateSwiper) { 61 | yield { ...state, 'swiper': event.swiper }; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /lib/develop/develop.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: webView & 拍照 3 | */ 4 | import 'dart:async'; 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | import 'package:image_picker/image_picker.dart'; 10 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 11 | 12 | import '../base.dart'; 13 | import '../service.dart'; 14 | 15 | final flutterWebviewPlugin = FlutterWebviewPlugin(); 16 | 17 | class DevelopTest extends StatefulWidget { 18 | @override 19 | _DevelopTest createState() => _DevelopTest(); 20 | } 21 | 22 | class _DevelopTest extends State with DYBase { 23 | final url = 'https://apiv2.douyucdn.cn/h5/ecydt/subjectRanklist'; 24 | File _image; // 拍照(从照片选择)后的文件 25 | bool _getPhotoSource = false; // 是否拍照 26 | bool _openWebViewType = false; // 是否全屏打开webView 27 | 28 | // 点击拍照 29 | Future _getImage() async { 30 | final picker = ImagePicker(); 31 | var image = await picker.getImage( 32 | source: _getPhotoSource ? ImageSource.camera : ImageSource.gallery, 33 | maxHeight: dp(200), 34 | maxWidth: dp(350), 35 | ); 36 | if (mounted) 37 | setState(() { 38 | _image = File(image.path); 39 | }); 40 | } 41 | 42 | // 弹出webView 43 | void _showWebView() { 44 | if (_openWebViewType) { 45 | Navigator.pushNamed(context, '/webView', 46 | arguments: { 47 | 'url': url, 48 | 'title': 'dy_flutter 源码' 49 | } 50 | ); 51 | return; 52 | } 53 | flutterWebviewPlugin.launch(url, 54 | rect: Rect.fromLTWH( 55 | 0.0, 56 | MediaQuery.of(context).size.height * .3, 57 | MediaQuery.of(context).size.width, 58 | MediaQuery.of(context).size.height * .7, 59 | ), 60 | ); 61 | Navigator.push(context, PageRouteBuilder( 62 | opaque: false, 63 | pageBuilder: (context, _, __) { 64 | return GestureDetector( 65 | onTap: () { 66 | flutterWebviewPlugin?.close(); 67 | Navigator.pop(context); 68 | }, 69 | child: WillPopScope( 70 | onWillPop: _onWillPop, 71 | child: Container( 72 | color: Color.fromARGB(100, 0, 0, 0), 73 | ), 74 | ) 75 | ); 76 | }, 77 | transitionsBuilder: (context, Animation animation, _, Widget child) { 78 | return FadeTransition( 79 | opacity: animation, 80 | child: child, 81 | ); 82 | } 83 | )); 84 | } 85 | 86 | Future _onWillPop() async { 87 | flutterWebviewPlugin?.close(); 88 | return true; 89 | } 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 94 | return Padding( 95 | padding: EdgeInsets.all(dp(10)), 96 | child: Column( 97 | children: [ 98 | Row( 99 | mainAxisAlignment: MainAxisAlignment.center, 100 | children: [ 101 | RaisedButton( 102 | textColor: Colors.white, 103 | color: DYBase.defaultColor, 104 | shape: RoundedRectangleBorder( 105 | borderRadius: BorderRadius.circular(100), 106 | ), 107 | child: Text(_getPhotoSource ? '拍照' : '从相册中选'), 108 | onPressed: _getImage, 109 | ), 110 | Padding(padding: EdgeInsets.only(left: dp(15)),), 111 | Text('是否拍照:'), 112 | Switch( 113 | value: _getPhotoSource, 114 | activeColor: DYBase.defaultColor, 115 | onChanged: (value) => { 116 | if (mounted) 117 | setState(() { 118 | _getPhotoSource = value; 119 | }) 120 | }, 121 | ), 122 | ], 123 | ), 124 | _image == null ? SizedBox() : Image.file(_image), 125 | RaisedButton( 126 | textColor: Colors.white, 127 | color: DYBase.defaultColor, 128 | child: Text('正在加载弹框'), 129 | onPressed: () => DYdialog.showLoading(context), 130 | ), 131 | RaisedButton( 132 | textColor: Colors.white, 133 | color: DYBase.defaultColor, 134 | child: Text('登陆弹窗'), 135 | onPressed: () => DYdialog.showLogin(context), 136 | ), 137 | Row( 138 | mainAxisAlignment: MainAxisAlignment.center, 139 | children: [ 140 | RaisedButton( 141 | textColor: Colors.white, 142 | color: DYBase.defaultColor, 143 | child: Text('${_openWebViewType ? '全屏' : '半屏'}webView'), 144 | onPressed: _showWebView, 145 | ), 146 | Padding(padding: EdgeInsets.only(left: dp(15)),), 147 | Text('是否全屏:'), 148 | Switch( 149 | value: _openWebViewType, 150 | activeColor: DYBase.defaultColor, 151 | onChanged: (value) => { 152 | if (mounted) 153 | setState(() { 154 | _openWebViewType = value; 155 | }) 156 | }, 157 | ), 158 | ], 159 | ) 160 | ], 161 | ), 162 | ); 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /lib/develop/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 开发中的测试功能集中页面 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | 8 | import 'develop.dart'; 9 | 10 | class DevelopPage extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | body: CustomScrollView( 15 | // physics: BouncingScrollPhysics(), 16 | slivers: [ 17 | SliverPersistentHeader( 18 | pinned: true, 19 | delegate: SliverCustomHeaderDelegate( 20 | title: '(现为新加功能测试页面)', 21 | collapsedHeight: 40, 22 | expandedHeight: 300, 23 | paddingTop: MediaQuery.of(context).padding.top, 24 | coverImgUrl: 'http://r.photo.store.qq.com/psb?/V14dALyK4PrHuj/iEyZ0zEnZQmbqfs.BUMe9Visc92Tqohh0OKKP0JOtVU!/r/dMMAAAAAAAAA' 25 | ), 26 | ), 27 | SliverToBoxAdapter( 28 | child: FilmContent(), 29 | ) 30 | ], 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | /// [SliverPersistentHeaderDelegate]在原有Widget的基础上封装了滚动坐标,在build方法中的shrinkOffset参数 37 | /// 这里利用继承原生组件实现了随滚动改变header透明度的效果 38 | class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate { 39 | final double collapsedHeight; 40 | final double expandedHeight; 41 | final double paddingTop; 42 | final String coverImgUrl; 43 | final String title; 44 | String statusBarMode = 'dark'; 45 | 46 | SliverCustomHeaderDelegate({ 47 | this.collapsedHeight, 48 | this.expandedHeight, 49 | this.paddingTop, 50 | this.coverImgUrl, 51 | this.title, 52 | }); 53 | 54 | @override 55 | double get minExtent => this.collapsedHeight + this.paddingTop; 56 | 57 | @override 58 | double get maxExtent => this.expandedHeight; 59 | 60 | @override 61 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { 62 | return true; 63 | } 64 | 65 | void updateStatusBarBrightness(shrinkOffset) { 66 | if(shrinkOffset <= 50 && this.statusBarMode == 'dark') { 67 | this.statusBarMode = 'light'; 68 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 69 | statusBarBrightness: Brightness.light, 70 | statusBarIconBrightness: Brightness.light, 71 | )); 72 | } else if(shrinkOffset > 50 && this.statusBarMode == 'light') { 73 | this.statusBarMode = 'dark'; 74 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 75 | statusBarBrightness: Brightness.dark, 76 | statusBarIconBrightness: Brightness.dark, 77 | )); 78 | } 79 | } 80 | 81 | Color makeStickyHeaderBgColor(shrinkOffset) { 82 | final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt(); 83 | return Color.fromARGB(alpha, 255, 255, 255); 84 | } 85 | 86 | Color makeStickyHeaderTextColor(shrinkOffset, isIcon) { 87 | if(shrinkOffset <= 50) { 88 | return isIcon ? Colors.white : Colors.transparent; 89 | } else { 90 | final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255).clamp(0, 255).toInt(); 91 | return Color.fromARGB(alpha, 0, 0, 0); 92 | } 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { 97 | this.updateStatusBarBrightness(shrinkOffset); 98 | return Container( 99 | height: this.maxExtent, 100 | width: MediaQuery.of(context).size.width, 101 | child: Stack( 102 | fit: StackFit.expand, 103 | children: [ 104 | Container(child: Image.network(this.coverImgUrl, fit: BoxFit.cover)), 105 | Positioned( 106 | left: 0, 107 | top: this.maxExtent / 2, 108 | right: 0, 109 | bottom: 0, 110 | child: Container( 111 | decoration: BoxDecoration( 112 | gradient: LinearGradient( 113 | begin: Alignment.topCenter, 114 | end: Alignment.bottomCenter, 115 | colors: [ 116 | Color(0x00000000), 117 | Color(0x90000000), 118 | ], 119 | ), 120 | ), 121 | ), 122 | ), 123 | Positioned( 124 | left: 0, 125 | right: 0, 126 | top: 0, 127 | child: Container( 128 | color: this.makeStickyHeaderBgColor(shrinkOffset), 129 | child: SafeArea( 130 | bottom: false, 131 | child: Container( 132 | height: this.collapsedHeight, 133 | child: Row( 134 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 135 | children: [ 136 | IconButton( 137 | icon: Icon( 138 | Icons.arrow_back_ios, 139 | color: this.makeStickyHeaderTextColor(shrinkOffset, true), 140 | ), 141 | onPressed: () => Fluttertoast.showToast(msg: '没有上一页了'), 142 | ), 143 | Text( 144 | this.title, 145 | style: TextStyle( 146 | fontSize: 18, 147 | fontWeight: FontWeight.w500, 148 | color: this.makeStickyHeaderTextColor(shrinkOffset, false), 149 | ), 150 | ), 151 | IconButton( 152 | icon: Icon( 153 | Icons.share, 154 | color: this.makeStickyHeaderTextColor(shrinkOffset, true), 155 | ), 156 | onPressed: () {}, 157 | ), 158 | ], 159 | ), 160 | ), 161 | ), 162 | ), 163 | ), 164 | ], 165 | ), 166 | ); 167 | } 168 | } 169 | 170 | class FilmContent extends StatelessWidget { 171 | @override 172 | Widget build(BuildContext context) { 173 | return Padding( 174 | padding: EdgeInsets.all(16), 175 | child: Column( 176 | crossAxisAlignment: CrossAxisAlignment.start, 177 | children: [ 178 | DevelopTest(), 179 | SizedBox( 180 | height: 700, 181 | ) 182 | ], 183 | ), 184 | ); 185 | } 186 | } -------------------------------------------------------------------------------- /lib/dy_dialog/loading.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: laoding弹窗 3 | */ 4 | import 'package:flutter/material.dart'; 5 | 6 | import '../base.dart'; 7 | 8 | class LoadingDialog extends Dialog with DYBase { 9 | final String text; 10 | 11 | LoadingDialog({Key key, @required this.text}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Material( 16 | type: MaterialType.transparency, 17 | child: Center( 18 | child: SizedBox( 19 | width: dp(120), 20 | height: dp(120), 21 | child: Container( 22 | decoration: ShapeDecoration( 23 | color: Color(0xffffffff), 24 | shape: RoundedRectangleBorder( 25 | borderRadius: BorderRadius.all( 26 | Radius.circular(8.0), 27 | ), 28 | ), 29 | ), 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | crossAxisAlignment: CrossAxisAlignment.center, 33 | children: [ 34 | CircularProgressIndicator(), 35 | Padding( 36 | padding: EdgeInsets.only( 37 | top: dp(20), 38 | ), 39 | child: Text( 40 | text, 41 | style: TextStyle(fontSize: 12.0), 42 | ), 43 | ), 44 | ], 45 | ), 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/dy_index/commend/broadcast.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 广播游戏订阅推荐 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:dy_flutter/service.dart'; 6 | import 'package:flutter_swiper/flutter_swiper.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:fluttertoast/fluttertoast.dart'; 9 | 10 | import '../../base.dart'; 11 | 12 | class BroadcastSwiper extends StatefulWidget { 13 | @override 14 | _BroadcastSwiper createState() => _BroadcastSwiper(); 15 | } 16 | 17 | class _BroadcastSwiper extends State with DYBase { 18 | List _broadcastList; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _getBroadcastList(); 24 | } 25 | 26 | Future _getBroadcastList() async { 27 | var res = await httpClient.get( 28 | API.broadcast, 29 | ); 30 | var list = res.data['data']; 31 | setState(() { 32 | _broadcastList = list; 33 | }); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 39 | 40 | return Container( 41 | padding: EdgeInsets.only(left: dp(15), right: dp(15)), 42 | decoration: BoxDecoration( 43 | border: Border( 44 | top: BorderSide( 45 | color: Color(0xfffbfbfb), 46 | width: 1, 47 | ) 48 | ) 49 | ), 50 | height: dp(40), 51 | child: _waitBroadcastSwiperData(), 52 | ); 53 | } 54 | 55 | String _formatTime(int timeSec) { 56 | var date = DateTime.fromMillisecondsSinceEpoch(timeSec * 1000); 57 | return '${date.month.toString().padLeft(2,'0')}/${date.day.toString().padLeft(2,'0')} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')} '; 58 | } 59 | 60 | void _changeOrderStatus(Map item) { 61 | var current = item['order']; 62 | setState(() { 63 | item['order'] = !current; 64 | }); 65 | if (current) { 66 | Fluttertoast.showToast(msg: '已取消订阅'); 67 | } else { 68 | Fluttertoast.showToast(msg: '已订阅'); 69 | } 70 | } 71 | 72 | Widget _waitBroadcastSwiperData() { 73 | if (_broadcastList == null) { 74 | return SizedBox(); 75 | } else if (_broadcastList.length > 0) { 76 | return Swiper( 77 | itemBuilder: (BuildContext context, int index) => Container( 78 | child: Row( 79 | children: [ 80 | Image.asset(index % 2 == 0 ? 'images/cjf.webp' : 'images/broadcast.webp', 81 | height: dp(20), 82 | ), 83 | Padding(padding: EdgeInsets.only(right: dp(5)),), 84 | Expanded( 85 | flex: 1, 86 | child: Text( 87 | _broadcastList[index]['title'], 88 | style: TextStyle( 89 | color: Color(0xff333333), 90 | fontSize: dp(12.5), 91 | ), 92 | maxLines: 1, 93 | overflow: TextOverflow.ellipsis, 94 | ), 95 | ), 96 | Padding(padding: EdgeInsets.only(right: dp(5)),), 97 | RichText( 98 | text: TextSpan( 99 | style: TextStyle( 100 | color: Color(0xff999999), 101 | fontSize: dp(12.5), 102 | ), 103 | children: [ 104 | TextSpan( 105 | text: _formatTime(_broadcastList[index]['time']), 106 | ), 107 | TextSpan( 108 | text: DYservice.formatNum(_broadcastList[index]['num']), 109 | style: TextStyle( 110 | color: DYBase.defaultColor 111 | ), 112 | ), 113 | TextSpan( 114 | text: '人预定', 115 | ), 116 | ], 117 | ), 118 | ), 119 | Padding(padding: EdgeInsets.only(right: dp(5)),), 120 | GestureDetector( 121 | onTap: () => _changeOrderStatus( _broadcastList[index]), 122 | child: Container( 123 | padding: EdgeInsets.only( 124 | left: dp(8), right: dp(8), top: dp(3), bottom: dp(3), 125 | ), 126 | child: Text( 127 | _broadcastList[index]['order'] ? '已预订' : '预订', 128 | style: TextStyle( 129 | color: _broadcastList[index]['order'] ? Colors.white : DYBase.defaultColor, 130 | fontSize: 11, 131 | ), 132 | ), 133 | decoration: BoxDecoration( 134 | color: _broadcastList[index]['order'] ? Color(0xffd9d9d9) : Colors.transparent, 135 | border: _broadcastList[index]['order'] ? null : Border.all(color: Color(0xffffd2a6), width: dp(.7)), 136 | borderRadius: BorderRadius.all( 137 | Radius.circular(dp(4)), 138 | ), 139 | ), 140 | ), 141 | ), 142 | ], 143 | ), 144 | ), 145 | itemCount: _broadcastList.length, 146 | scrollDirection: Axis.vertical, 147 | autoplay: true, 148 | duration: 1500, 149 | autoplayDelay: 4500, 150 | curve: Curves.linear, 151 | ); 152 | } 153 | 154 | return null; 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /lib/dy_index/commend/cate.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 分区 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | // import 'package:cached_network_image/cached_network_image.dart'; 8 | 9 | import '../../base.dart'; 10 | import '../../service.dart'; 11 | 12 | const _tab = ['好声音', '舞蹈', '电子竞技', '颜值', '主机游戏', '打榜', '心动FM', '你画我猜', '竞速', '陪玩', '做任务']; 13 | 14 | class CateList extends StatefulWidget { 15 | @override 16 | _CateList createState() => _CateList(); 17 | } 18 | 19 | class _CateList extends State with DYBase { 20 | double scrollRatio = 0; 21 | 22 | Widget _renderWidget(context) { 23 | var tabRender = _tab.asMap().keys.map((index) => InkWell( 24 | onTap: () => DYdialog.alert( 25 | context, 26 | title: _tab[index], 27 | text: '年轻人不讲武德,偷袭!🪓\n我还没上线呢...', 28 | yes: '😭 好的,下次一定' 29 | ), 30 | child: Container( 31 | width: dp(375 / 5), 32 | color: Colors.transparent, 33 | child: Column( 34 | mainAxisAlignment: MainAxisAlignment.center, 35 | children: [ 36 | Image.asset( 37 | 'images/cate/tab-${index.toString()}.webp', 38 | height: dp(40), 39 | ), 40 | Padding(padding: EdgeInsets.only(top: dp(7)),), 41 | Text( 42 | _tab[index], 43 | style: TextStyle( 44 | color: Color(0xff888888), 45 | fontSize: dp(13.5), 46 | ), 47 | ), 48 | ], 49 | ), 50 | ), 51 | )).toList(); 52 | 53 | double indicatorBoxWidth = dp(34); 54 | double indicatorHeight = dp(3.5); 55 | double indicatorWidth = dp(15); 56 | double scrollValue = scrollRatio * (indicatorBoxWidth - indicatorWidth); 57 | 58 | return Column( 59 | children: [ 60 | Container( 61 | height: dp(80), 62 | child: NotificationListener( 63 | onNotification: (ScrollNotification note) { 64 | setState(() { 65 | scrollRatio = note.metrics.pixels / note.metrics.maxScrollExtent; 66 | }); 67 | return true; 68 | }, 69 | child: ListView( 70 | scrollDirection: Axis.horizontal, 71 | physics: BouncingScrollPhysics(), 72 | padding: EdgeInsets.all(0), 73 | children: tabRender, 74 | ), 75 | ), 76 | ), 77 | SizedBox( 78 | height: dp(18), 79 | child: Center( 80 | child: ClipRRect( 81 | borderRadius: BorderRadius.all( 82 | Radius.circular(indicatorHeight / 2), 83 | ), 84 | child: Container( 85 | width: indicatorBoxWidth, 86 | height: indicatorHeight, 87 | color: Color(0xffd0d0d0), 88 | child: Stack( 89 | alignment: Alignment.centerLeft, 90 | children: [ 91 | Positioned( 92 | left: scrollValue, 93 | child: Container( 94 | width: indicatorWidth, 95 | height: indicatorHeight, 96 | color: Color(0xffff5d24), 97 | ), 98 | ) 99 | ] 100 | ), 101 | ), 102 | ), 103 | ), 104 | ), 105 | Padding(padding: EdgeInsets.only(top: dp(6)),) 106 | ], 107 | ); 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 113 | 114 | return BlocBuilder( 115 | builder: (ctx, indexState) => _renderWidget(context) 116 | ); 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /lib/dy_index/commend/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 推荐 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 8 | 9 | import '../../bloc.dart'; 10 | import '../../base.dart'; 11 | import '../../service.dart'; 12 | import '../header.dart'; 13 | import 'swiper.dart'; 14 | import 'broadcast.dart'; 15 | import 'list.dart'; 16 | import 'cate.dart'; 17 | 18 | class CommendPage extends StatefulWidget { 19 | final _scrollController; 20 | CommendPage(this._scrollController); 21 | 22 | @override 23 | _CommendPage createState() => _CommendPage(this._scrollController); 24 | } 25 | 26 | class _CommendPage extends State with DYBase, AutomaticKeepAliveClientMixin { 27 | RefreshController _refreshController = RefreshController(initialRefresh: false); 28 | final _scrollController; 29 | 30 | _CommendPage(this._scrollController); 31 | 32 | @override 33 | bool get wantKeepAlive => true; 34 | 35 | @override 36 | void dispose() { 37 | _refreshController?.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | // 下拉刷新 42 | void _onRefresh() async { 43 | await dioManager.deleteByPrimaryKey(DYBase.baseUrl + API.liveData); 44 | 45 | final counterBloc = BlocProvider.of(context); 46 | final indexBloc = BlocProvider.of(context); 47 | 48 | counterBloc.add(CounterEvent.reset); 49 | 50 | var liveList = await DYservice.getLiveData(context, 1); 51 | indexBloc.add(UpdateLiveData(liveList)); 52 | // setState(() => null); 53 | 54 | _refreshController.refreshCompleted(); 55 | } 56 | 57 | // 上拉加载 58 | void _onLoading() async { 59 | final indexBloc = BlocProvider.of(context); 60 | 61 | List liveData = BlocObj.index.state['liveData']; 62 | var liveList = await DYservice.getLiveData(context); 63 | liveData.addAll(liveList); 64 | indexBloc.add(UpdateLiveData(liveData)); 65 | // setState(() => null); 66 | 67 | _refreshController.loadComplete(); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | super.build(context); 73 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 74 | 75 | return BlocBuilder( 76 | builder: (context, indexState) { 77 | List navList = indexState['nav']; 78 | return Scaffold( 79 | body: navList.length == 0 ? null : DefaultTabController( 80 | length: navList.length, 81 | child: NestedScrollView( // 嵌套式滚动视图 82 | controller: _scrollController, 83 | headerSliverBuilder: (context, innerScrolled) => [ 84 | /// 使用[SliverAppBar]组件实现下拉收起头部的效果 85 | SliverAppBar( 86 | backgroundColor: Color(0xffff6634), 87 | brightness: Brightness.dark, 88 | pinned: true, 89 | floating: true, 90 | snap: true, 91 | expandedHeight: dp(55) + 49, 92 | actions: [ 93 | DyHeader( 94 | decoration: BoxDecoration( 95 | color: Colors.transparent, 96 | ), 97 | ), 98 | ], 99 | flexibleSpace: FlexibleSpaceBar( // 下拉渐入背景 100 | background: Container( 101 | decoration: BoxDecoration( 102 | gradient: LinearGradient( 103 | begin: FractionalOffset.centerLeft, 104 | end: FractionalOffset.centerRight, 105 | colors: [ 106 | Color(0xffff8633), 107 | Color(0xffff6634) 108 | ], 109 | ), 110 | ), 111 | ), 112 | ), 113 | bottom: TabBar( 114 | indicator: BoxDecoration(), 115 | isScrollable: true, 116 | //设置tab文字得类型 117 | unselectedLabelStyle: TextStyle( 118 | fontSize: 15, 119 | ), 120 | labelStyle: TextStyle( 121 | fontSize: 18, 122 | ), 123 | //设置tab选中得颜色 124 | labelColor: Colors.white, 125 | //设置tab未选中得颜色 126 | unselectedLabelColor: Colors.white70, 127 | //设置自定义tab的指示器,CustomUnderlineTabIndicator 128 | //若不需要自定义,可直接通过 129 | // indicatorColor: Colors.white, // 设置指示器颜色 130 | indicatorWeight: 3, // 设置指示器厚度 131 | //indicatorPadding 132 | //indicatorSize 设置指示器大小计算方式 133 | ///指示器大小计算方式,TabBarIndicatorSize.label跟文字等宽,TabBarIndicatorSize.tab跟每个tab等宽 134 | // indicatorSize: TabBarIndicatorSize.label, 135 | tabs: navList.map((e) => Tab(text: e)).toList(), 136 | ), 137 | forceElevated: innerScrolled, 138 | ), 139 | ], 140 | body: TabBarView( 141 | children: navList.asMap().map((i, tab) => MapEntry(i, Builder( 142 | builder: (context) => Container( 143 | color: Colors.white, 144 | child: ScrollConfiguration( 145 | behavior: DyBehaviorNull(), 146 | child: RefreshConfiguration( 147 | headerTriggerDistance: dp(80), 148 | maxOverScrollExtent : dp(100), 149 | footerTriggerDistance: dp(50), 150 | maxUnderScrollExtent: 0, 151 | headerBuilder: () => DYrefreshHeader(), 152 | footerBuilder: () => DYrefreshFooter(), 153 | child: SmartRefresher( 154 | enablePullDown: true, 155 | enablePullUp: true, 156 | footer: DYrefreshFooter(bgColor: Color(0xfff1f5f6),), 157 | controller: _refreshController, 158 | onRefresh: _onRefresh, 159 | onLoading: _onLoading, 160 | child: CustomScrollView( 161 | // physics: BouncingScrollPhysics(), 162 | slivers: [ 163 | SliverToBoxAdapter( 164 | child: Container( 165 | child: i == 0 ? Column( 166 | children: [ 167 | SwiperList(), 168 | CateList(), 169 | BroadcastSwiper(), 170 | LiveList(), 171 | ], 172 | ) : null, 173 | ), 174 | ) 175 | ], 176 | ), 177 | ), 178 | ), 179 | ), 180 | ), 181 | ),), 182 | ).values.toList(), 183 | ), 184 | ), 185 | ), 186 | ); 187 | }, 188 | ); 189 | } 190 | 191 | } -------------------------------------------------------------------------------- /lib/dy_index/commend/swiper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 轮播图 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_swiper/flutter_swiper.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | 10 | import '../../bloc.dart'; 11 | import '../../base.dart'; 12 | 13 | class SwiperList extends StatelessWidget with DYBase { 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 18 | 19 | return BlocBuilder( 20 | builder: (ctx, indexState) { 21 | List swiperPic = indexState['swiper']; 22 | return Padding( 23 | padding: EdgeInsets.all(dp(16)), 24 | child: ClipRRect( 25 | borderRadius: BorderRadius.all( 26 | Radius.circular(dp(10)), 27 | ), 28 | child: Container( 29 | height: dp(130), 30 | child: _waitSwiperData(swiperPic), 31 | ), 32 | ), 33 | ); 34 | } 35 | ); 36 | } 37 | 38 | Widget _waitSwiperData(swiperPic) { 39 | if (swiperPic == null) { 40 | return Image.asset( 41 | 'images/shayuniang.png', 42 | ); 43 | } else if (swiperPic.length > 0) { 44 | return Swiper( 45 | itemBuilder: (BuildContext context, int index) => CachedNetworkImage( 46 | imageUrl: swiperPic[index], 47 | placeholder: (context, url) => Image.asset( 48 | 'images/shayuniang.png', 49 | fit: BoxFit.contain, 50 | ), 51 | fit: BoxFit.cover, 52 | ), 53 | itemCount: swiperPic.length, 54 | pagination: SwiperPagination( 55 | builder: DotSwiperPaginationBuilder( 56 | color: Colors.white, 57 | size: dp(6), 58 | activeSize: dp(9), 59 | activeColor: DYBase.defaultColor, 60 | ), 61 | margin: EdgeInsets.only( 62 | right: dp(10), 63 | bottom: dp(5), 64 | ), 65 | alignment: Alignment.bottomRight 66 | ), 67 | scrollDirection: Axis.horizontal, 68 | autoplay: true, 69 | onTap: (index) => print('Swiper pic $index click'), 70 | ); 71 | } 72 | 73 | return null; 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /lib/dy_index/fishbar/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 鱼吧 3 | */ 4 | import 'dart:ui'; 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | 10 | import '../../base.dart'; 11 | import '../../service.dart'; 12 | import '../header.dart'; 13 | import 'myConcern.dart'; 14 | 15 | int _headerAnimationTime = 250; 16 | 17 | // 头部动画组件 18 | class AnimatedLogo extends AnimatedWidget with DYBase { 19 | Tween _opacityTween, _heightTween; 20 | double height, opacity, beginH, beginO, endH, endO; 21 | final direction; 22 | AnimatedLogo({ 23 | Key key, Animation animation, this.direction, 24 | }) : super(key: key, listenable: animation) { 25 | beginH = direction == -1 ? DYBase.statusBarHeight : DYBase.statusBarHeight + dp(55); 26 | endH = direction == -1 ? DYBase.statusBarHeight + dp(55) : DYBase.statusBarHeight; 27 | beginO = direction == -1 ? 0 : 1; 28 | endO = direction == -1 ? 1 : 0; 29 | _heightTween = Tween( 30 | begin: beginH, end: endH, 31 | ); 32 | _opacityTween = Tween( 33 | begin: beginO, end: endO, 34 | ); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | final Animation animation = listenable; 40 | return DyHeader(height: _heightTween.evaluate(animation), opacity: _opacityTween.evaluate(animation),); 41 | } 42 | } 43 | 44 | class LogoApp extends StatefulWidget { 45 | final direction; 46 | LogoApp(this.direction, {Key key}) : super(key: key); 47 | 48 | _LogoAppState createState() => new _LogoAppState(direction); 49 | } 50 | 51 | class _LogoAppState extends State with DYBase, SingleTickerProviderStateMixin { 52 | AnimationController controller; 53 | Animation animation; 54 | final direction; 55 | 56 | _LogoAppState(this.direction); 57 | 58 | initState() { 59 | super.initState(); 60 | controller = AnimationController( 61 | duration: Duration(milliseconds: _headerAnimationTime), 62 | vsync: this 63 | ); 64 | 65 | animation = CurvedAnimation(parent: controller, curve: Curves.easeInOut); 66 | 67 | controller.forward(); 68 | } 69 | 70 | dispose() { 71 | controller.dispose(); 72 | super.dispose(); 73 | } 74 | 75 | Widget build(BuildContext context) { 76 | return AnimatedLogo(animation: animation, direction: direction); 77 | } 78 | } 79 | 80 | class FishBarPage extends StatefulWidget { 81 | @override 82 | _FishBarPage createState() => _FishBarPage(); 83 | } 84 | 85 | class _FishBarPage extends State with DYBase { 86 | int _navActIndex = 0; 87 | List _navList = ['我的', '广场', '找吧']; 88 | bool _duringAnimation = false; 89 | AnimationController _controller; 90 | int _direction; 91 | 92 | @override 93 | void initState() { 94 | super.initState(); 95 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 96 | statusBarIconBrightness: Brightness.light, 97 | )); 98 | } 99 | 100 | @override 101 | void dispose() { 102 | _controller?.dispose(); 103 | super.dispose(); 104 | } 105 | 106 | List _nav() { 107 | return _navList.asMap().map((i, name) { 108 | return MapEntry(i, GestureDetector( 109 | onTap: () => setState(() { 110 | _navActIndex = i; 111 | }), 112 | child: Container( 113 | width: dp(70), 114 | height: dp(55), 115 | color: Colors.transparent, 116 | child: Stack( 117 | children: [ 118 | Center( 119 | child: Text( 120 | name, 121 | style: TextStyle( 122 | fontSize: _navActIndex == i ? 20 : 16, 123 | fontWeight: FontWeight.bold, 124 | color: _navActIndex == i ? DYBase.defaultColor : Color(0xff333333) 125 | ), 126 | ), 127 | ), 128 | _navActIndex == i ? Positioned( 129 | bottom: 0, 130 | left: dp(35.0 - 4), 131 | child: Container( 132 | width: dp(8), 133 | height: dp(4), 134 | decoration: BoxDecoration( 135 | color: DYBase.defaultColor, 136 | borderRadius: BorderRadius.all( 137 | Radius.circular(dp(2)), 138 | ), 139 | ), 140 | ), 141 | ) : SizedBox(), 142 | ], 143 | ), 144 | ), 145 | ),); 146 | }).values.toList(); 147 | } 148 | 149 | Widget _view() { 150 | var color; 151 | switch (_navActIndex) { 152 | case 0: 153 | return NotificationListener( 154 | onNotification: (notification) { 155 | switch (notification.runtimeType){ 156 | case ScrollUpdateNotification: 157 | if (notification?.dragDetails != null) { 158 | _onVerticalDragUpdate(notification.dragDetails); 159 | } 160 | break; 161 | } 162 | return true; 163 | }, 164 | child: MyConcern(headerAnimated: _headerAnimated), 165 | ); 166 | case 1: 167 | color = Colors.lightGreen; 168 | break; 169 | case 2: 170 | color = Colors.pink; 171 | break; 172 | } 173 | return ScrollConfiguration( 174 | behavior: DyBehaviorNull(), 175 | child: ListView( 176 | key: ObjectKey(_navActIndex), 177 | padding: EdgeInsets.all(0), 178 | physics: BouncingScrollPhysics(), 179 | children: [ 180 | Container( 181 | height: 900, 182 | color: color, 183 | ), 184 | ], 185 | ), 186 | ); 187 | } 188 | 189 | // 通过监听手势触发头部动画的下拉与收起 190 | void _onVerticalDragUpdate(DragUpdateDetails details) { 191 | if (details.delta.dy >= 1.0) { 192 | _headerAnimated(-1); // 向下滑动 ↓ 193 | } else if (details.delta.dy <= -1.0 && !_duringAnimation) { 194 | _headerAnimated(1); // 向上滑动 ↑ 195 | } 196 | } 197 | 198 | void _headerAnimated(direction) { 199 | if (_duringAnimation) { 200 | return; 201 | } 202 | _duringAnimation = true; 203 | 204 | setState(() { 205 | _direction = direction; 206 | }); 207 | 208 | Timer(Duration(milliseconds: _headerAnimationTime), () { 209 | _duringAnimation = false; 210 | }); 211 | } 212 | 213 | @override 214 | Widget build(BuildContext context) { 215 | return Scaffold( 216 | body: GestureDetector( 217 | onVerticalDragUpdate: _onVerticalDragUpdate, 218 | child: Column( 219 | children: [ 220 | _direction == null ? DyHeader() : LogoApp(_direction, key: ObjectKey(_direction)), 221 | Container( 222 | color: Colors.transparent, 223 | child: Row( 224 | children: _nav(), 225 | ), 226 | ), 227 | Expanded( 228 | flex: 1, 229 | child: _view(), 230 | ), 231 | ], 232 | ), 233 | ), 234 | ); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /lib/dy_index/fishbar/myConcern.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 鱼吧贴子推荐页(tab - 我的) 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 6 | 7 | import '../../base.dart'; 8 | import '../../service.dart'; 9 | import 'cardList.dart'; 10 | 11 | class MyConcern extends StatefulWidget { 12 | final headerAnimated; 13 | MyConcern({ this.headerAnimated }); 14 | 15 | @override 16 | _MyConcern createState() => _MyConcern(); 17 | } 18 | 19 | class _MyConcern extends State with DYBase { 20 | List _myActive = ['鱼吧签到', '我的车队', '狼人杀']; 21 | GlobalKey _hourTitleKey = GlobalKey(); 22 | RefreshController _refreshController = RefreshController(initialRefresh: false); 23 | 24 | // 下拉刷新 25 | void _onRefresh() async { 26 | rx.push('yubaList', data: 'refresh'); 27 | } 28 | 29 | // 上拉加载 30 | void _onLoading() async { 31 | rx.push('yubaList', data: 'more'); 32 | } 33 | 34 | // 渲染我的活动功能 35 | Widget _renderMyActive() { 36 | return Wrap( 37 | children: _myActive.asMap().map((i, item) { 38 | return MapEntry( 39 | i, Container( 40 | width: (MediaQuery.of(context).size.width - 20) / 4, 41 | color: Colors.transparent, 42 | child: Column( 43 | children: [ 44 | Padding( 45 | padding: EdgeInsets.only( 46 | top: dp(16), 47 | ), 48 | child: Image.asset( 49 | 'images/bar/tab-$i.png', 50 | width: dp(70), 51 | ), 52 | ), 53 | Padding( 54 | padding: EdgeInsets.only( 55 | top: dp(8), 56 | ), 57 | child: Text( 58 | item, 59 | style: TextStyle( 60 | color: Color(0xff333333) 61 | ), 62 | ), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ); 68 | }).values.toList(), 69 | ); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return Stack( 75 | children: [ 76 | Container( 77 | child: SmartRefresher( 78 | enablePullDown: true, 79 | enablePullUp: true, 80 | header: DYrefreshHeader(), 81 | footer: DYrefreshFooter(), 82 | controller: _refreshController, 83 | onRefresh: _onRefresh, 84 | onLoading: _onLoading, 85 | child: ListView( 86 | // controller: _scrollController, 87 | padding: EdgeInsets.all(0), 88 | physics: BouncingScrollPhysics(), 89 | children: [ 90 | Padding( 91 | padding: EdgeInsets.only(bottom: dp(15), left: dp(10), right: dp(10)), 92 | child: _renderMyActive() 93 | ), 94 | Padding( 95 | key: _hourTitleKey, 96 | padding: EdgeInsets.only( 97 | left: dp(15), right: dp(15), top: dp(5), bottom: dp(10), 98 | ), 99 | child: Row( 100 | children: [ 101 | Image.asset( 102 | 'images/bar/day-title.png', 103 | height: dp(16), 104 | ), 105 | Padding(padding: EdgeInsets.only(left: dp(6)),), 106 | Expanded( 107 | flex: 1, 108 | child: Text( 109 | '每日斗鱼', 110 | style: TextStyle( 111 | color: Color(0xff333333), 112 | fontWeight: FontWeight.bold, 113 | fontSize: 15 114 | ), 115 | ), 116 | ), 117 | Image.asset( 118 | 'images/bar/usefulSelect.webp', 119 | height: dp(25), 120 | ), 121 | ], 122 | ), 123 | ), 124 | Container( 125 | color: Color(0xfff3f3f3), 126 | child: Column( 127 | children: [ 128 | Container( 129 | margin: EdgeInsets.all(dp(15)), 130 | child: FishBarCardList( 131 | hourTitleKey: _hourTitleKey, 132 | refreshController: _refreshController, 133 | ), 134 | ), 135 | ], 136 | ), 137 | ), 138 | ], 139 | ), 140 | ), 141 | ), 142 | Positioned( 143 | right: 0, 144 | top: 0, 145 | child: Container( 146 | padding: EdgeInsets.only( left: dp(10), right: dp(8), top: dp(6), bottom: dp(6) ), 147 | decoration: BoxDecoration( 148 | color: Colors.white, 149 | borderRadius: BorderRadius.only( 150 | topLeft: Radius.circular(dp(20)), 151 | bottomLeft: Radius.circular(dp(20)), 152 | ), 153 | boxShadow: [ 154 | BoxShadow(color: Color.fromARGB(50, 92, 92, 92), offset: Offset(dp(-1), dp(5)), blurRadius: dp(4), spreadRadius: dp(-2)), 155 | ], 156 | ), 157 | child: Row( 158 | children: [ 159 | Image.asset( 160 | 'images/bar/hot-title.png', 161 | height: dp(16), 162 | ), 163 | Padding( 164 | padding: EdgeInsets.only(right: dp(5)), 165 | ), 166 | Text( 167 | '斗鱼24小时精彩' 168 | ) 169 | ], 170 | ), 171 | ), 172 | ), 173 | ], 174 | ); 175 | } 176 | } -------------------------------------------------------------------------------- /lib/dy_index/fishbar/photoGallery.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 图片组全屏切换预览 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:photo_view/photo_view.dart'; 6 | import 'package:photo_view/photo_view_gallery.dart'; 7 | 8 | class GalleryItem { 9 | GalleryItem({this.id, this.resource }); 10 | 11 | final String id; 12 | final String resource; 13 | } 14 | 15 | class GalleryPhotoViewWrapper extends StatefulWidget { 16 | GalleryPhotoViewWrapper({ 17 | this.loadingChild, 18 | this.backgroundDecoration, 19 | this.minScale, 20 | this.maxScale, 21 | this.initialIndex, 22 | @required this.galleryItems, 23 | this.scrollDirection = Axis.horizontal, 24 | }) : pageController = PageController(initialPage: initialIndex); 25 | 26 | final Widget loadingChild; 27 | final Decoration backgroundDecoration; 28 | final dynamic minScale; 29 | final dynamic maxScale; 30 | final int initialIndex; 31 | final PageController pageController; 32 | final List galleryItems; 33 | final Axis scrollDirection; 34 | 35 | @override 36 | State createState() { 37 | return _GalleryPhotoViewWrapperState(); 38 | } 39 | } 40 | 41 | class _GalleryPhotoViewWrapperState extends State { 42 | int currentIndex; 43 | 44 | @override 45 | void initState() { 46 | currentIndex = widget.initialIndex; 47 | super.initState(); 48 | } 49 | 50 | void onPageChanged(int index) { 51 | setState(() { 52 | currentIndex = index; 53 | }); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return Scaffold( 59 | body: Container( 60 | decoration: widget.backgroundDecoration, 61 | constraints: BoxConstraints.expand( 62 | height: MediaQuery.of(context).size.height, 63 | ), 64 | child: Stack( 65 | alignment: Alignment.topCenter, 66 | children: [ 67 | PhotoViewGallery.builder( 68 | scrollPhysics: BouncingScrollPhysics(), 69 | builder: _buildItem, 70 | itemCount: widget.galleryItems.length, 71 | loadingChild: widget.loadingChild, 72 | backgroundDecoration: widget.backgroundDecoration, 73 | pageController: widget.pageController, 74 | onPageChanged: onPageChanged, 75 | scrollDirection: widget.scrollDirection, 76 | ), 77 | SafeArea( 78 | child: Container( 79 | padding: EdgeInsets.all(20.0), 80 | child: Text( 81 | "${currentIndex + 1}/${widget.galleryItems.length}", 82 | style: TextStyle( 83 | color: Colors.white, 84 | fontSize: 17.0, 85 | decoration: null, 86 | ), 87 | ), 88 | ), 89 | ), 90 | ], 91 | ), 92 | ), 93 | ); 94 | } 95 | 96 | PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) { 97 | final GalleryItem item = widget.galleryItems[index]; 98 | 99 | return PhotoViewGalleryPageOptions( 100 | imageProvider: NetworkImage(item.resource), 101 | initialScale: PhotoViewComputedScale.contained, 102 | minScale: PhotoViewComputedScale.contained * (0.5 + index / 10), 103 | maxScale: PhotoViewComputedScale.covered * 1.1, 104 | heroAttributes: PhotoViewHeroAttributes(tag: item.id), 105 | ); 106 | } 107 | } -------------------------------------------------------------------------------- /lib/dy_index/fishbar/picView.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 单图片全屏预览 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | 7 | import '../../base.dart'; 8 | 9 | class PicView extends StatefulWidget { 10 | final picUrl, width, height; 11 | PicView(this.picUrl, { this.width, this.height }); 12 | 13 | @override 14 | _PicView createState() => _PicView(picUrl, width, height); 15 | } 16 | 17 | class _PicView extends State with DYBase { 18 | final picUrl, width, height; 19 | _PicView(this.picUrl, this.width, this.height ); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | body: GestureDetector( 25 | onTap: () => Navigator.of(context).pop(), 26 | child: Container( 27 | color: Colors.black, 28 | child: SafeArea( 29 | child: Stack( 30 | alignment: Alignment.topCenter, 31 | children: [ 32 | Center( 33 | child: Hero( 34 | tag: picUrl, 35 | child: Image.network( 36 | picUrl, 37 | width: dp(375), 38 | height: height == null ? null : dp(375 * height / width), 39 | fit: BoxFit.cover, 40 | ), 41 | ), 42 | ), 43 | Positioned( 44 | top: dp(20), 45 | child: Text('1/1', 46 | style: TextStyle( 47 | color: Colors.white, 48 | fontSize: 23.0, 49 | ), 50 | ), 51 | ), 52 | Positioned( 53 | bottom: dp(20), 54 | child: GestureDetector( 55 | onTap: () {}, 56 | child: Container( 57 | height: dp(90), 58 | width: dp(375), 59 | color: Color.fromARGB(100, 255, 255, 255), 60 | child: ListView( 61 | scrollDirection: Axis.horizontal, 62 | children: [ 63 | Row( 64 | children: [ 65 | Padding(padding: EdgeInsets.only(left: dp(10)), 66 | child: Container( 67 | decoration: BoxDecoration( 68 | border: Border.all( 69 | color: DYBase.defaultColor, width: dp(4) 70 | ), 71 | ), 72 | child: Image.network( 73 | picUrl, 74 | width: dp(70), 75 | height: dp(70), 76 | fit: BoxFit.cover, 77 | ), 78 | ), 79 | ), 80 | ], 81 | ), 82 | ], 83 | ), 84 | ), 85 | ), 86 | ), 87 | ], 88 | ), 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | } -------------------------------------------------------------------------------- /lib/dy_index/focus/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 关注 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | import '../../service.dart'; 8 | import '../../base.dart'; 9 | import '../header.dart'; 10 | 11 | /// 该页头部为自定义手势实现的与斗鱼安卓APP相同效果,而不是像首页那样直接调用Flutter封装好的[AppBar]的交互。 12 | 13 | /// 头部[AnimatedBuilder]动画封装 14 | class AnimatedHeader extends StatefulWidget { 15 | final Key key; 16 | final Tween opacityTween, heightTween; 17 | final Function cb; 18 | AnimatedHeader({ 19 | this.key, 20 | this.opacityTween, 21 | this.heightTween, 22 | this.cb, 23 | }); 24 | 25 | @override 26 | _AnimatedHeader createState() => _AnimatedHeader(); 27 | } 28 | 29 | class _AnimatedHeader extends State with SingleTickerProviderStateMixin { 30 | Animation animation; 31 | AnimationController controller; 32 | 33 | @override 34 | initState() { 35 | super.initState(); 36 | controller = AnimationController( 37 | duration: Duration(milliseconds: 250), 38 | vsync: this 39 | ); 40 | animation = CurvedAnimation(parent: controller, curve: Curves.easeOut); 41 | animation.addStatusListener((status) { 42 | if (status == AnimationStatus.completed) { 43 | widget.cb(); 44 | } 45 | }); 46 | controller.forward(); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | controller?.dispose(); 52 | super.dispose(); 53 | } 54 | 55 | Widget build(BuildContext context) { 56 | return AnimatedBuilder( 57 | animation: animation, 58 | builder: (BuildContext ctx, Widget child) { 59 | return DyHeader(height: widget.heightTween.evaluate(animation), opacity: widget.opacityTween.evaluate(animation),); 60 | }, 61 | ); 62 | } 63 | } 64 | 65 | // 页面总结构 66 | class FocusPage extends StatefulWidget with DYBase { 67 | double headerHeightMax; 68 | FocusPage() { 69 | headerHeightMax = DYBase.statusBarHeight + dp(55); 70 | } 71 | 72 | @override 73 | _FocusPage createState() => _FocusPage(headerHeightMax); 74 | } 75 | 76 | class _FocusPage extends State with DYBase, TickerProviderStateMixin { 77 | double _headerHeight; 78 | double _headerOpacity = 1.0; 79 | Tween _opacityTween, _heightTween; 80 | bool _isAnimating = false; 81 | PointerDownEvent _pointDownEvent; 82 | 83 | _FocusPage(this._headerHeight); 84 | 85 | @override 86 | void initState() { 87 | super.initState(); 88 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 89 | statusBarIconBrightness: Brightness.light, 90 | )); 91 | } 92 | 93 | List _colorBlock() { 94 | var res = []; 95 | for (var i = 0; i < 8; i++) { 96 | res.add(Container( 97 | margin: EdgeInsets.only(top: dp(20), left: dp(20), right: dp(20)), 98 | height: dp(120), 99 | decoration: BoxDecoration( 100 | borderRadius: BorderRadius.all(Radius.circular(4)), 101 | border: Border.all(width: dp(2), color: DYBase.defaultColor), 102 | ), 103 | child: Center( 104 | child: Text( 105 | (i + 1).toString(), 106 | style: TextStyle( 107 | color: DYBase.defaultColor, 108 | fontSize: dp(38), 109 | fontWeight: FontWeight.bold, 110 | ), 111 | ), 112 | ), 113 | )); 114 | } 115 | 116 | return res; 117 | } 118 | 119 | void _onPointerMove(PointerMoveEvent e) { 120 | var nextHeight = _headerHeight + e.delta.dy; 121 | 122 | if (nextHeight <= DYBase.statusBarHeight || nextHeight >= widget.headerHeightMax) { 123 | return; 124 | } 125 | setState(() { 126 | _headerHeight = nextHeight; 127 | _headerOpacity = (nextHeight - DYBase.statusBarHeight) / dp(55); 128 | }); 129 | } 130 | 131 | void _onPointerDown(PointerDownEvent e) { 132 | _pointDownEvent = e; 133 | } 134 | 135 | void _onPointerUp(PointerUpEvent e) { 136 | double headerHeightNow = _headerHeight, 137 | headerOpacityNow = _headerOpacity, 138 | direction; // header动画方向,1-展开;0-收起 139 | 140 | // 快速滚动捕获,触摸松开间隔小于300ms直接根据滚动方向伸缩header 141 | if ( 142 | (_pointDownEvent != null) && 143 | (e.timeStamp.inMilliseconds - _pointDownEvent.timeStamp.inMilliseconds < 300) 144 | ) { 145 | if (e.position.dy > _pointDownEvent.position.dy) { 146 | direction = 1; 147 | } else { 148 | direction = 0; 149 | } 150 | } 151 | // 滚动松开时header高度一半以下收起 152 | else if (_headerHeight < (widget.headerHeightMax / 2 + dp(15))) { 153 | direction = 0; 154 | } 155 | // 超过一半就完展开 156 | else { 157 | direction = 1; 158 | } 159 | 160 | setState(() { 161 | if (direction == 0) { 162 | _headerHeight = DYBase.statusBarHeight; 163 | _headerOpacity = 0; 164 | } else { 165 | _headerHeight = widget.headerHeightMax; 166 | _headerOpacity = 1; 167 | } 168 | _heightTween = Tween( 169 | begin: headerHeightNow, end: _headerHeight, 170 | ); 171 | _opacityTween = Tween( 172 | begin: headerOpacityNow, end: _headerOpacity, 173 | ); 174 | _isAnimating = true; 175 | }); 176 | } 177 | 178 | void _animateEndCallBack() { 179 | setState(() { 180 | _isAnimating = false; 181 | }); 182 | } 183 | 184 | /// 在[ListView]之上无法通过[GestureDetector]进行手势捕获,因为部分手势(如上下滑)会提前被[ListView]所命中。 185 | /// 所以在整个页面的最外层使用底层[Listener]监听原始触摸事件,判断手势需要自己取坐标计算。 186 | @override 187 | Widget build(BuildContext context) { 188 | return Scaffold( 189 | body: Listener( 190 | onPointerDown: _onPointerDown, 191 | onPointerUp: _onPointerUp, 192 | onPointerMove: _onPointerMove, 193 | child:Column( 194 | children: [ 195 | _isAnimating ? AnimatedHeader( 196 | key: ObjectKey(_isAnimating), 197 | opacityTween: _opacityTween, 198 | heightTween: _heightTween, 199 | cb: _animateEndCallBack, 200 | ) : DyHeader(height: _headerHeight, opacity: _headerOpacity,), 201 | Expanded( 202 | flex: 1, 203 | child: ScrollConfiguration( 204 | behavior: DyBehaviorNull(), 205 | child: ListView( 206 | physics: BouncingScrollPhysics(), 207 | padding: EdgeInsets.all(0), 208 | children: _colorBlock(), 209 | ), 210 | ), 211 | ), 212 | ], 213 | ), 214 | ), 215 | ); 216 | } 217 | } -------------------------------------------------------------------------------- /lib/dy_index/funny/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 娱乐 3 | */ 4 | import 'package:flutter/material.dart'; 5 | 6 | import '../../base.dart'; 7 | import '../header.dart'; 8 | import 'lottery.dart'; 9 | 10 | class FunnyPage extends StatefulWidget { 11 | @override 12 | _FunnyPage createState() => _FunnyPage(); 13 | } 14 | 15 | class _FunnyPage extends State with DYBase, SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 16 | List tabs = ['抽奖', '竞猜', '答题', '充能', '太空探险', '幻神降临', '幸运水晶']; // 顶部导航栏 17 | 18 | TabController tabController; // 导航栏切换Controller 19 | 20 | @override 21 | bool get wantKeepAlive => true; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | tabController = TabController(initialIndex: 0, length: tabs.length, vsync: this); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | super.build(context); 32 | return Scaffold( 33 | appBar: PreferredSize( 34 | child: AppBar( 35 | bottom: buildTabBar(), 36 | centerTitle: true, 37 | // backgroundColor: Colors.white, 38 | actions: [ 39 | DyHeader(decoration: BoxDecoration( 40 | color: Colors.transparent, 41 | ),), 42 | ], 43 | flexibleSpace: Container( 44 | decoration: BoxDecoration( 45 | gradient: LinearGradient( 46 | colors: [ 47 | Color(0xffff8633), 48 | Color(0xffff6634) 49 | ], 50 | ), 51 | ), 52 | ), 53 | brightness: Brightness.dark, 54 | textTheme: TextTheme( 55 | headline6: TextStyle( 56 | color: Colors.white, 57 | ), 58 | ) 59 | ), 60 | preferredSize: Size.fromHeight(49 + dp(55)), 61 | ), 62 | body: buildBodyView(), 63 | ); 64 | } 65 | 66 | @override 67 | void dispose() { 68 | tabController.dispose(); 69 | super.dispose(); 70 | } 71 | 72 | buildBodyView() { 73 | Widget tabBarBodyView = TabBarView( 74 | controller: tabController, 75 | //创建Tab页 76 | children: tabs.asMap().map((i, e) { 77 | if (i == 0) { // 九宫格抽奖 78 | return MapEntry(i, Lottery()); 79 | } 80 | return MapEntry(i, Container( 81 | alignment: Alignment.center, 82 | child: Text(e, textScaleFactor: 1), 83 | )); 84 | }).values.toList(), 85 | ); 86 | return tabBarBodyView; 87 | } 88 | 89 | buildTabBar() { 90 | Widget tabBar = TabBar( 91 | isScrollable: true, 92 | unselectedLabelStyle: TextStyle( 93 | fontSize: 15, 94 | ), 95 | labelStyle: TextStyle( 96 | fontSize: 18, 97 | ), 98 | labelColor: Colors.white, 99 | unselectedLabelColor: Colors.white70, 100 | indicatorColor: Colors.white, 101 | indicatorWeight: 3, 102 | indicatorSize: TabBarIndicatorSize.label, 103 | controller: tabController, 104 | tabs: tabs.map((e) => Tab(text: e)).toList(), 105 | ); 106 | 107 | return tabBar; 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /lib/dy_index/funny/lottery.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 九宫格抽奖 3 | */ 4 | import 'dart:async'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 9 | 10 | import '../../base.dart'; 11 | import '../../service.dart'; 12 | 13 | class Lottery extends StatefulWidget { 14 | static final List lotteryInOrder = [0, 1, 2, 5, 8, 7, 6, 3]; // 九宫格奖品顺序对应Widget索引 15 | static final int roundBase = 2; // 九宫格匀速转动的基础圈数 16 | static final int slowStep = 7; // 在转到奖品前缓速的步数 17 | static final double slowMultiple = 1.7; // 每次缓速间隔时间的倍率 18 | 19 | @override 20 | _Lottery createState() => _Lottery(); 21 | } 22 | 23 | class _Lottery extends State with DYBase { 24 | Map lotteryConfig; // 从服务端获取的抽奖UI配置信息 25 | int runCount; // 九宫格转动时的次数累计 26 | Timer timer; // 转动时的计时器 27 | Map lotteryResult; // 服务端返回的抽奖结果 28 | FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | // 请求抽奖UI配置渲染 34 | httpClient.get( 35 | API.lotteryConfig, 36 | ).then((res) { 37 | if (mounted) 38 | setState(() { 39 | lotteryConfig = res.data['data']; 40 | }); 41 | }); 42 | } 43 | 44 | // 点击开始抽奖 45 | void _startLottery() { 46 | // 防止重复点击 47 | if (timer != null) { 48 | return; 49 | } 50 | // 初始化九宫格抽奖 51 | if (mounted) 52 | setState(() { 53 | runCount = 0; 54 | lotteryResult = null; 55 | }); 56 | // 开始计时器转动 57 | _lotteryTimer(); 58 | // 同时请求抽奖结果 59 | httpClient.post( 60 | API.lotteryResult, 61 | ).then((res) { 62 | if (mounted) 63 | setState(() { 64 | lotteryResult = res.data['data']; 65 | }); 66 | }); 67 | } 68 | 69 | // 九宫格匀速计时器 70 | void _lotteryTimer() { 71 | timer = Timer(Duration(milliseconds: 100), () { 72 | if (mounted) 73 | setState(() { 74 | runCount++; 75 | }); 76 | if (runCount <= 8 * Lottery.roundBase) { // 首先转动基础圈数,这个时候顺便等待抽奖接口异步结果 77 | _lotteryTimer(); 78 | } else if ( 79 | runCount <= 8 * (Lottery.roundBase + 1) + lotteryResult['giftIndex'] - Lottery.slowStep 80 | ) { // 转满基础圈数后,计算出多转一圈 + 结果索引 - 缓速步数,进行最后几步的匀速转动 81 | _lotteryTimer(); 82 | } else { // 匀速结果,进入开奖前缓速转动 83 | _slowLotteryTimer(100); 84 | } 85 | }); 86 | } 87 | 88 | // 九宫格缓速计时器 89 | void _slowLotteryTimer(ms) { 90 | timer = Timer(Duration(milliseconds: ms), () { 91 | if (mounted) 92 | setState(() { 93 | runCount++; 94 | }); 95 | if (runCount < 8 * (Lottery.roundBase + 1) + lotteryResult['giftIndex']) 96 | { // 如果当前步数没有达到结果位置,继续缓速转动,并在下一步增长缓速时间,实现越来越慢的开奖效果 97 | _slowLotteryTimer((ms * Lottery.slowMultiple).ceil()); 98 | } else { // 已转到开奖位置,弹窗提醒 99 | if (lotteryResult['giftIndex'] == 3) { 100 | DYdialog.alert(context, title: '很遗憾~', text: '谢谢参与', 101 | yesCallBack: () => { 102 | if (mounted) 103 | setState(() { 104 | runCount = null; 105 | }) 106 | }, 107 | ); 108 | } else { 109 | var title = '中奖了!', 110 | body = '恭喜您获得 ${lotteryResult['giftName']}'; 111 | DYdialog.alert(context, title: title, text: body, 112 | yesCallBack: () => { 113 | if (mounted) 114 | setState(() { 115 | runCount = null; 116 | }) 117 | }, 118 | ); 119 | _showNotification(title, body); 120 | } 121 | timer?.cancel(); 122 | timer = null; 123 | } 124 | }); 125 | } 126 | 127 | // 系统通知栏消息推送 128 | void _showNotification(String title, String body) async { 129 | flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); 130 | var initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/dy'); 131 | var initializationSettingsIOS = IOSInitializationSettings( 132 | onDidReceiveLocalNotification: (int id, String title, String body, String payload) async => null); 133 | var initializationSettings = InitializationSettings( 134 | android: initializationSettingsAndroid, 135 | iOS: initializationSettingsIOS); 136 | 137 | flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: (String payload) async { 138 | if (payload != null) { 139 | print('notification payload: ' + payload); 140 | } 141 | // 点击通知栏跳转的页面(暂为空白) 142 | // await Navigator.push( 143 | // context, 144 | // new MaterialPageRoute(builder: (context) => Container(color: Colors.white,)), 145 | // ); 146 | }); 147 | 148 | var androidPlatformChannelSpecifics = AndroidNotificationDetails( 149 | '0', title, body, 150 | importance: Importance.max, priority: Priority.high, ticker: 'ticker', 151 | ); 152 | var iOSPlatformChannelSpecifics = IOSNotificationDetails(); 153 | var platformChannelSpecifics = NotificationDetails( 154 | android: androidPlatformChannelSpecifics, 155 | iOS: iOSPlatformChannelSpecifics); 156 | await flutterLocalNotificationsPlugin.show( 157 | DYservice.randomBit(8, type: 'num'), title, body, platformChannelSpecifics, 158 | payload: body 159 | ); 160 | } 161 | 162 | @override 163 | void dispose() { 164 | timer?.cancel(); 165 | super.dispose(); 166 | } 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 171 | return Container( 172 | child: lotteryConfig == null ? null : Card( 173 | elevation: 5, 174 | margin: EdgeInsets.all(dp(20)), 175 | shape: RoundedRectangleBorder( 176 | borderRadius: BorderRadius.all( 177 | Radius.circular(dp(25)), 178 | ), 179 | ), 180 | clipBehavior: Clip.antiAlias, 181 | color: Color(0xffff9434), 182 | child: ListView( 183 | physics: BouncingScrollPhysics(), 184 | children: [ 185 | Container( 186 | height: dp(lotteryConfig['pageH']), 187 | decoration: BoxDecoration( 188 | image: DecorationImage( 189 | image: NetworkImage(lotteryConfig['pageBg']), 190 | fit: BoxFit.fill, 191 | ), 192 | ), 193 | child: Stack( 194 | alignment: AlignmentDirectional.center, 195 | children: [ 196 | Positioned( 197 | bottom: 0, 198 | child: Transform.scale( 199 | scale: 0.9, 200 | child: Container( 201 | width: dp(lotteryConfig['lotteryW']), 202 | height: dp(lotteryConfig['lotteryH']), 203 | decoration: BoxDecoration( 204 | image: DecorationImage( 205 | image: NetworkImage(lotteryConfig['lotteryBg']), 206 | fit: BoxFit.fill, 207 | ), 208 | ), 209 | child: Wrap( 210 | children: _renderLotteryItem(), 211 | ), 212 | ), 213 | ), 214 | ), 215 | ], 216 | ), 217 | ), 218 | Row( 219 | mainAxisAlignment: MainAxisAlignment.center, 220 | children: [ 221 | GestureDetector( 222 | onTap: () => DYdialog.alert(context, text: '正在建设中~'), 223 | child: Container( 224 | width: dp(lotteryConfig['myRewardW']), 225 | height: dp(lotteryConfig['myRewardH']), 226 | margin: EdgeInsets.only(bottom: dp(10)), 227 | decoration: BoxDecoration( 228 | image: DecorationImage( 229 | image: NetworkImage(lotteryConfig['myRewardBg']), 230 | fit: BoxFit.fill, 231 | ), 232 | ), 233 | ), 234 | ), 235 | ], 236 | ) 237 | ], 238 | ), 239 | ), 240 | ); 241 | } 242 | 243 | List _renderLotteryItem() { 244 | var inOrder = Lottery.lotteryInOrder, 245 | height = dp(lotteryConfig['lotteryH'] / 3), 246 | width = dp(lotteryConfig['lotteryW'] / 3); 247 | return List(9).asMap().map((i, item) { 248 | if (i == 4) { 249 | return MapEntry(i, GestureDetector( 250 | onTap: _startLottery, 251 | child: Container( 252 | width: width, height: height, 253 | color: Colors.transparent, 254 | ), 255 | ), 256 | ); 257 | } 258 | return MapEntry(i, Container( 259 | width: width, height: height, 260 | decoration: runCount != null && i == inOrder[runCount % inOrder.length] ? BoxDecoration( 261 | image: DecorationImage( 262 | image: NetworkImage(lotteryConfig['highLightBg']), 263 | fit: BoxFit.fill, 264 | ), 265 | ) : null, 266 | )); 267 | }).values.toList(); 268 | } 269 | } -------------------------------------------------------------------------------- /lib/dy_index/header.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: app通用头部组件 3 | */ 4 | import 'dart:ui'; 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | // import 'package:barcode_scan/barcode_scan.dart'; 9 | // import 'package:flutter/services.dart'; 10 | 11 | import '../base.dart'; 12 | import '../service.dart'; 13 | 14 | class DyHeader extends StatefulWidget { 15 | final num height; 16 | final num opacity; 17 | final BoxDecoration decoration; 18 | DyHeader({ this.height, this.opacity = 1.0, this.decoration }); 19 | 20 | @override 21 | _DyHeader createState() => _DyHeader(); 22 | } 23 | 24 | class _DyHeader extends State with DYBase { 25 | TextEditingController _search = TextEditingController(); 26 | // ScanResult _scanResult; 27 | 28 | Future _scan() async { 29 | // try { 30 | // _scanResult = await BarcodeScanner.scan(); 31 | // if (_scanResult.rawContent != '') { 32 | // _search.text = _scanResult.rawContent; 33 | // } 34 | // } on PlatformException catch (e) { 35 | // if (e.code == BarcodeScanner.cameraAccessDenied) { 36 | // DYdialog.alert(context, text: '设备未获得权限'); 37 | // } else { 38 | // DYdialog.alert(context, text: '未捕获的错误: $e'); 39 | // } 40 | // } 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | double screenWidth = MediaQuery.of(context).size.width; 46 | 47 | return Container( 48 | height: widget.height != null ? widget.height : DYBase.statusBarHeight + dp(55), 49 | width: screenWidth, 50 | decoration: widget.decoration != null ? widget.decoration : BoxDecoration( 51 | gradient: LinearGradient( 52 | colors: [ 53 | Color(0xffff8633), 54 | Color(0xffff6634), 55 | ], 56 | ), 57 | ), 58 | child: Stack( 59 | alignment: AlignmentDirectional.center, 60 | children: [ 61 | Positioned( 62 | bottom: dp(10), 63 | child: Opacity( 64 | opacity: widget.opacity, 65 | child: SizedBox( 66 | width: screenWidth - dp(30), 67 | height: dp(40), 68 | child: Row( 69 | mainAxisAlignment: MainAxisAlignment.spaceAround, 70 | children: [ 71 | ClipRRect( 72 | borderRadius: BorderRadius.all( 73 | Radius.circular(dp(20)), 74 | ), 75 | child: GestureDetector( 76 | onTap: () => DYdialog.showLogin(context), 77 | child: Image.asset( 78 | 'images/default-avatar.webp', 79 | width: dp(40), 80 | height: dp(40), 81 | fit: BoxFit.fill, 82 | ), 83 | ), 84 | ), 85 | Expanded( 86 | flex: 1, 87 | child: Container( 88 | height: dp(35), 89 | margin: EdgeInsets.only(left: dp(15)), 90 | padding: EdgeInsets.only(left: dp(5), right: dp(5)), 91 | decoration: BoxDecoration( 92 | color: Colors.white, 93 | borderRadius: BorderRadius.all( 94 | Radius.circular(dp(35 / 2)), 95 | ), 96 | ), 97 | child: Row( 98 | children: [ 99 | // 搜索ICON 100 | Image.asset( 101 | 'images/head/search.webp', 102 | width: dp(25), 103 | height: dp(15), 104 | ), 105 | // 搜索输入框 106 | Expanded( 107 | flex: 1, 108 | child: TextField( 109 | controller: _search, 110 | cursorColor: DYBase.defaultColor, 111 | cursorWidth: 1.5, 112 | style: TextStyle( 113 | color: DYBase.defaultColor, 114 | fontSize: 14.0, 115 | ), 116 | decoration: InputDecoration( 117 | border: OutlineInputBorder( 118 | borderSide: BorderSide.none 119 | ), 120 | contentPadding: EdgeInsets.all(0), 121 | hintText: '搜你喜欢的主播吧~', 122 | ), 123 | ), 124 | ), 125 | Platform.isAndroid ? GestureDetector( 126 | onTap: _scan, 127 | child: Image.asset( 128 | 'images/head/camera.webp', 129 | width: dp(20), 130 | height: dp(15), 131 | ), 132 | ) : SizedBox(), 133 | ], 134 | ), 135 | ), 136 | ), 137 | Padding( 138 | padding: EdgeInsets.only(left: dp(10)), 139 | child: Image.asset( 140 | 'images/head/history.webp', 141 | width: dp(25), 142 | ), 143 | ), 144 | Padding( 145 | padding: EdgeInsets.only(left: dp(10)), 146 | child: Image.asset( 147 | 'images/head/game.webp', 148 | width: dp(25), 149 | ), 150 | ), 151 | Padding( 152 | padding: EdgeInsets.only(left: dp(10)), 153 | child: Image.asset( 154 | 'images/head/chat.webp', 155 | width: dp(25), 156 | ), 157 | ), 158 | ], 159 | ), 160 | ), 161 | ), 162 | ) 163 | ], 164 | ), 165 | ); 166 | } 167 | } -------------------------------------------------------------------------------- /lib/dy_index/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 底部导航 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | 8 | import '../base.dart'; 9 | import 'header.dart'; 10 | import 'funny/index.dart'; 11 | import 'focus/index.dart'; 12 | import 'commend/index.dart'; 13 | import 'fishbar/index.dart'; 14 | 15 | class DyIndexPage extends StatefulWidget { 16 | final arguments; 17 | DyIndexPage({Key key, this.arguments}) : super(key: key); 18 | 19 | @override 20 | _DyIndexPageState createState() => _DyIndexPageState(); 21 | } 22 | 23 | class _DyIndexPageState extends State with DYBase { 24 | final _bottomNavList = ["推荐", "娱乐", "鱼吧", "关注", "发现"]; // 底部导航 25 | DateTime _lastCloseApp; //上次点击返回按钮时间 26 | int _currentIndex = 0; // 底部导航当前页面 27 | ScrollController _scrollController = ScrollController(); // 首页整体滚动控制器 28 | PageController _pageController = PageController(); 29 | 30 | @override 31 | void dispose() { 32 | _scrollController?.dispose(); 33 | super.dispose(); 34 | } 35 | 36 | // 点击悬浮标回到顶部 37 | void _indexPageScrollTop() { 38 | _scrollController.animateTo(.0, 39 | duration: Duration(milliseconds: 300), 40 | curve: Curves.ease 41 | ); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 47 | 48 | return WillPopScope( 49 | onWillPop: () async { 50 | if (_lastCloseApp == null || DateTime.now().difference(_lastCloseApp) > Duration(seconds: 1)) { 51 | _lastCloseApp = DateTime.now(); 52 | Fluttertoast.showToast(msg: '再按一次退出斗鱼'); 53 | return false; 54 | } 55 | return true; 56 | }, 57 | child: Scaffold( 58 | // 底部导航栏 59 | bottomNavigationBar: BottomNavigationBar( 60 | backgroundColor: Colors.white, 61 | currentIndex: _currentIndex, 62 | type: BottomNavigationBarType.fixed, 63 | selectedItemColor: DYBase.defaultColor, 64 | unselectedItemColor: Color(0xff333333), 65 | selectedFontSize: dp(12), 66 | unselectedFontSize: dp(12), 67 | onTap: (index) { 68 | if (mounted) 69 | setState(() { 70 | _currentIndex = index; 71 | }); 72 | _pageController.jumpToPage(index); 73 | }, 74 | items: [ 75 | BottomNavigationBarItem( 76 | label: _bottomNavList[0], 77 | icon: _currentIndex == 0 78 | ? _bottomIcon('images/nav/nav-12.jpg') 79 | : _bottomIcon('images/nav/nav-11.jpg')), 80 | BottomNavigationBarItem( 81 | label: _bottomNavList[1], 82 | icon: _currentIndex == 1 83 | ? _bottomIcon('images/nav/nav-22.jpg') 84 | : _bottomIcon('images/nav/nav-21.jpg')), 85 | BottomNavigationBarItem( 86 | label: _bottomNavList[2], 87 | icon: _currentIndex == 2 88 | ? _bottomIcon('images/nav/nav-42.jpg') 89 | : _bottomIcon('images/nav/nav-41.jpg')), 90 | BottomNavigationBarItem( 91 | label: _bottomNavList[3], 92 | icon: _currentIndex == 3 93 | ? _bottomIcon('images/nav/nav-32.jpg') 94 | : _bottomIcon('images/nav/nav-31.jpg')), 95 | BottomNavigationBarItem( 96 | label: _bottomNavList[4], 97 | icon: _currentIndex == 4 98 | ? _bottomIcon('images/nav/nav-52.jpg') 99 | : _bottomIcon('images/nav/nav-51.jpg')), 100 | ] 101 | ), 102 | body: _currentPage(), 103 | floatingActionButton: _currentIndex != 0 ? null : FloatingActionButton( 104 | onPressed: _indexPageScrollTop, 105 | foregroundColor: DYBase.defaultColor, 106 | backgroundColor: Colors.white, 107 | tooltip: 'Increment', 108 | child: Image.asset( 109 | 'images/float-icon.webp', 110 | width: dp(100), 111 | height: dp(100), 112 | fit: BoxFit.contain, 113 | ) 114 | ), 115 | resizeToAvoidBottomInset: false, 116 | ), 117 | ); 118 | } 119 | 120 | void _openTestPage() { 121 | Navigator.pushNamed(context, '/develop'); 122 | } 123 | 124 | // 底部导航对应的页面 125 | Widget _currentPage() { 126 | var pageInDevelop = Scaffold( 127 | appBar: PreferredSize( 128 | child: AppBar( 129 | title:Text(_bottomNavList[_currentIndex]), 130 | backgroundColor: DYBase.defaultColor, 131 | brightness: Brightness.dark, 132 | textTheme: TextTheme( 133 | headline6: TextStyle( 134 | color: Colors.white, 135 | fontSize: 18, 136 | ), 137 | ), 138 | flexibleSpace: Container( 139 | decoration: BoxDecoration( 140 | gradient: LinearGradient( 141 | colors: [ 142 | Color(0xffff8633), 143 | Color(0xffff6634) 144 | ], 145 | ), 146 | ), 147 | ), 148 | actions: [ 149 | DyHeader(decoration: BoxDecoration( 150 | color: Colors.transparent, 151 | ),), 152 | ], 153 | ), 154 | preferredSize: Size.fromHeight(dp(55)), 155 | ), 156 | body: Center( 157 | child: RaisedButton( 158 | textColor: Colors.white, 159 | color: DYBase.defaultColor, 160 | child: Text('打开测试页面'), 161 | onPressed: _openTestPage, 162 | ), 163 | ), 164 | ); 165 | var _pages = [ 166 | CommendPage(_scrollController), 167 | FunnyPage(), 168 | FishBarPage(), 169 | FocusPage(), 170 | pageInDevelop, 171 | ]; 172 | 173 | return PageView.builder( 174 | physics: NeverScrollableScrollPhysics(), 175 | controller: _pageController, 176 | itemCount: _pages.length, 177 | itemBuilder: (context,index)=>_pages[index] 178 | ); 179 | } 180 | 181 | Widget _bottomIcon(path) { 182 | return Padding( 183 | padding: EdgeInsets.only(bottom: dp(4)), 184 | child: Image.asset( 185 | path, 186 | width: dp(25), 187 | height: dp(25), 188 | repeat:ImageRepeat.noRepeat, 189 | fit: BoxFit.contain, 190 | alignment: Alignment.center, 191 | ) 192 | ); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /lib/dy_init/countdown.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 启动页右上角倒计时 3 | */ 4 | import 'dart:math' as Math; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 9 | 10 | import '../base.dart'; 11 | 12 | class CountdownInit extends StatefulWidget { 13 | CountdownInit({Key key}) : super(key: key); 14 | 15 | @override 16 | _CountdownInit createState() => _CountdownInit(); 17 | } 18 | 19 | class _CountdownInit extends State with DYBase, SingleTickerProviderStateMixin { 20 | Animation _animation; // canvas转动动画函数 21 | AnimationController _controller; // canvas转动动画控制器 22 | int _time = 5; // 首页载入秒数 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 28 | statusBarBrightness: Brightness.light, 29 | statusBarIconBrightness: Brightness.light, 30 | )); 31 | // SystemChrome.setEnabledSystemUIOverlays([]); 32 | 33 | _controller = AnimationController(duration: Duration(seconds: _time), vsync: this,); // 倒计时动画控制器 34 | _animation = Tween(begin: 0.0, end: 360.0).animate(_controller); 35 | _controller.addListener(() { 36 | setState(() {}); 37 | }); 38 | _controller.addStatusListener((AnimationStatus a) { 39 | if (a == AnimationStatus.completed) { 40 | _jumpIndex(); 41 | } 42 | }); 43 | _controller.forward(); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | _controller?.dispose(); 49 | super.dispose(); 50 | } 51 | 52 | void _jumpIndex() { 53 | Navigator.of(context).pushReplacementNamed('/index'); 54 | /* Future.delayed(Duration(milliseconds: 300), () { 55 | SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); 56 | }); */ 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 62 | var countNum = _time - (_animation.value * _time / 360).round(); 63 | 64 | return GestureDetector( 65 | onTap: _jumpIndex, 66 | child: Container( 67 | width: dp(50), 68 | height: dp(50), 69 | decoration: ShapeDecoration( 70 | color: Color.fromARGB(70, 0, 0, 0), 71 | shape: RoundedRectangleBorder( 72 | borderRadius: BorderRadius.all( 73 | Radius.circular(dp(50)), 74 | ), 75 | ), 76 | ), 77 | child: CustomPaint( 78 | child: Center( 79 | child: Text( 80 | '${countNum}s', 81 | style: TextStyle( 82 | color: Colors.white, 83 | fontSize: 16, 84 | ), 85 | ), 86 | ), 87 | painter: CircleProgressBarPainter(_animation.value), 88 | ), 89 | ), 90 | ); 91 | } 92 | 93 | } 94 | 95 | // 画布绘制加载倒计时 96 | class CircleProgressBarPainter extends CustomPainter { 97 | // Paint _paintBackground; 98 | Paint _paintFore; 99 | final rate; 100 | 101 | CircleProgressBarPainter(this.rate) { 102 | // _paintBackground = Paint() 103 | // ..color = Colors.white 104 | // ..strokeCap = StrokeCap.round 105 | // ..style = PaintingStyle.stroke 106 | // ..strokeWidth = 2.0 107 | // ..isAntiAlias = true; 108 | _paintFore = Paint() 109 | ..color = Colors.white 110 | ..strokeCap = StrokeCap.round 111 | ..style = PaintingStyle.stroke 112 | ..strokeWidth = 10.0 113 | ..isAntiAlias = true; 114 | } 115 | @override 116 | void paint(Canvas canvas, Size size) { 117 | /* canvas.drawCircle(Offset(size.width / 2, size.height / 2), size.width / 2, 118 | _paintBackground); */ 119 | Rect rect = Rect.fromCircle( 120 | center: Offset(size.width / 2, size.height / 2), 121 | radius: size.width / 2, 122 | ); 123 | canvas.drawArc(rect, 0.0, rate * Math.pi / 180, false, _paintFore); 124 | } 125 | 126 | @override 127 | bool shouldRepaint(CustomPainter oldDelegate) { 128 | return true; 129 | } 130 | } -------------------------------------------------------------------------------- /lib/dy_init/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 载入APP启动页 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | import 'package:dio_http_cache/dio_http_cache.dart'; 9 | import 'package:dio/dio.dart'; 10 | 11 | import '../bloc.dart'; 12 | import '../base.dart'; 13 | import '../service.dart'; 14 | import 'countdown.dart'; 15 | 16 | class SplashPage extends StatefulWidget { 17 | SplashPage({Key key}) : super(key: key); 18 | 19 | @override 20 | _SplashPageState createState() => _SplashPageState(); 21 | } 22 | 23 | class _SplashPageState extends State with DYBase { 24 | SharedPreferences prefs; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _initAsync(); 30 | _getNav(); 31 | _getSwiperPic(); 32 | _initLiveData(); 33 | } 34 | 35 | void _initAsync() async { 36 | // App启动时读取Sp数据,需要异步等待Sp初始化完成。 37 | prefs = await SharedPreferences.getInstance(); 38 | } 39 | 40 | // 获取导航列表 41 | void _getNav() { 42 | final indexBloc = BlocProvider.of(context); 43 | 44 | httpClient.get( 45 | API.nav, 46 | ).then((res) { 47 | var navList = res.data['data']; 48 | indexBloc.add(UpdateTab(navList)); 49 | }); 50 | } 51 | 52 | // 获取轮播图列表 53 | void _getSwiperPic() { 54 | final indexBloc = BlocProvider.of(context); 55 | 56 | httpClient.post( 57 | API.swiper, 58 | data: FormData.fromMap({ 59 | 'num': 4 60 | }), 61 | options: buildCacheOptions( 62 | Duration(minutes: 30), 63 | // options: Options(contentType: ContentType.parse("application/x-www-form-urlencoded")) 64 | ), 65 | ).then((res) { 66 | var swiper = res.data['data']; 67 | indexBloc.add(UpdateSwiper(swiper)); 68 | }).catchError((err) { 69 | indexBloc.add(UpdateSwiper(null)); 70 | }); 71 | } 72 | 73 | void _initLiveData() async { 74 | final indexBloc = BlocProvider.of(context); 75 | 76 | var liveList = await DYservice.getLiveData(context); 77 | indexBloc.add(UpdateLiveData(liveList)); 78 | } 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 83 | return Scaffold( 84 | body: SizedBox( 85 | width: MediaQuery.of(context).size.width, 86 | child: Stack( 87 | children: [ 88 | Positioned( 89 | right: dp(25), 90 | top: dp(25), 91 | child: CountdownInit(), 92 | ), 93 | SizedBox( 94 | width: dp(375), 95 | child: Column( 96 | mainAxisAlignment: MainAxisAlignment.center, 97 | children: [ 98 | Image.asset( 99 | 'images/init_logo.webp', 100 | width: dp(300), 101 | ), 102 | Padding(padding: EdgeInsets.only(top: dp(70)),), 103 | Image.asset( 104 | 'images/init_icon.png', 105 | width: dp(90), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ], 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/dy_login/area.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 国家首字母选择页(仿微信联系人列表) 3 | */ 4 | import 'dart:ui'; 5 | import 'dart:math'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 10 | import 'package:dio_http_cache/dio_http_cache.dart'; 11 | 12 | import '../base.dart'; 13 | 14 | const _letter = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 15 | 16 | class AreaTel extends StatefulWidget { 17 | AreaTel({Key key}) : super(key: key); 18 | 19 | @override 20 | _AreaTel createState() => _AreaTel(); 21 | } 22 | 23 | class _AreaTel extends State with DYBase { 24 | double _letterListHeight = 0; // 字母列表的高度 25 | int _actLetter; // 当前触摸区域字母的索引 26 | ScrollController _scrollController = ScrollController(); 27 | Map _titlekey = {}; 28 | Map _area = {}; 29 | Map _letterScrollTopMap; 30 | 31 | _AreaTel() { 32 | _letterListHeight = MediaQueryData.fromWindow(window).size.height - DYBase.statusBarHeight - dp(50) - 60; 33 | } 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | _getAreaList(); 39 | } 40 | 41 | void _chooseArea(String country, String tel) { 42 | rx.push('chooseArea', data: [country, tel] ); 43 | Navigator.of(context).pop(); 44 | /* if (key.currentContext == null) { 45 | return; 46 | } 47 | RenderBox renderBox = key.currentContext.findRenderObject(); 48 | Offset offset = renderBox.localToGlobal(Offset.zero); 49 | 50 | print('${offset.dx},${offset.dy}'); */ 51 | } 52 | 53 | // 触摸右侧字母栏滑动时,滚动到对应的字母地区集 54 | void _onVerticalDragUpdate(details) { 55 | var eachHeight =_letterListHeight / 26; 56 | if (details.localPosition.dy <= 0) { 57 | _actLetter = 0; 58 | } else if (details.localPosition.dy >= _letterListHeight) { 59 | _actLetter = 25; 60 | } else { 61 | _actLetter = details.localPosition.dy ~/ eachHeight; 62 | } 63 | setState(() {}); 64 | if (_letterScrollTopMap[_letter[_actLetter]] != null) { 65 | _scrollController.jumpTo( 66 | min(_letterScrollTopMap[_letter[_actLetter]], _scrollController.position.maxScrollExtent) 67 | ); 68 | } 69 | } 70 | 71 | // 触摸松手置空选中字母 72 | void _onVerticalDragEnd(details) { 73 | setState(() { 74 | _actLetter = null; 75 | }); 76 | } 77 | 78 | // 获取地区静态配置文件 79 | void _getAreaList() async { 80 | var res = await httpClient.get( 81 | API.areaList, 82 | options: buildCacheOptions( 83 | Duration(days: 365), 84 | ), 85 | ); 86 | var data = res.data; 87 | var letterScrollTopMap = {}; 88 | double height = 0; 89 | data.forEach((key, value) { 90 | if (value.length == 0) { 91 | return; 92 | } 93 | letterScrollTopMap[key] = height; 94 | var i = 0; 95 | do { 96 | height += dp(46.6); 97 | i++; 98 | } while(i < value.length + 1); 99 | }); 100 | setState(() { 101 | _area = data; 102 | _letterScrollTopMap = letterScrollTopMap; 103 | }); 104 | } 105 | 106 | List _areaList() { 107 | if (_area == null) { 108 | return [SizedBox()]; 109 | } 110 | List result = []; 111 | _area.forEach((key, value) { 112 | if (value.length == 0) { 113 | return null; 114 | } 115 | GlobalKey _key = GlobalKey(); 116 | _titlekey[key] = _key; 117 | result.add(Container( 118 | key: _key, 119 | margin: EdgeInsets.only(left: dp(6), right: dp(6)), 120 | decoration: BoxDecoration( 121 | border: Border(bottom: BorderSide(color: Color(0xff999999), width: dp(.6))), 122 | ), 123 | child: Container( 124 | height: dp(46), 125 | color: Colors.transparent, 126 | child: Row( 127 | children: [ 128 | Padding( 129 | padding: EdgeInsets.only(top: dp(10)), 130 | child: Text( 131 | key, 132 | style: TextStyle(color: Color(0xff999999),), 133 | ), 134 | ), 135 | ], 136 | ), 137 | ), 138 | )); 139 | value.forEach((item) { 140 | var _item = item.split('-'); 141 | var enName = _item[0]; 142 | var cnName = _item[1]; 143 | var tel = _item[2]; 144 | result.add(GestureDetector( 145 | onTap: () => _chooseArea(cnName, tel), 146 | child: Container( 147 | margin: EdgeInsets.only(left: dp(6), right: dp(6)), 148 | decoration: BoxDecoration( 149 | border: Border(bottom: BorderSide(color: Color(0xff999999), width: dp(.6))), 150 | ), 151 | child: Container( 152 | height: dp(46), 153 | color: Colors.transparent, 154 | child: Row( 155 | children: [ 156 | Text( 157 | '$cnName($enName)', 158 | overflow: TextOverflow.ellipsis, 159 | ), 160 | ], 161 | ), 162 | ), 163 | ), 164 | )); 165 | }); 166 | }); 167 | 168 | return result; 169 | } 170 | 171 | List _letterList() { 172 | List config = _area.map((key, value) { 173 | return MapEntry(key, { 174 | 'key': key, 175 | }); 176 | }).values.toList(); 177 | 178 | return config.asMap().map((i, item) { 179 | var baseColor = _actLetter == null ? Colors.black : Colors.white; 180 | return MapEntry(i, Container( 181 | height: _letterListHeight / 26, 182 | child: Center(child: Text(item['key'], 183 | style: TextStyle( 184 | color: i == _actLetter ? DYBase.defaultColor : baseColor, 185 | fontSize: i == _actLetter ? 12.0 : 8.0, 186 | fontWeight: FontWeight.bold, 187 | ), 188 | ),), 189 | )); 190 | }).values.toList(); 191 | } 192 | 193 | @override 194 | Widget build(BuildContext context) { 195 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 196 | return Scaffold( 197 | appBar: PreferredSize( 198 | child: AppBar( 199 | title: Text('选择国家和地区代码'), 200 | titleSpacing: 0, 201 | backgroundColor: Colors.white, 202 | brightness: Brightness.light, 203 | leading: GestureDetector( 204 | onTap: () => Navigator.of(context).pop(), 205 | child: Container( 206 | color: Colors.white, 207 | child: Center( 208 | child: Image.asset('images/back.webp', 209 | width: dp(8), 210 | ), 211 | ), 212 | ), 213 | ), 214 | elevation: 0, 215 | ), 216 | preferredSize: Size.fromHeight(dp(55)), 217 | ), 218 | backgroundColor: Color(0xffeeeeee), 219 | body: Stack( 220 | alignment: AlignmentDirectional.center, 221 | children: [ 222 | ListView( 223 | controller: _scrollController, 224 | padding: EdgeInsets.all(0), 225 | children: [ 226 | ..._areaList(), 227 | ], 228 | ), 229 | Positioned( 230 | right: 0, 231 | child: Container( 232 | color: _actLetter == null ? Colors.transparent : Color.fromARGB(90, 0, 0, 0), 233 | width: dp(30), 234 | height: _letterListHeight + 60, 235 | child: Center( 236 | child: GestureDetector( 237 | onVerticalDragEnd: _onVerticalDragEnd, 238 | onVerticalDragDown: _onVerticalDragUpdate, 239 | onVerticalDragUpdate: _onVerticalDragUpdate, 240 | child: Container( 241 | color: Colors.transparent, 242 | height: _letterListHeight, 243 | child: Column( 244 | children: _letterList(), 245 | ), 246 | ), 247 | ), 248 | ), 249 | ), 250 | ), 251 | _actLetter != null ? Positioned( 252 | child: Container( 253 | height: dp(120), 254 | width: dp(120), 255 | decoration: BoxDecoration( 256 | color: DYBase.defaultColor, 257 | borderRadius: BorderRadius.all(Radius.circular(15)), 258 | ), 259 | child: Center( 260 | child: Text(_letter[_actLetter], 261 | style: TextStyle( 262 | fontSize: dp(60), 263 | color: Colors.white, 264 | fontWeight: FontWeight.bold, 265 | ), 266 | ), 267 | ), 268 | ), 269 | ) : SizedBox(), 270 | ], 271 | ), 272 | ); 273 | } 274 | 275 | } -------------------------------------------------------------------------------- /lib/dy_room/animate.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 弹幕区礼物横幅动画队列 3 | */ 4 | import 'dart:async'; 5 | 6 | import 'package:flutter/material.dart'; 7 | 8 | import '../base.dart'; 9 | 10 | // 暴露给直播间调用的礼物横幅类 11 | class Gift extends DYBase { 12 | // 在礼物横幅队列中增加 13 | Gift.add(giftBannerView, json, removeTime, cb) { 14 | json['widget'] = GiftBanner( 15 | giftInfo: json['config'], 16 | queueLength: giftBannerView.length, 17 | ); 18 | giftBannerView.add(json); 19 | cb(giftBannerView); // 将重新生成的礼物横幅队列Widget返回给直播间setState 20 | 21 | // 给定时间后从队列中将礼物移除 22 | Timer(Duration(milliseconds: removeTime), () { 23 | for (int i = 0; i < giftBannerView.length; i++) { 24 | if (json['stamp'] == giftBannerView[i]['stamp']) { 25 | giftBannerView.removeAt(i); 26 | cb(giftBannerView); 27 | } 28 | } 29 | }); 30 | } 31 | } 32 | 33 | class GiftBanner extends StatefulWidget with DYBase { 34 | final Map giftInfo; 35 | final int queueLength; 36 | GiftBanner({this.giftInfo, this.queueLength}); 37 | 38 | @override 39 | _GiftBannerState createState() => _GiftBannerState(); 40 | } 41 | 42 | // 单个礼物横幅的动画Widget 43 | class _GiftBannerState extends State with DYBase, SingleTickerProviderStateMixin { 44 | Animation animationGiftNum_1, animationGiftNum_2, animationGiftNum_3; 45 | AnimationController controller; 46 | 47 | @override 48 | void initState() { 49 | super.initState(); 50 | if (widget.queueLength >= 4) return; 51 | 52 | controller = AnimationController( 53 | duration: Duration(milliseconds: 1800), 54 | vsync: this 55 | ); 56 | 57 | // 礼物数量图片变大 58 | animationGiftNum_1 = Tween( 59 | begin: 0.0, end: 1.7, 60 | ).animate( 61 | CurvedAnimation( 62 | parent: controller, 63 | curve: Interval( 64 | 0.75, 0.85, 65 | curve: Curves.easeOut 66 | ), 67 | ) 68 | ); 69 | 70 | // 礼物数量图片变小 71 | animationGiftNum_2 = Tween( 72 | begin: 1.7, end: 1.0, 73 | ).animate( 74 | CurvedAnimation( 75 | parent: controller, 76 | curve: Interval( 77 | 0.85, 1.0, 78 | curve: Curves.easeIn 79 | ), 80 | ) 81 | ); 82 | 83 | // 横幅从屏幕外滑入 84 | double an3Begin = -dp(244); 85 | animationGiftNum_3 = Tween( 86 | begin: an3Begin, end: 0.0, 87 | ).animate( 88 | CurvedAnimation( 89 | parent: controller, 90 | curve: Interval( 91 | 0.65, 0.85, 92 | curve: Curves.easeIn 93 | ), 94 | ) 95 | ); 96 | 97 | controller.forward(); 98 | } 99 | 100 | @override 101 | void dispose() { 102 | controller?.dispose(); 103 | super.dispose(); 104 | } 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | return AnimatedBuilder( 109 | animation: controller, 110 | builder: (BuildContext context, Widget child) { 111 | return Positioned( 112 | left: dp(animationGiftNum_3.value), 113 | top: dp(45) + dp(80) * widget.queueLength, 114 | child: Stack( 115 | overflow: Overflow.visible, 116 | children: [ 117 | Container( 118 | height: dp(48), 119 | width: dp(244), 120 | decoration: BoxDecoration( 121 | image: DecorationImage( 122 | image: AssetImage('images/gift-banner.png'), 123 | fit: BoxFit.fill, 124 | ), 125 | ), 126 | child: Row( 127 | children: [ 128 | Container( 129 | width: dp(40), 130 | height: dp(40), 131 | margin: EdgeInsets.only(left: dp(4)), 132 | decoration: BoxDecoration( 133 | borderRadius: BorderRadius.circular(20), 134 | image: DecorationImage( 135 | image: NetworkImage(widget.giftInfo['avatar']), 136 | fit: BoxFit.fill, 137 | ), 138 | ), 139 | ), 140 | SizedBox( 141 | height: dp(48), 142 | width: dp(105), 143 | child: Padding( 144 | padding: EdgeInsets.all(dp(3)), 145 | child: Column( 146 | mainAxisAlignment: MainAxisAlignment.spaceAround, 147 | crossAxisAlignment: CrossAxisAlignment.start, 148 | children: [ 149 | Text( 150 | widget.giftInfo['nickName'], 151 | overflow: TextOverflow.ellipsis, 152 | style: TextStyle( 153 | color: Colors.white 154 | ), 155 | ), 156 | RichText( 157 | overflow: TextOverflow.ellipsis, 158 | text: TextSpan( 159 | style: TextStyle(color: Colors.yellow), 160 | children: [ 161 | TextSpan( 162 | text: '送出', 163 | ), 164 | TextSpan( 165 | text: widget.giftInfo['giftName'], 166 | style: TextStyle( 167 | fontWeight: FontWeight.bold, 168 | fontStyle: FontStyle.italic 169 | ), 170 | ), 171 | ] 172 | ), 173 | ), 174 | ], 175 | ), 176 | ), 177 | ), 178 | Padding(padding: EdgeInsets.only(right: dp(50))), 179 | Padding( 180 | padding: EdgeInsets.only(top: dp(10)), 181 | child: Transform.scale( 182 | scale: animationGiftNum_1.value >= 1.7 ? animationGiftNum_2.value : animationGiftNum_1.value, 183 | child: Image.asset( 184 | 'images/gift-x.png', 185 | height: dp(10), 186 | ), 187 | ), 188 | ), 189 | Transform.scale( 190 | scale: animationGiftNum_1.value >= 1.7 ? animationGiftNum_2.value : animationGiftNum_1.value, 191 | child: Image.asset( 192 | 'images/gift-1.png', 193 | height: dp(30), 194 | ), 195 | ), 196 | ], 197 | ), 198 | ), 199 | Positioned( 200 | left: dp(145), 201 | top: dp(-15), 202 | child: Image.network( 203 | widget.giftInfo['giftImg'], 204 | height: dp(50), 205 | ), 206 | ), 207 | ], 208 | ), 209 | ); 210 | }, 211 | child: null, 212 | ); 213 | } 214 | } -------------------------------------------------------------------------------- /lib/dy_room/chat.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 弹幕 & 礼物 3 | */ 4 | import 'dart:convert'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | import 'package:web_socket_channel/status.dart' as status; 9 | 10 | import '../base.dart'; 11 | import 'animate.dart' show Gift; 12 | 13 | class ChatWidgets extends StatefulWidget { 14 | @override 15 | _ChatWidgets createState() => _ChatWidgets(); 16 | } 17 | 18 | class _ChatWidgets extends State with DYBase { 19 | List msgData = []; // 弹幕消息列表 20 | List giftBannerView = []; // 礼物横幅列表JSON 21 | 22 | ScrollController _chatController = ScrollController(); // 弹幕区滚动Controller 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | SocketClient.create(); 28 | var channel = SocketClient.channel; 29 | // 接受弹幕、礼物消息(webSocket) 30 | channel.stream.listen((message) { 31 | message = json.decode(message); 32 | var sign = message[0]; 33 | var data = message[1]; 34 | if (sign == 'getChat') { 35 | if (mounted) 36 | setState(() { 37 | msgData.add(data); 38 | }); 39 | _chatController.jumpTo(_chatController.position.maxScrollExtent); 40 | } else if (sign == 'getGift') { 41 | var now = DateTime.now(); 42 | var obj = { 43 | 'stamp': now.millisecondsSinceEpoch, 44 | 'config': data 45 | }; 46 | Gift.add(giftBannerView, obj, 6500, (giftBannerViewNew) { 47 | if (mounted) 48 | setState(() { 49 | giftBannerView = giftBannerViewNew; 50 | }); 51 | }); 52 | } 53 | }); 54 | 55 | channel.sink.add('getChat'); 56 | channel.sink.add('getGift'); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | SocketClient.channel?.sink?.close(status.goingAway); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 68 | 69 | return Expanded( 70 | flex: 1, 71 | child: Container( 72 | decoration: BoxDecoration( 73 | border: Border( 74 | top: BorderSide(color: Color(0xffeeeeee), width: dp(1)), 75 | bottom: BorderSide(color: Color(0xffeeeeee), width: dp(1)) 76 | ), 77 | ), 78 | child: Stack( 79 | children: [ 80 | ListView( 81 | controller: _chatController, 82 | padding: EdgeInsets.all(dp(10)), 83 | children: _chatMsg(), 84 | ), 85 | ..._setGiftBannerView(), 86 | ], 87 | ), 88 | ), 89 | ); 90 | } 91 | 92 | List _chatMsg() { 93 | var msgList = List(); 94 | 95 | msgData.forEach((item) { 96 | var isAdmin = item['lv'] > 0; 97 | var msgBoart = RichText( 98 | strutStyle: StrutStyle( 99 | leading: 3.5 / 15, 100 | height: 1 101 | ), 102 | text: TextSpan( 103 | style: TextStyle( 104 | color: Color(0xff666666), 105 | fontSize: dp(15), 106 | ), 107 | children: [ 108 | WidgetSpan( 109 | child: isAdmin ? Padding( 110 | padding: EdgeInsets.only(right: dp(5)), 111 | child: Image.asset( 112 | 'images/lv/${item['lv']}.png', 113 | height: dp(18), 114 | ), 115 | ) : SizedBox() 116 | ), 117 | TextSpan( 118 | text: '${item['name']}: ', 119 | style: TextStyle( 120 | color: !isAdmin ? Colors.red : Color(0xff999999) 121 | ), 122 | ), 123 | TextSpan( 124 | text: item['text'], 125 | ), 126 | ] 127 | ), 128 | ); 129 | 130 | msgList.addAll([ 131 | msgBoart, 132 | Padding(padding: EdgeInsets.only(bottom: dp(5))) 133 | ]); 134 | }); 135 | 136 | return msgList; 137 | } 138 | 139 | List _setGiftBannerView() { 140 | List banner = []; 141 | 142 | giftBannerView.forEach((item) { 143 | banner.add(item['widget']); 144 | }); 145 | return banner; 146 | } 147 | 148 | } -------------------------------------------------------------------------------- /lib/dy_room/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 直播间弹幕 3 | */ 4 | import 'dart:ui'; 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 10 | import 'package:lottie/lottie.dart'; 11 | 12 | import '../base.dart'; 13 | import '../service.dart'; 14 | import 'player.dart'; 15 | import 'chat.dart'; 16 | 17 | class DyRoomPage extends StatefulWidget { 18 | final arguments; 19 | DyRoomPage({Key key, this.arguments}) : super(key: key); 20 | 21 | @override 22 | _DyRoomPageState createState() => _DyRoomPageState(arguments); 23 | } 24 | 25 | class _DyRoomPageState extends State with DYBase { 26 | Timer timerCloseLottlie; 27 | 28 | final _routeProp; // 首页路由跳转传递的参数 29 | _DyRoomPageState(this._routeProp); 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | timerCloseLottlie = Timer(Duration(seconds: 15), () { 35 | setState(() { 36 | timerCloseLottlie = null; 37 | }); 38 | }); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | timerCloseLottlie?.cancel(); 44 | super.dispose(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 50 | 51 | return AnnotatedRegion( 52 | value: SystemUiOverlayStyle( 53 | statusBarBrightness: Brightness.light, 54 | statusBarIconBrightness: Brightness.light, 55 | ), 56 | child: Scaffold( 57 | body: Stack( 58 | alignment: AlignmentDirectional.bottomCenter, 59 | children: [ 60 | Column( 61 | children: [ 62 | Container( 63 | height: DYBase.statusBarHeight, 64 | color: Color(0xff333333), 65 | ), 66 | PlayerWidgets(_routeProp), 67 | _nav(), 68 | ChatWidgets(), 69 | _bottom(), 70 | ], 71 | ), 72 | timerCloseLottlie != null ? Container( 73 | child: Lottie.network( 74 | '${DYBase.baseUrl}/static/fire.json', 75 | width: dp(200), 76 | fit: BoxFit.cover 77 | ), 78 | ) : SizedBox(), 79 | ], 80 | ) 81 | ), 82 | ); 83 | } 84 | 85 | Widget _nav() { 86 | return Row( 87 | mainAxisAlignment: MainAxisAlignment.spaceAround, 88 | children: [ 89 | Container( 90 | height: dp(40), 91 | padding: EdgeInsets.only(top: dp(9)), 92 | width: dp(60), 93 | decoration: BoxDecoration( 94 | border: Border(bottom: BorderSide(color: DYBase.defaultColor, width: dp(3))), 95 | ), 96 | child: Text( 97 | '弹幕', 98 | textAlign: TextAlign.center, 99 | style: TextStyle( 100 | color: DYBase.defaultColor, 101 | ), 102 | ), 103 | ), 104 | GestureDetector( 105 | onTap: () => DYdialog.alert(context, text: '正在建设中~'), 106 | child: Container( 107 | height: dp(40), 108 | padding: EdgeInsets.only(top: dp(9)), 109 | width: dp(60), 110 | child: Text( 111 | '主播', 112 | textAlign: TextAlign.center, 113 | ), 114 | ), 115 | ), 116 | ], 117 | ); 118 | } 119 | 120 | Widget _bottom() { 121 | return SizedBox( 122 | height: dp(50), 123 | child: Row( 124 | children: [ 125 | Expanded( 126 | flex: 1, 127 | child: Container( 128 | margin: EdgeInsets.only(left: dp(12), right: dp(12)), 129 | padding: EdgeInsets.only(left: dp(10), right: dp(10)), 130 | height: dp(36), 131 | decoration: BoxDecoration( 132 | color: Color(0xfff7f7f7), 133 | borderRadius: BorderRadius.all( 134 | Radius.circular(dp(8)), 135 | ), 136 | ), 137 | child: Row( 138 | children: [ 139 | Expanded( 140 | flex: 1, 141 | child: TextField( 142 | cursorColor: DYBase.defaultColor, 143 | cursorWidth: 1.5, 144 | style: TextStyle( 145 | color: Color(0xff333333), 146 | fontSize: 14.0, 147 | ), 148 | decoration: InputDecoration( 149 | border: OutlineInputBorder( 150 | borderSide: BorderSide.none 151 | ), 152 | contentPadding: EdgeInsets.all(0), 153 | hintText: '吐个槽呗~', 154 | ), 155 | ), 156 | ), 157 | GestureDetector( 158 | onTap: () => DYdialog.alert(context, text: '正在建设中~'), 159 | child: Container( 160 | width: dp(40), 161 | height: dp(26), 162 | padding: EdgeInsets.only(top: dp(2)), 163 | margin: EdgeInsets.only(left: dp(10)), 164 | decoration: BoxDecoration( 165 | borderRadius: BorderRadius.all(Radius.circular(dp(4))), 166 | gradient: LinearGradient( 167 | begin: Alignment(-1.2, 0.0), 168 | end: Alignment(0.2, 0.0), 169 | colors: [ 170 | Color(0xffff4e00), 171 | Color(0xffff8b00), 172 | ], 173 | ), 174 | ), 175 | child: Text( 176 | '发送', 177 | style: TextStyle( 178 | color: Colors.white, 179 | ), 180 | textAlign: TextAlign.center, 181 | ), 182 | ), 183 | ), 184 | ], 185 | ), 186 | ), 187 | ), 188 | GestureDetector( 189 | onTap: () => DYdialog.alert(context, text: '正在建设中~'), 190 | child: Image.asset( 191 | 'images/gift.png', 192 | height: dp(36), 193 | ), 194 | ), 195 | Padding(padding: EdgeInsets.only(right: dp(12))) 196 | ], 197 | ), 198 | ); 199 | } 200 | } -------------------------------------------------------------------------------- /lib/dy_room/player.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 视频播放器 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | import 'package:video_player/video_player.dart'; 7 | 8 | import '../base.dart'; 9 | 10 | class PlayerWidgets extends StatefulWidget { 11 | final routeProp; 12 | PlayerWidgets(this.routeProp); 13 | 14 | @override 15 | _PlayerWidgets createState() => _PlayerWidgets(); 16 | } 17 | 18 | class _PlayerWidgets extends State with DYBase { 19 | Future _initializeVideoPlayerFuture; 20 | VideoPlayerController _videoPlayerController; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _videoPlayerController = VideoPlayerController.network('${DYBase.baseUrl}/static/suen.mp4') 26 | ..setLooping(true); 27 | 28 | _initializeVideoPlayerFuture = _videoPlayerController.initialize().then((_) { 29 | setState(() {}); 30 | }); 31 | _videoPlayerController.play(); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | _videoPlayerController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | ScreenUtil.instance = ScreenUtil(width: DYBase.dessignWidth)..init(context); 43 | double screenWidth = MediaQuery.of(context).size.width; 44 | double playerHeight = screenWidth * 540 / 960; 45 | 46 | return Container( 47 | width: screenWidth, 48 | height: playerHeight, 49 | color: Color(0xff333333), 50 | child: FutureBuilder( 51 | future: _initializeVideoPlayerFuture, 52 | builder: (context, snapshot) { 53 | if (snapshot.hasError) { 54 | print(snapshot.error); 55 | } 56 | if (snapshot.connectionState == ConnectionState.done) { 57 | return AspectRatio( 58 | aspectRatio: _videoPlayerController.value.aspectRatio, 59 | child: VideoPlayer(_videoPlayerController), 60 | ); 61 | } else { 62 | return Stack( 63 | alignment: AlignmentDirectional.center, 64 | children: [ 65 | Positioned( 66 | child: Image.network( 67 | widget.routeProp['roomSrc'], 68 | height: playerHeight, 69 | ), 70 | ), 71 | Positioned( 72 | child: Image.asset( 73 | 'images/play.png', 74 | height: dp(60), 75 | ), 76 | ), 77 | ], 78 | ); 79 | } 80 | }, 81 | ), 82 | ); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /lib/httpUrl.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 网络服务 3 | */ 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_http_cache/dio_http_cache.dart'; 6 | import 'package:web_socket_channel/io.dart'; 7 | 8 | import 'base.dart'; 9 | 10 | // 接口URL 11 | abstract class API { 12 | static const nav = '/dy/flutter/nav'; // 首页顶部导航 13 | static const swiper = '/dy/flutter/swiper'; // 首页轮播图 14 | static const broadcast = '/dy/flutter/broadcast'; // 首页推荐广播 15 | static const liveData = '/dy/flutter/liveData'; // 首页直播视频列表 16 | static const lotteryConfig = '/dy/flutter/lotteryConfig'; // 抽奖配置信息 17 | static const lotteryResult = '/dy/flutter/lotteryResult'; // 点击抽奖结果 18 | static const yubaList = '/dy/flutter/yubaList'; // 鱼吧列表 19 | static const areaList = '/static/areaTel.json'; // 国家地区号码静态文件 20 | } 21 | 22 | // http请求 23 | final dioManager = DioCacheManager( 24 | CacheConfig( 25 | skipDiskCache: true 26 | ) 27 | ); 28 | final httpClient = Dio(BaseOptions( 29 | baseUrl: DYBase.baseUrl, 30 | responseType: ResponseType.json, 31 | connectTimeout: 5000, 32 | receiveTimeout: 3000, 33 | ))..interceptors.add( 34 | dioManager.interceptor, 35 | ); 36 | 37 | // 直播房间webSocket 38 | class SocketClient { 39 | static IOWebSocketChannel channel; 40 | 41 | SocketClient.create() { 42 | SocketClient.channel = IOWebSocketChannel.connect('ws://${DYBase.baseHost}:${DYBase.basePort}/socket/dy/flutter'); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /lib/io.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 文件操作类 3 | */ 4 | import 'dart:convert'; 5 | import 'dart:io'; 6 | 7 | import 'package:path_provider/path_provider.dart'; 8 | import 'package:fluttertoast/fluttertoast.dart'; 9 | 10 | // txt缓存文件读写清 11 | class DYio { 12 | // 获取缓存目录 13 | static Future getTempPath() async { 14 | var tempDir = await getTemporaryDirectory(); 15 | return tempDir.path; 16 | } 17 | 18 | // 设置缓存 19 | static void setTempFile(fileName, [str = '']) async { 20 | String tempPath = await getTempPath(); 21 | await File('$tempPath/$fileName.txt').writeAsString(str); 22 | } 23 | 24 | // 读取缓存 25 | static Future getTempFile(fileName) async { 26 | String tempPath = await getTempPath(); 27 | try { 28 | String contents = await File('$tempPath/$fileName.txt').readAsString(); 29 | return jsonDecode(contents); 30 | } catch(e) { 31 | print('$fileName:缓存不存在'); 32 | } 33 | } 34 | 35 | // 清缓存 36 | static void clearCache() async { 37 | try { 38 | Directory tempDir = await getTemporaryDirectory(); 39 | await _delDir(tempDir); 40 | Fluttertoast.showToast(msg: '清除缓存成功'); 41 | } catch (e) { 42 | Fluttertoast.showToast(msg: '清除缓存失败'); 43 | } finally {} 44 | } 45 | 46 | // 递归方式删除目录 47 | static Future _delDir(FileSystemEntity file) async { 48 | try { 49 | if (file is Directory) { 50 | final List children = file.listSync(); 51 | for (final FileSystemEntity child in children) { 52 | await _delDir(child); 53 | } 54 | } 55 | await file.delete(); 56 | } catch (e) { 57 | print(e); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 斗鱼APP 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 8 | 9 | import 'base.dart'; 10 | import 'dy_init/index.dart'; 11 | import 'dy_index/index.dart'; 12 | import 'dy_room/index.dart'; 13 | import 'dy_login/index.dart'; 14 | import 'develop/index.dart'; 15 | 16 | class DyApp extends StatelessWidget { 17 | // 路由路径匹配 18 | Route _getRoute(RouteSettings settings) { 19 | Map routes = { 20 | '/': (BuildContext context) => SplashPage(), 21 | '/index': (BuildContext context) => DyIndexPage(), 22 | '/room': (BuildContext context) => DyRoomPage(arguments: settings.arguments), 23 | '/login': (BuildContext context) => DyLoginPage(arguments: settings.arguments), 24 | '/webView': (BuildContext context) { // webView全屏容器 25 | Map arg = settings.arguments; 26 | return WebviewScaffold( 27 | url: arg['url'], 28 | appBar: AppBar( 29 | iconTheme: IconThemeData(color: Colors.white), 30 | backgroundColor: DYBase.defaultColor, 31 | brightness: Brightness.dark, 32 | textTheme: TextTheme( 33 | headline6: TextStyle( 34 | color: Colors.white, 35 | fontSize: 18, 36 | ), 37 | ), 38 | title: Text(arg['title']), 39 | ), 40 | ); 41 | }, 42 | '/develop': (BuildContext context) => DevelopPage(), 43 | }; 44 | var widget = routes[settings.name]; 45 | 46 | if (widget != null) { 47 | return MaterialPageRoute( 48 | settings: settings, 49 | builder: widget, 50 | ); 51 | } 52 | 53 | return null; 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return MultiBlocProvider( // 多个Bloc注册 59 | providers: [ 60 | BlocProvider( 61 | create: (context) => BlocObj.counter, 62 | ), 63 | BlocProvider( 64 | create: (context) => BlocObj.index, 65 | ), 66 | ], 67 | child: MaterialApp( 68 | title: '斗鱼', 69 | theme: ThemeData( 70 | scaffoldBackgroundColor: Color.fromARGB(255, 255, 255, 255), 71 | primarySwatch: Colors.orange, 72 | textTheme: TextTheme( 73 | bodyText1: TextStyle(color: Colors.black), 74 | ), 75 | appBarTheme: AppBarTheme( 76 | textTheme: TextTheme( 77 | headline6: TextStyle( 78 | color: Colors.black, 79 | fontSize: 18, 80 | ), 81 | bodyText1: TextStyle(color: Colors.black), 82 | ), 83 | ) 84 | // splashFactory: NoSplashFactory() 85 | ), 86 | onGenerateRoute: _getRoute, 87 | ), 88 | ); 89 | } 90 | } 91 | 92 | void main() { 93 | WidgetsFlutterBinding.ensureInitialized(); 94 | // 强制竖屏 95 | SystemChrome.setPreferredOrientations([ 96 | DeviceOrientation.portraitUp, 97 | DeviceOrientation.portraitDown 98 | ]); 99 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent)); 100 | runApp(DyApp()); 101 | } -------------------------------------------------------------------------------- /lib/rx.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: RxDart全局消息通信封装处理 3 | */ 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | class _Rx { 7 | final _subject = PublishSubject(); 8 | Map>> _signMapping = {}; 9 | 10 | _Rx() { 11 | _subject.listen((addArg) { 12 | String sign = addArg[0]; 13 | var data = addArg[1]; 14 | if (_signMapping[sign] != null) { 15 | _signMapping[sign].forEach((name, callBackList) { 16 | callBackList.forEach((callBack) { 17 | callBack(data); 18 | }); 19 | }); 20 | } 21 | }); 22 | } 23 | void push(String sign, { dynamic data }) { 24 | _subject.add([sign, data]); 25 | } 26 | // 参数name为销毁监听标识,当同一个sign被多个模块监听时,销毁方法unSubscribe只会销毁对应name的监听,不传入会销毁当前sign的所有注册回调 27 | void subscribe(String sign, void Function(dynamic data) callBack, { String name = '' }) { 28 | if (_signMapping[sign] == null) { 29 | _signMapping[sign] = { 30 | name: [callBack] 31 | }; 32 | } else if (_signMapping[sign][name] == null) { 33 | _signMapping[sign][name] = [callBack]; 34 | } else { 35 | _signMapping[sign][name].add(callBack); 36 | } 37 | } 38 | void unSubscribe(String sign, { String name = '' }) { 39 | if (_signMapping[sign] != null && _signMapping[sign][name] is List) { 40 | _signMapping[sign].remove(name); 41 | } 42 | } 43 | } 44 | 45 | final rx = _Rx(); -------------------------------------------------------------------------------- /lib/service.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @discripe: 业务层方法 3 | */ 4 | import 'dart:io'; 5 | import 'dart:math'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | import 'package:dio_http_cache/dio_http_cache.dart'; 10 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 11 | import 'package:lottie/lottie.dart'; 12 | 13 | import 'bloc.dart'; 14 | import 'base.dart'; 15 | import './dy_dialog/login.dart'; 16 | import './dy_dialog/loading.dart'; 17 | 18 | abstract class DYservice { 19 | // 获取直播间列表 20 | static Future getLiveData(context, [ pageIndex ]) async { 21 | final counterBloc = BlocProvider.of(context); 22 | int livePageIndex = BlocObj.counter.state; 23 | 24 | var res = await httpClient.get( 25 | API.liveData, 26 | queryParameters: { 27 | 'page': pageIndex == null ? livePageIndex : pageIndex 28 | }, 29 | options: livePageIndex == 1 ? buildCacheOptions( 30 | Duration(minutes: 30), 31 | ) : null, 32 | ); 33 | 34 | counterBloc.add(CounterEvent.increment); 35 | return res.data['data']['list']; 36 | } 37 | 38 | // 格式化数值 39 | static String formatNum(int number) { 40 | if (number > 10000) { 41 | var str = DYservice._formatNum(number / 10000, 1); 42 | if (str.split('.')[1] == '0') { 43 | str = str.split('.')[0]; 44 | } 45 | return str + '万'; 46 | } 47 | return number.toString(); 48 | } 49 | static String _formatNum(double number, int postion) { 50 | if((number.toString().length - number.toString().lastIndexOf(".") - 1) < postion) { 51 | // 小数点后有几位小数 52 | return ( number.toStringAsFixed(postion).substring(0, number.toString().lastIndexOf(".")+postion + 1).toString()); 53 | } else { 54 | return ( number.toString().substring(0, number.toString().lastIndexOf(".") + postion + 1).toString()); 55 | } 56 | } 57 | 58 | // 格式化时间 59 | static String formatTime(int timeSec) { 60 | var date = DateTime.fromMillisecondsSinceEpoch(timeSec * 1000); 61 | var now = DateTime.now(); 62 | var yesterday = DateTime.fromMillisecondsSinceEpoch(now.millisecondsSinceEpoch - 24 * 60 * 60 * 1000); 63 | 64 | if (date.year == now.year && date.month == now.month && date.day == now.day) { 65 | return '今天${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; 66 | } else if (date.year == yesterday.year && date.month == yesterday.month && date.day == yesterday.day) { 67 | return '昨天${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; 68 | } 69 | return '${date.year.toString()}-${date.month.toString().padLeft(2,'0')}-${date.day.toString().padLeft(2,'0')} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; 70 | } 71 | 72 | // 生成随机串 73 | static dynamic randomBit(int len, { String type }) { 74 | String character = type == 'num' ? '0123456789' : 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'; 75 | String left = ''; 76 | for (var i = 0; i < len; i++) { 77 | left = left + character[Random().nextInt(character.length)]; 78 | } 79 | return type == 'num' ? int.parse(left) : left; 80 | } 81 | } 82 | 83 | abstract class DYdialog { 84 | // 默认弹窗alert 85 | static void alert(context, { 86 | @required String text, String title = '提示', String yes = '确定', 87 | Function yesCallBack 88 | }) { 89 | showDialog( 90 | context: context, 91 | barrierDismissible: false, 92 | builder: (BuildContext context) { 93 | return AlertDialog( 94 | title: Text(title), 95 | content: SingleChildScrollView( 96 | child: ListBody( 97 | children: [ 98 | Text(text), 99 | ], 100 | ), 101 | ), 102 | actions: [ 103 | FlatButton( 104 | child: Text(yes), 105 | onPressed: () { 106 | if (yesCallBack != null) yesCallBack(); 107 | Navigator.of(context).pop(); 108 | }, 109 | ), 110 | ], 111 | ); 112 | }, 113 | ).then((val) {}); 114 | } 115 | 116 | // loadingDialog 117 | static void showLoading(context, { 118 | String title = '正在加载...' 119 | }) { 120 | showDialog( 121 | context: context, 122 | barrierDismissible: false, 123 | builder: (BuildContext context) { 124 | return LoadingDialog( 125 | text: title, 126 | ); 127 | } 128 | ); 129 | } 130 | 131 | // login 132 | static void showLogin(context) { 133 | showDialog( 134 | context: context, 135 | barrierDismissible: false, 136 | builder: (BuildContext context) { 137 | return LoginDialog(); 138 | } 139 | ); 140 | } 141 | } 142 | 143 | // 禁用点击水波纹 144 | class NoSplashFactory extends InteractiveInkFeatureFactory { 145 | @override 146 | InteractiveInkFeature create({ 147 | MaterialInkController controller, 148 | RenderBox referenceBox, 149 | Offset position, 150 | Color color, 151 | TextDirection textDirection, 152 | bool containedInkWell = false, 153 | rectCallback, 154 | BorderRadius borderRadius, 155 | ShapeBorder customBorder, 156 | double radius, 157 | onRemoved 158 | }) { 159 | return NoSplash( 160 | controller: controller, 161 | referenceBox: referenceBox, 162 | ); 163 | } 164 | } 165 | 166 | class NoSplash extends InteractiveInkFeature { 167 | NoSplash({ 168 | @required MaterialInkController controller, 169 | @required RenderBox referenceBox, 170 | }) : super( 171 | controller: controller, 172 | referenceBox: referenceBox, 173 | ); 174 | 175 | @override 176 | void paintFeature(Canvas canvas, Matrix4 transform) {} 177 | } 178 | 179 | // 去除安卓滚动视图水波纹 180 | class DyBehaviorNull extends ScrollBehavior { 181 | @override 182 | Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { 183 | if (Platform.isAndroid || Platform.isFuchsia) { 184 | return child; 185 | } else { 186 | return super.buildViewportChrome(context,child,axisDirection); 187 | } 188 | } 189 | } 190 | 191 | // 下拉刷新头部、底部组件 192 | class DYrefreshHeader extends StatelessWidget with DYBase { 193 | @override 194 | Widget build(BuildContext context) { 195 | final refreshing = Lottie.network( 196 | '${DYBase.baseUrl}/static/if_refresh.json', 197 | height: dp(50) 198 | ); 199 | 200 | return CustomHeader( 201 | refreshStyle: RefreshStyle.Follow, 202 | builder: (BuildContext context,RefreshStatus status) { 203 | bool swimming = (status == RefreshStatus.refreshing || status == RefreshStatus.completed); 204 | return Container( 205 | height: dp(50), 206 | child: Stack( 207 | alignment: AlignmentDirectional.center, 208 | children: [ 209 | swimming ? SizedBox() : Image.asset( 210 | 'images/fun_home_pull_down.png', 211 | height: dp(50), 212 | ), 213 | Offstage( 214 | offstage: !swimming, 215 | child: refreshing, 216 | ), 217 | ] 218 | ) 219 | ); 220 | } 221 | ); 222 | } 223 | } 224 | 225 | class DYrefreshFooter extends StatelessWidget with DYBase { 226 | final bgColor; 227 | DYrefreshFooter({this.bgColor}); 228 | 229 | @override 230 | Widget build(BuildContext context) { 231 | final height = dp(50); 232 | 233 | return CustomFooter( 234 | height: height, 235 | builder: (BuildContext context,LoadStatus mode){ 236 | final textStyle = TextStyle( 237 | color: Color(0xffA7A7A7), 238 | fontSize: dp(13), 239 | ); 240 | Widget body; 241 | Widget loading = Row( 242 | mainAxisAlignment: MainAxisAlignment.center, 243 | children: [ 244 | Lottie.network( 245 | '${DYBase.baseUrl}/static/loading.json', 246 | height: dp(34) 247 | ), 248 | Text( 249 | '用力加载中...', 250 | style: textStyle, 251 | ), 252 | ], 253 | ); 254 | if(mode==LoadStatus.idle){ 255 | body = loading; 256 | } 257 | else if(mode==LoadStatus.loading){ 258 | body = loading; 259 | } 260 | else if(mode == LoadStatus.failed){ 261 | body = Text( 262 | '网络出错啦 😭', 263 | style: textStyle, 264 | ); 265 | } 266 | else if(mode == LoadStatus.canLoading){ 267 | body = loading; 268 | } 269 | else{ 270 | body = Text( 271 | '我是有底线的 😭', 272 | style: textStyle, 273 | ); 274 | } 275 | return Container( 276 | color: bgColor, 277 | height: height, 278 | child: Center(child:body), 279 | ); 280 | }, 281 | ); 282 | } 283 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dy_flutter 2 | description: Douyu App of flutter. 3 | version: 1.2.0 4 | 5 | environment: 6 | sdk: ">=2.7.0 <3.0.0" 7 | flutter: ">=1.22.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | fluttertoast: ^3.1.0 14 | path_provider: ^1.1.2 15 | flutter_screenutil: ^0.5.3 16 | flutter_swiper: ^1.1.6 17 | flutter_bloc: ^6.1.1 18 | rxdart: ^0.24.1 19 | equatable: ^0.3.0 20 | dio: ^3.0.0 21 | dio_http_cache: ^0.2.11 22 | video_player: ^1.0.1 23 | image_picker: ^0.6.7 24 | flutter_webview_plugin: ^0.3.11 25 | pull_to_refresh: ^1.6.4 26 | web_socket_channel: ^1.0.15 27 | shared_preferences: ^0.5.12 28 | cached_network_image: ^2.1.0 29 | photo_view: ^0.9.0 30 | # barcode_scan: ^3.0.1 31 | flutter_local_notifications: ^4.0.1 32 | lottie: ^0.7.0 33 | 34 | dev_dependencies: 35 | mockito: ^4.1.0 36 | flutter_test: 37 | sdk: flutter 38 | 39 | flutter: 40 | uses-material-design: true 41 | assets: 42 | - images/pic-default.jpg 43 | - images/back.webp 44 | - images/default-avatar.webp 45 | - images/shayuniang.png 46 | - images/search.webp 47 | - images/cfk.webp 48 | - images/hot.png 49 | - images/member.png 50 | - images/play.png 51 | - images/gift.png 52 | - images/gift-banner.png 53 | - images/gift-x.png 54 | - images/gift-1.png 55 | - images/float-icon.webp 56 | - images/init_logo.webp 57 | - images/init_icon.png 58 | - images/show-area.webp 59 | - images/broadcast.webp 60 | - images/fun_home_pull_down.png 61 | - images/cjf.webp 62 | - images/dg.webp 63 | - images/dg0.webp 64 | - images/cqe.webp 65 | - images/num/0.webp 66 | - images/num/1.webp 67 | - images/num/2.webp 68 | - images/num/3.webp 69 | - images/num/4.webp 70 | - images/num/5.webp 71 | - images/num/6.webp 72 | - images/num/7.webp 73 | - images/num/8.webp 74 | - images/num/9.webp 75 | - images/cate/tab-0.webp 76 | - images/cate/tab-1.webp 77 | - images/cate/tab-2.webp 78 | - images/cate/tab-3.webp 79 | - images/cate/tab-4.webp 80 | - images/cate/tab-5.webp 81 | - images/cate/tab-6.webp 82 | - images/cate/tab-7.webp 83 | - images/cate/tab-8.webp 84 | - images/cate/tab-9.webp 85 | - images/cate/tab-10.webp 86 | 87 | - images/login/syn.webp 88 | - images/login/qq.webp 89 | - images/login/wx.webp 90 | - images/login/weibo.webp 91 | - images/login/close.webp 92 | - images/login/safe.webp 93 | - images/login/member.webp 94 | - images/login/lock.webp 95 | 96 | - images/head/dylogo.png 97 | - images/head/camera.webp 98 | - images/head/chat.webp 99 | - images/head/game.webp 100 | - images/head/history.webp 101 | 102 | - images/lv/3.png 103 | - images/lv/30.png 104 | - images/lv/50.png 105 | - images/lv/80.png 106 | 107 | - images/nav/nav-11.jpg 108 | - images/nav/nav-21.jpg 109 | - images/nav/nav-31.jpg 110 | - images/nav/nav-41.jpg 111 | - images/nav/nav-51.jpg 112 | - images/nav/nav-12.jpg 113 | - images/nav/nav-22.jpg 114 | - images/nav/nav-32.jpg 115 | - images/nav/nav-42.jpg 116 | - images/nav/nav-52.jpg 117 | 118 | - images/bar/hot-chat.jpg 119 | - images/bar/day-title.png 120 | - images/bar/hot-title.png 121 | - images/bar/pull-icon.webp 122 | - images/bar/usefulSelect.webp 123 | - images/bar/girl.webp 124 | - images/bar/boy.webp 125 | - images/bar/tab-0.png 126 | - images/bar/tab-1.png 127 | - images/bar/tab-2.png 128 | - images/bar/chat-add.jpg 129 | - images/bar/chat-share.jpg 130 | - images/bar/chat-star.jpg 131 | - images/bar/chat-star-act.webp 132 | -------------------------------------------------------------------------------- /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:dy_flutter/main.dart'; 12 | 13 | void main() { 14 | } 15 | --------------------------------------------------------------------------------