├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── README-ZH.md ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── shuqi │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── img ├── 2.0x │ ├── actionbar_checkin.png │ ├── actionbar_search.png │ ├── aliuser_title_back_normal.9.png │ ├── arrow_right.png │ ├── bookshelf_add.png │ ├── bookshelf_bg.png │ ├── bookshelf_cloud_0.png │ ├── bookshelf_cloud_1.png │ ├── bookshelf_cloud_2.png │ ├── bookshelf_cloud_3.png │ ├── bookshelf_continue_read.png │ ├── detail_chapter.png │ ├── detail_down.png │ ├── detail_fold_bg.png │ ├── detail_latest.png │ ├── detail_star.png │ ├── detail_star_half.png │ ├── detail_up.png │ ├── detail_write_comment.png │ ├── home_search.png │ ├── home_tip.png │ ├── icon_menu_catalog.png │ ├── icon_menu_share.png │ ├── me_action.png │ ├── me_buy.png │ ├── me_comment.png │ ├── me_coupon.png │ ├── me_date.png │ ├── me_favorite.png │ ├── me_feedback.png │ ├── me_gift.png │ ├── me_night.png │ ├── me_record.png │ ├── me_setting.png │ ├── me_theme.png │ ├── me_vip.png │ ├── me_wallet.png │ ├── menu_category.png │ ├── menu_complete.png │ ├── menu_publish.png │ ├── menu_rank.png │ ├── menu_vip.png │ ├── placeholder_avatar.png │ ├── pub_back_gray.png │ ├── pub_back_white.png │ ├── read_bg.png │ ├── read_icon_brightness.png │ ├── read_icon_catalog.png │ ├── read_icon_chapter_next.png │ ├── read_icon_chapter_previous.png │ ├── read_icon_font.png │ ├── read_icon_more.png │ ├── read_icon_setting.png │ ├── read_icon_vip.png │ ├── read_icon_voice.png │ ├── read_more_detail.png │ ├── reader_battery.png │ ├── tab_bookshelf_n.png │ ├── tab_bookshelf_p.png │ ├── tab_bookstore_n.png │ ├── tab_bookstore_p.png │ ├── tab_me_n.png │ ├── tab_me_p.png │ ├── tab_writer_n.png │ └── tab_writer_p.png ├── actionbar_checkin.png ├── actionbar_search.png ├── aliuser_title_back_normal.9.png ├── arrow_right.png ├── bookshelf_add.png ├── bookshelf_bg.png ├── bookshelf_cloud_0.png ├── bookshelf_cloud_1.png ├── bookshelf_cloud_2.png ├── bookshelf_cloud_3.png ├── bookshelf_continue_read.png ├── detail_chapter.png ├── detail_down.png ├── detail_fold_bg.png ├── detail_latest.png ├── detail_star.png ├── detail_star_half.png ├── detail_up.png ├── detail_write_comment.png ├── home_search.png ├── home_tip.png ├── icon_menu_catalog.png ├── icon_menu_share.png ├── me_action.png ├── me_buy.png ├── me_comment.png ├── me_coupon.png ├── me_date.png ├── me_favorite.png ├── me_feedback.png ├── me_gift.png ├── me_night.png ├── me_record.png ├── me_setting.png ├── me_theme.png ├── me_vip.png ├── me_wallet.png ├── menu_category.png ├── menu_complete.png ├── menu_publish.png ├── menu_rank.png ├── menu_vip.png ├── placeholder_avatar.png ├── pub_back_gray.png ├── pub_back_white.png ├── read_bg.png ├── read_icon_brightness.png ├── read_icon_catalog.png ├── read_icon_chapter_next.png ├── read_icon_chapter_previous.png ├── read_icon_font.png ├── read_icon_more.png ├── read_icon_setting.png ├── read_icon_vip.png ├── read_icon_voice.png ├── read_more_detail.png ├── reader_battery.png ├── tab_bookshelf_n.png ├── tab_bookshelf_p.png ├── tab_bookstore_n.png ├── tab_bookstore_p.png ├── tab_me_n.png ├── tab_me_p.png ├── tab_writer_n.png └── tab_writer_p.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── 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 │ │ ├── img_app-1.png │ │ └── img_app.png │ ├── Contents.json │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@3x.png │ │ └── img_loading.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── app │ ├── app_navigator.dart │ ├── app_scene.dart │ ├── constant.dart │ ├── request.dart │ ├── root_scene.dart │ ├── sq_color.dart │ └── user_manager.dart ├── bookshelf │ ├── bookshelf_cloud_widget.dart │ ├── bookshelf_header.dart │ ├── bookshelf_item_view.dart │ └── bookshelf_scene.dart ├── global.dart ├── home │ ├── home_banner.dart │ ├── home_list_view.dart │ ├── home_menu.dart │ ├── home_model.dart │ ├── home_novel_cover_view.dart │ ├── home_scene.dart │ ├── home_section_view.dart │ ├── novel_cell.dart │ ├── novel_first_hybird_card.dart │ ├── novel_four_grid_view.dart │ ├── novel_grid_item.dart │ ├── novel_normal_card.dart │ └── novel_second_hybird_card.dart ├── main.dart ├── me │ ├── login_page.dart │ ├── me_cell.dart │ ├── me_header.dart │ ├── me_page.dart │ ├── setting_page.dart │ └── web_page.dart ├── model │ ├── article.dart │ ├── chapter.dart │ ├── novel.dart │ └── novel_comment.dart ├── novel_detail │ ├── novel_comment_cell.dart │ ├── novel_detail_cell.dart │ ├── novel_detail_header.dart │ ├── novel_detail_page.dart │ ├── novel_detail_recommend_view.dart │ ├── novel_detail_toolbar.dart │ └── novel_summary_view.dart ├── public.dart ├── reader │ ├── article_provider.dart │ ├── battery_view.dart │ ├── reader_config.dart │ ├── reader_menu.dart │ ├── reader_overlayer.dart │ ├── reader_page.dart │ ├── reader_page_agent.dart │ ├── reader_utils.dart │ └── reader_view.dart ├── utility │ ├── event_bus.dart │ └── styles.dart └── widget │ ├── novel_cover_image.dart │ └── novel_cover_view.dart ├── mock ├── article_1000.json ├── article_1001.json ├── article_1002.json ├── article_1003.json ├── article_1004.json ├── article_1005.json ├── article_1006.json ├── bookshelf.json ├── catalog.json ├── home_cartoon.json ├── home_excellent.json ├── home_female.json ├── home_male.json ├── login.json ├── novel_comment.json ├── novel_detail.json ├── novel_recommend.json └── sms.json ├── pubspec.lock ├── pubspec.yaml └── screenshot ├── android_0.png ├── android_1.png ├── android_2.png ├── ios_0.png ├── ios_1.png └── ios_2.png /.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 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: 4d7946a68d26794349189cf21b3f68cc6fe61dcb 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": "shuqi", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "shuqi (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "shuqi (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | }, 24 | ] 25 | } -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # 高仿书旗小说 Flutter 版 2 | 3 | Language: [English](README.md) | 中文 4 | 5 | 如果你对SwiftUI也感兴趣,欢迎关注[swiftui-shuqi-reader](https://github.com/huanxsd/swiftui-shuqi-reader) 6 | 7 | ## iOS截图 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ## Android截图 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ## 简介 24 | 25 | 这是一个用Flutter写的书旗小说客户端。 26 | 27 | 主要实现的功能有: 28 | * 框架:App常用的Tab框架,UI根据系统字体设定自适应; 29 | * 书城:3D轮播、菜单、五种通过API配置的卡片样式; 30 | * 小说详情:导航栏样式切换动效、高斯模糊效果、文字伸缩; 31 | * 书架:顶部云彩动效、导航栏样式切换动效、书籍展示; 32 | * 我的:未登录/登录状态切换,菜单展示; 33 | * 登录:获取验证码、用户登录、用户状态缓存、用户注销; 34 | * 阅读:文章加载、横向翻页、菜单展示。 35 | 36 | 所有功能都是用Dart写的,iOS和Android的代码复用率达到了100% 37 | 38 | 我试着让这个Demo的结构尽量接近实际项目,同时使用比较简单方式去实现功能。这样可以让刚接触Flutter的人更够容易理解代码。 39 | 40 | App中的网络请求均通过一个名为**Request**的工具类。在Request内部,通过**本地mock**方式,获取模拟数据。 41 | 42 | ## 第三方依赖 43 | 44 | * [je_kit](https://github.com/jayden320/je_kit) 45 | 46 | ## 安装 47 | 48 | 1. **Clone the repo** 49 | 50 | ``` 51 | $ git clone https://github.com/huanxsd/flutter_shuqi.git 52 | $ cd flutter_shuqi 53 | ``` 54 | 55 | 2. **Running:** 56 | 57 | ``` 58 | $ flutter run 59 | ``` 60 | 61 | ## 联系 62 | 63 | 如果有任何建议,可以在简书上给我留言 64 | [简书](https://www.jianshu.com/p/aed5e319b313) 65 | 66 | ## License 67 | 68 | MIT 69 | 70 | ## 最后 71 | 72 | 如果你喜欢这个项目,欢迎给我一个star。我将持续更新这个项目 :) 73 | 74 | 也欢迎在[Github主页](https://github.com/huanxsd)关注我的其他项目。 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 高仿书旗小说 Flutter 版 2 | 3 | Language: English | [中文](README-ZH.md) 4 | 5 | If you are also interested in SwiftUI, welcome to follow [swiftui-shuqi-reader](https://github.com/huanxsd/swiftui-shuqi-reader) 6 | 7 | ## screen shot for iOS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ## screen shot for Android 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ## Setup 24 | 25 | 1. **Clone the repo** 26 | 27 | ``` 28 | $ git clone https://github.com/huanxsd/flutter_shuqi.git 29 | $ cd flutter_shuqi 30 | ``` 31 | 32 | 2. **Running:** 33 | 34 | ``` 35 | $ flutter run 36 | ``` 37 | 38 | ## Dependency 39 | 40 | * [je_kit](https://github.com/jayden320/je_kit) 41 | 42 | ## Contact 43 | 44 | If you have any suggestions, leave a message here 45 | [简书](https://www.jianshu.com/p/aed5e319b313) 46 | 47 | ## License 48 | 49 | 50 | ``` 51 | MIT License 52 | 53 | Copyright (c) 2018 Jpeng 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a copy 56 | of this software and associated documentation files (the "Software"), to deal 57 | in the Software without restriction, including without limitation the rights 58 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 59 | copies of the Software, and to permit persons to whom the Software is 60 | furnished to do so, subject to the following conditions: 61 | 62 | The above copyright notice and this permission notice shall be included in all 63 | copies or substantial portions of the Software. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 66 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 67 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 68 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 69 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 70 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 71 | SOFTWARE. 72 | ``` -------------------------------------------------------------------------------- /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 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.shuqi" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/shuqi/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.shuqi 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /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 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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.7-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /img/2.0x/actionbar_checkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/actionbar_checkin.png -------------------------------------------------------------------------------- /img/2.0x/actionbar_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/actionbar_search.png -------------------------------------------------------------------------------- /img/2.0x/aliuser_title_back_normal.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/aliuser_title_back_normal.9.png -------------------------------------------------------------------------------- /img/2.0x/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/arrow_right.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_add.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_bg.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_cloud_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_cloud_0.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_cloud_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_cloud_1.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_cloud_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_cloud_2.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_cloud_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_cloud_3.png -------------------------------------------------------------------------------- /img/2.0x/bookshelf_continue_read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/bookshelf_continue_read.png -------------------------------------------------------------------------------- /img/2.0x/detail_chapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_chapter.png -------------------------------------------------------------------------------- /img/2.0x/detail_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_down.png -------------------------------------------------------------------------------- /img/2.0x/detail_fold_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_fold_bg.png -------------------------------------------------------------------------------- /img/2.0x/detail_latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_latest.png -------------------------------------------------------------------------------- /img/2.0x/detail_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_star.png -------------------------------------------------------------------------------- /img/2.0x/detail_star_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_star_half.png -------------------------------------------------------------------------------- /img/2.0x/detail_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_up.png -------------------------------------------------------------------------------- /img/2.0x/detail_write_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/detail_write_comment.png -------------------------------------------------------------------------------- /img/2.0x/home_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/home_search.png -------------------------------------------------------------------------------- /img/2.0x/home_tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/home_tip.png -------------------------------------------------------------------------------- /img/2.0x/icon_menu_catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/icon_menu_catalog.png -------------------------------------------------------------------------------- /img/2.0x/icon_menu_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/icon_menu_share.png -------------------------------------------------------------------------------- /img/2.0x/me_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_action.png -------------------------------------------------------------------------------- /img/2.0x/me_buy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_buy.png -------------------------------------------------------------------------------- /img/2.0x/me_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_comment.png -------------------------------------------------------------------------------- /img/2.0x/me_coupon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_coupon.png -------------------------------------------------------------------------------- /img/2.0x/me_date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_date.png -------------------------------------------------------------------------------- /img/2.0x/me_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_favorite.png -------------------------------------------------------------------------------- /img/2.0x/me_feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_feedback.png -------------------------------------------------------------------------------- /img/2.0x/me_gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_gift.png -------------------------------------------------------------------------------- /img/2.0x/me_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_night.png -------------------------------------------------------------------------------- /img/2.0x/me_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_record.png -------------------------------------------------------------------------------- /img/2.0x/me_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_setting.png -------------------------------------------------------------------------------- /img/2.0x/me_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_theme.png -------------------------------------------------------------------------------- /img/2.0x/me_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_vip.png -------------------------------------------------------------------------------- /img/2.0x/me_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/me_wallet.png -------------------------------------------------------------------------------- /img/2.0x/menu_category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/menu_category.png -------------------------------------------------------------------------------- /img/2.0x/menu_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/menu_complete.png -------------------------------------------------------------------------------- /img/2.0x/menu_publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/menu_publish.png -------------------------------------------------------------------------------- /img/2.0x/menu_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/menu_rank.png -------------------------------------------------------------------------------- /img/2.0x/menu_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/menu_vip.png -------------------------------------------------------------------------------- /img/2.0x/placeholder_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/placeholder_avatar.png -------------------------------------------------------------------------------- /img/2.0x/pub_back_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/pub_back_gray.png -------------------------------------------------------------------------------- /img/2.0x/pub_back_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/pub_back_white.png -------------------------------------------------------------------------------- /img/2.0x/read_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_bg.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_brightness.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_catalog.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_chapter_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_chapter_next.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_chapter_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_chapter_previous.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_font.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_more.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_setting.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_vip.png -------------------------------------------------------------------------------- /img/2.0x/read_icon_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_icon_voice.png -------------------------------------------------------------------------------- /img/2.0x/read_more_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/read_more_detail.png -------------------------------------------------------------------------------- /img/2.0x/reader_battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/reader_battery.png -------------------------------------------------------------------------------- /img/2.0x/tab_bookshelf_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_bookshelf_n.png -------------------------------------------------------------------------------- /img/2.0x/tab_bookshelf_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_bookshelf_p.png -------------------------------------------------------------------------------- /img/2.0x/tab_bookstore_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_bookstore_n.png -------------------------------------------------------------------------------- /img/2.0x/tab_bookstore_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_bookstore_p.png -------------------------------------------------------------------------------- /img/2.0x/tab_me_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_me_n.png -------------------------------------------------------------------------------- /img/2.0x/tab_me_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_me_p.png -------------------------------------------------------------------------------- /img/2.0x/tab_writer_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_writer_n.png -------------------------------------------------------------------------------- /img/2.0x/tab_writer_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/2.0x/tab_writer_p.png -------------------------------------------------------------------------------- /img/actionbar_checkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/actionbar_checkin.png -------------------------------------------------------------------------------- /img/actionbar_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/actionbar_search.png -------------------------------------------------------------------------------- /img/aliuser_title_back_normal.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/aliuser_title_back_normal.9.png -------------------------------------------------------------------------------- /img/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/arrow_right.png -------------------------------------------------------------------------------- /img/bookshelf_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_add.png -------------------------------------------------------------------------------- /img/bookshelf_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_bg.png -------------------------------------------------------------------------------- /img/bookshelf_cloud_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_cloud_0.png -------------------------------------------------------------------------------- /img/bookshelf_cloud_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_cloud_1.png -------------------------------------------------------------------------------- /img/bookshelf_cloud_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_cloud_2.png -------------------------------------------------------------------------------- /img/bookshelf_cloud_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_cloud_3.png -------------------------------------------------------------------------------- /img/bookshelf_continue_read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/bookshelf_continue_read.png -------------------------------------------------------------------------------- /img/detail_chapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_chapter.png -------------------------------------------------------------------------------- /img/detail_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_down.png -------------------------------------------------------------------------------- /img/detail_fold_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_fold_bg.png -------------------------------------------------------------------------------- /img/detail_latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_latest.png -------------------------------------------------------------------------------- /img/detail_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_star.png -------------------------------------------------------------------------------- /img/detail_star_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_star_half.png -------------------------------------------------------------------------------- /img/detail_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_up.png -------------------------------------------------------------------------------- /img/detail_write_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/detail_write_comment.png -------------------------------------------------------------------------------- /img/home_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/home_search.png -------------------------------------------------------------------------------- /img/home_tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/home_tip.png -------------------------------------------------------------------------------- /img/icon_menu_catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/icon_menu_catalog.png -------------------------------------------------------------------------------- /img/icon_menu_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/icon_menu_share.png -------------------------------------------------------------------------------- /img/me_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_action.png -------------------------------------------------------------------------------- /img/me_buy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_buy.png -------------------------------------------------------------------------------- /img/me_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_comment.png -------------------------------------------------------------------------------- /img/me_coupon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_coupon.png -------------------------------------------------------------------------------- /img/me_date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_date.png -------------------------------------------------------------------------------- /img/me_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_favorite.png -------------------------------------------------------------------------------- /img/me_feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_feedback.png -------------------------------------------------------------------------------- /img/me_gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_gift.png -------------------------------------------------------------------------------- /img/me_night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_night.png -------------------------------------------------------------------------------- /img/me_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_record.png -------------------------------------------------------------------------------- /img/me_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_setting.png -------------------------------------------------------------------------------- /img/me_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_theme.png -------------------------------------------------------------------------------- /img/me_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_vip.png -------------------------------------------------------------------------------- /img/me_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/me_wallet.png -------------------------------------------------------------------------------- /img/menu_category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/menu_category.png -------------------------------------------------------------------------------- /img/menu_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/menu_complete.png -------------------------------------------------------------------------------- /img/menu_publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/menu_publish.png -------------------------------------------------------------------------------- /img/menu_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/menu_rank.png -------------------------------------------------------------------------------- /img/menu_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/menu_vip.png -------------------------------------------------------------------------------- /img/placeholder_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/placeholder_avatar.png -------------------------------------------------------------------------------- /img/pub_back_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/pub_back_gray.png -------------------------------------------------------------------------------- /img/pub_back_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/pub_back_white.png -------------------------------------------------------------------------------- /img/read_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_bg.png -------------------------------------------------------------------------------- /img/read_icon_brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_brightness.png -------------------------------------------------------------------------------- /img/read_icon_catalog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_catalog.png -------------------------------------------------------------------------------- /img/read_icon_chapter_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_chapter_next.png -------------------------------------------------------------------------------- /img/read_icon_chapter_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_chapter_previous.png -------------------------------------------------------------------------------- /img/read_icon_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_font.png -------------------------------------------------------------------------------- /img/read_icon_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_more.png -------------------------------------------------------------------------------- /img/read_icon_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_setting.png -------------------------------------------------------------------------------- /img/read_icon_vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_vip.png -------------------------------------------------------------------------------- /img/read_icon_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_icon_voice.png -------------------------------------------------------------------------------- /img/read_more_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/read_more_detail.png -------------------------------------------------------------------------------- /img/reader_battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/reader_battery.png -------------------------------------------------------------------------------- /img/tab_bookshelf_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_bookshelf_n.png -------------------------------------------------------------------------------- /img/tab_bookshelf_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_bookshelf_p.png -------------------------------------------------------------------------------- /img/tab_bookstore_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_bookstore_n.png -------------------------------------------------------------------------------- /img/tab_bookstore_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_bookstore_p.png -------------------------------------------------------------------------------- /img/tab_me_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_me_n.png -------------------------------------------------------------------------------- /img/tab_me_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_me_p.png -------------------------------------------------------------------------------- /img/tab_writer_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_writer_n.png -------------------------------------------------------------------------------- /img/tab_writer_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/img/tab_writer_p.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.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 | - battery (0.0.1): 3 | - Flutter 4 | - device_info (0.0.1): 5 | - Flutter 6 | - Flutter (1.0.0) 7 | - fluttertoast (0.0.2): 8 | - Flutter 9 | - Toast 10 | - FMDB (2.7.5): 11 | - FMDB/standard (= 2.7.5) 12 | - FMDB/standard (2.7.5) 13 | - image_gallery_saver (1.5.0): 14 | - Flutter 15 | - package_info_plus (0.4.5): 16 | - Flutter 17 | - path_provider_foundation (0.0.1): 18 | - Flutter 19 | - FlutterMacOS 20 | - permission_handler_apple (9.0.4): 21 | - Flutter 22 | - share (0.0.1): 23 | - Flutter 24 | - shared_preferences_foundation (0.0.1): 25 | - Flutter 26 | - FlutterMacOS 27 | - sqflite (0.0.2): 28 | - Flutter 29 | - FMDB (>= 2.7.5) 30 | - Toast (4.0.0) 31 | - url_launcher_ios (0.0.1): 32 | - Flutter 33 | - webview_flutter_wkwebview (0.0.1): 34 | - Flutter 35 | 36 | DEPENDENCIES: 37 | - battery (from `.symlinks/plugins/battery/ios`) 38 | - device_info (from `.symlinks/plugins/device_info/ios`) 39 | - Flutter (from `Flutter`) 40 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 41 | - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) 42 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 43 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 44 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) 45 | - share (from `.symlinks/plugins/share/ios`) 46 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) 47 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 48 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 49 | - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) 50 | 51 | SPEC REPOS: 52 | trunk: 53 | - FMDB 54 | - Toast 55 | 56 | EXTERNAL SOURCES: 57 | battery: 58 | :path: ".symlinks/plugins/battery/ios" 59 | device_info: 60 | :path: ".symlinks/plugins/device_info/ios" 61 | Flutter: 62 | :path: Flutter 63 | fluttertoast: 64 | :path: ".symlinks/plugins/fluttertoast/ios" 65 | image_gallery_saver: 66 | :path: ".symlinks/plugins/image_gallery_saver/ios" 67 | package_info_plus: 68 | :path: ".symlinks/plugins/package_info_plus/ios" 69 | path_provider_foundation: 70 | :path: ".symlinks/plugins/path_provider_foundation/ios" 71 | permission_handler_apple: 72 | :path: ".symlinks/plugins/permission_handler_apple/ios" 73 | share: 74 | :path: ".symlinks/plugins/share/ios" 75 | shared_preferences_foundation: 76 | :path: ".symlinks/plugins/shared_preferences_foundation/ios" 77 | sqflite: 78 | :path: ".symlinks/plugins/sqflite/ios" 79 | url_launcher_ios: 80 | :path: ".symlinks/plugins/url_launcher_ios/ios" 81 | webview_flutter_wkwebview: 82 | :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" 83 | 84 | SPEC CHECKSUMS: 85 | battery: 6b19dafdbba590f03b1f0cd2f1224e3ffedadbe6 86 | device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 87 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 88 | fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 89 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 90 | image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2 91 | package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e 92 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 93 | permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce 94 | share: 0b2c3e82132f5888bccca3351c504d0003b3b410 95 | shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 96 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 97 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 98 | url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 99 | webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a 100 | 101 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 102 | 103 | COCOAPODS: 1.12.1 104 | -------------------------------------------------------------------------------- /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 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "1x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "2x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "3x", 26 | "size" : "29x29" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "3x", 36 | "size" : "40x40" 37 | }, 38 | { 39 | "filename" : "img_app.png", 40 | "idiom" : "iphone", 41 | "scale" : "2x", 42 | "size" : "60x60" 43 | }, 44 | { 45 | "filename" : "img_app-1.png", 46 | "idiom" : "iphone", 47 | "scale" : "3x", 48 | "size" : "60x60" 49 | }, 50 | { 51 | "idiom" : "ios-marketing", 52 | "scale" : "1x", 53 | "size" : "1024x1024" 54 | } 55 | ], 56 | "info" : { 57 | "author" : "xcode", 58 | "version" : 1 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/img_app-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/ios/Runner/Assets.xcassets/AppIcon.appiconset/img_app-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/img_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/ios/Runner/Assets.xcassets/AppIcon.appiconset/img_app.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "img_loading.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/img_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/ios/Runner/Assets.xcassets/LaunchImage.imageset/img_loading.png -------------------------------------------------------------------------------- /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 | shuqi 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UIViewControllerBasedStatusBarAppearance 46 | 47 | CADisableMinimumFrameDurationOnPhone 48 | 49 | UIApplicationSupportsIndirectInputEvents 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/app/app_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | import 'package:shuqi/novel_detail/novel_detail_page.dart'; 6 | import 'package:shuqi/me/login_page.dart'; 7 | import 'package:shuqi/me/web_page.dart'; 8 | import 'package:shuqi/reader/reader_page.dart'; 9 | 10 | class AppNavigator { 11 | static push(BuildContext context, Widget scene) { 12 | Navigator.push( 13 | context, 14 | MaterialPageRoute( 15 | builder: (BuildContext context) => scene, 16 | ), 17 | ); 18 | } 19 | 20 | static pushNovelDetail(BuildContext context, Novel novel) { 21 | AppNavigator.push(context, NovelDetailPage(novel.id)); 22 | } 23 | 24 | static pushLogin(BuildContext context) { 25 | Navigator.push(context, MaterialPageRoute(builder: (context) { 26 | return LoginPage(); 27 | })); 28 | } 29 | 30 | static pushWeb(BuildContext context, String url, String title) { 31 | Navigator.push(context, MaterialPageRoute(builder: (context) { 32 | return WebPage(url: url, title: title); 33 | })); 34 | } 35 | 36 | static pushReader(BuildContext context, int articleId) { 37 | Navigator.push(context, MaterialPageRoute(builder: (context) { 38 | return ReaderPage(articleId: articleId); 39 | })); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/app/app_scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuqi/public.dart'; 3 | import 'package:shuqi/app/root_scene.dart'; 4 | import 'package:oktoast/oktoast.dart'; 5 | 6 | final RouteObserver routeObserver = RouteObserver(); 7 | 8 | class AppPage extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: '书旗小说', 13 | navigatorObservers: [routeObserver], 14 | debugShowCheckedModeBanner: false, 15 | theme: ThemeData( 16 | primaryColor: SQColor.primary, 17 | dividerColor: SQColor.lightGray, 18 | scaffoldBackgroundColor: SQColor.paper, 19 | textTheme: TextTheme(bodyLarge: TextStyle(color: SQColor.darkGray)), 20 | ), 21 | home: OKToast(child: RootPage()), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/constant.dart: -------------------------------------------------------------------------------- 1 | const String EventToggleTabBarIndex = 'EventToggleTabBarIndex'; 2 | -------------------------------------------------------------------------------- /lib/app/request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/services.dart' show rootBundle; 3 | import 'dart:convert'; 4 | 5 | class Request { 6 | static const String baseUrl = 'http://www.shuqi.com/'; 7 | 8 | static Future get({required String action, Map? params}) async { 9 | return Request.mock(action: action, params: params); 10 | } 11 | 12 | static Future post({required String action, Map? params}) async { 13 | return Request.mock(action: action, params: params); 14 | } 15 | 16 | static Future mock({required String action, Map? params}) async { 17 | var responseStr = await rootBundle.loadString('mock/$action.json'); 18 | var responseJson = json.decode(responseStr); 19 | return responseJson['data']; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/app/root_scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | import 'package:shuqi/public.dart'; 6 | 7 | import 'package:shuqi/home/home_scene.dart'; 8 | import 'package:shuqi/bookshelf/bookshelf_scene.dart'; 9 | import 'package:shuqi/me/me_page.dart'; 10 | 11 | class RootPage extends StatefulWidget { 12 | @override 13 | State createState() => RootPageState(); 14 | } 15 | 16 | class RootPageState extends State { 17 | int _tabIndex = 1; 18 | bool isFinishSetup = false; 19 | List _tabImages = [ 20 | Image.asset('img/tab_bookshelf_n.png'), 21 | Image.asset('img/tab_bookstore_n.png'), 22 | Image.asset('img/tab_me_n.png'), 23 | ]; 24 | List _tabSelectedImages = [ 25 | Image.asset('img/tab_bookshelf_p.png'), 26 | Image.asset('img/tab_bookstore_p.png'), 27 | Image.asset('img/tab_me_p.png'), 28 | ]; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | 34 | setupApp(); 35 | 36 | eventBus.on(EventUserLogin, (arg) { 37 | setState(() {}); 38 | }); 39 | 40 | eventBus.on(EventUserLogout, (arg) { 41 | setState(() {}); 42 | }); 43 | 44 | eventBus.on(EventToggleTabBarIndex, (arg) { 45 | setState(() { 46 | _tabIndex = arg; 47 | }); 48 | }); 49 | } 50 | 51 | @override 52 | void dispose() { 53 | eventBus.off(EventUserLogin); 54 | eventBus.off(EventUserLogout); 55 | eventBus.off(EventToggleTabBarIndex); 56 | super.dispose(); 57 | } 58 | 59 | setupApp() async { 60 | preferences = await SharedPreferences.getInstance(); 61 | setState(() { 62 | isFinishSetup = true; 63 | }); 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | if (!isFinishSetup) { 69 | return Container(); 70 | } 71 | 72 | return Scaffold( 73 | body: IndexedStack( 74 | children: [ 75 | BookshelfPage(), 76 | HomePage(), 77 | MePage(), 78 | ], 79 | index: _tabIndex, 80 | ), 81 | bottomNavigationBar: CupertinoTabBar( 82 | backgroundColor: Colors.white, 83 | activeColor: SQColor.primary, 84 | items: [ 85 | BottomNavigationBarItem(icon: getTabIcon(0), label: '书架'), 86 | BottomNavigationBarItem(icon: getTabIcon(1), label: '书城'), 87 | BottomNavigationBarItem(icon: getTabIcon(2), label: '我的'), 88 | ], 89 | currentIndex: _tabIndex, 90 | onTap: (index) { 91 | setState(() { 92 | _tabIndex = index; 93 | }); 94 | }, 95 | ), 96 | ); 97 | } 98 | 99 | Image getTabIcon(int index) { 100 | if (index == _tabIndex) { 101 | return _tabSelectedImages[index]; 102 | } else { 103 | return _tabImages[index]; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/app/sq_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SQColor { 4 | static Color primary = Color(0xFF23B38E); 5 | static Color secondary = Color(0xFF51DEC6); 6 | static Color red = Color(0xFFFF2B45); 7 | static Color orange = Color(0xFFF67264); 8 | static Color white = Color(0xFFFFFFFF); 9 | static Color paper = Color(0xFFF5F5F5); 10 | static Color lightGray = Color(0xFFBBBBBB); 11 | static Color darkGray = Color(0xFF333333); 12 | static Color gray = Color(0xFF888888); 13 | static Color blue = Color(0xFF3688FF); 14 | static Color golden = Color(0xff8B7961); 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/user_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:shuqi/utility/event_bus.dart'; 4 | import 'package:shuqi/global.dart'; 5 | 6 | const String EventUserLogin = 'EventUserLogin'; 7 | const String EventUserLogout = 'EventUserLogout'; 8 | 9 | class UserManager { 10 | static UserManager instance = UserManager(); 11 | 12 | User? user; 13 | static User? get currentUser { 14 | return UserManager.instance.user; 15 | } 16 | 17 | logout() { 18 | this.user = null; 19 | preferences.remove('user'); 20 | eventBus.emit(EventUserLogout); 21 | } 22 | 23 | login(Map userJson) { 24 | var user = User.fromJson(userJson); 25 | this.user = user; 26 | saveUser(); 27 | 28 | eventBus.emit(EventUserLogin); 29 | } 30 | 31 | bool get isLogin { 32 | return user != null; 33 | } 34 | 35 | loadUserFromLocal() { 36 | String? userJson = preferences.getString('user'); 37 | if (userJson != null) { 38 | user = User.fromJson(json.decode(userJson)); 39 | } 40 | } 41 | 42 | void saveUser() async { 43 | var data = json.encode(user); 44 | preferences.setString('user', data); 45 | } 46 | } 47 | 48 | class User { 49 | late String token; 50 | late int id; 51 | late String nickname; 52 | late String avatarUrl; 53 | late bool isVip; 54 | late double wealth; 55 | late int coupon; 56 | late int monthlyTicket; 57 | 58 | User.fromJson(Map json) { 59 | token = json['token']; 60 | id = json['id']; 61 | nickname = json['nickname']; 62 | avatarUrl = json['avatar']; 63 | isVip = json['is_vip'] == 1; 64 | wealth = json['wealth']; 65 | coupon = json['coupon']; 66 | monthlyTicket = json['ticket']; 67 | } 68 | 69 | Map toJson() { 70 | return { 71 | 'token': token, 72 | 'id': id, 73 | 'nickname': nickname, 74 | 'avatar': avatarUrl, 75 | 'is_vip': isVip ? 1 : 0, 76 | 'wealth': wealth, 77 | 'coupon': coupon, 78 | 'ticket': monthlyTicket, 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/bookshelf/bookshelf_cloud_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuqi/public.dart'; 3 | 4 | class BookshelfCloudWidget extends AnimatedWidget { 5 | final double width; 6 | BookshelfCloudWidget({required Animation animation, required this.width}) : super(listenable: animation); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | var width = Screen.width; 11 | final Animation animation = listenable as Animation; 12 | return Container( 13 | width: width, 14 | height: width * 0.73, 15 | child: Stack( 16 | alignment: AlignmentDirectional.bottomStart, 17 | children: [ 18 | Positioned( 19 | bottom: -30, 20 | child: Image.asset( 21 | 'img/bookshelf_cloud_0.png', 22 | fit: BoxFit.cover, 23 | width: width, 24 | ), 25 | ), 26 | Positioned( 27 | bottom: animation.value * -5, 28 | child: Opacity( 29 | opacity: animation.value, 30 | child: Image.asset( 31 | 'img/bookshelf_cloud_1.png', 32 | fit: BoxFit.cover, 33 | width: width, 34 | ), 35 | ), 36 | ), 37 | Positioned( 38 | bottom: (1 - animation.value) * -10, 39 | child: Opacity( 40 | opacity: animation.value, 41 | child: Image.asset( 42 | 'img/bookshelf_cloud_2.png', 43 | fit: BoxFit.cover, 44 | width: width, 45 | ), 46 | ), 47 | ), 48 | Positioned( 49 | bottom: (animation.value - 0.5).abs() * -15, 50 | child: Opacity( 51 | opacity: (1.0 - animation.value), 52 | child: Image.asset( 53 | 'img/bookshelf_cloud_3.png', 54 | fit: BoxFit.cover, 55 | width: width, 56 | ), 57 | ), 58 | ), 59 | ], 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/bookshelf/bookshelf_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | import 'bookshelf_cloud_widget.dart'; 6 | 7 | class BookshelfHeader extends StatefulWidget { 8 | final Novel novel; 9 | 10 | BookshelfHeader(this.novel); 11 | 12 | @override 13 | _BookshelfHeaderState createState() => _BookshelfHeaderState(); 14 | } 15 | 16 | class _BookshelfHeaderState extends State with SingleTickerProviderStateMixin { 17 | late AnimationController controller; 18 | late Animation animation; 19 | 20 | @override 21 | initState() { 22 | super.initState(); 23 | controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); 24 | animation = Tween(begin: 0.0, end: 1.0).animate(controller); 25 | 26 | animation.addStatusListener((status) { 27 | if (status == AnimationStatus.completed) { 28 | controller.reverse(); 29 | } else if (status == AnimationStatus.dismissed) { 30 | controller.forward(); 31 | } 32 | }); 33 | controller.forward(); 34 | } 35 | 36 | dispose() { 37 | controller.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | var width = Screen.width; 44 | var bgHeight = width / 0.9; 45 | var height = Screen.topSafeHeight + 250; 46 | return Container( 47 | width: width, 48 | height: height, 49 | child: Stack( 50 | children: [ 51 | Positioned( 52 | top: height - bgHeight, 53 | child: Image.asset( 54 | 'img/bookshelf_bg.png', 55 | fit: BoxFit.cover, 56 | width: width, 57 | height: bgHeight, 58 | ), 59 | ), 60 | Positioned( 61 | bottom: 0, 62 | child: BookshelfCloudWidget( 63 | animation: animation, 64 | width: width, 65 | ), 66 | ), 67 | buildContent(context), 68 | ], 69 | ), 70 | ); 71 | } 72 | 73 | Widget buildContent(BuildContext context) { 74 | Novel novel = this.widget.novel; 75 | 76 | var width = Screen.width; 77 | return Container( 78 | width: width, 79 | padding: EdgeInsets.fromLTRB(15, 54 + Screen.topSafeHeight, 10, 0), 80 | color: Colors.transparent, 81 | child: GestureDetector( 82 | onTap: () { 83 | AppNavigator.pushNovelDetail(context, novel); 84 | }, 85 | child: Row( 86 | crossAxisAlignment: CrossAxisAlignment.start, 87 | children: [ 88 | DecoratedBox( 89 | child: NovelCoverImage(novel.imgUrl, width: 120, height: 160), 90 | decoration: BoxDecoration(boxShadow: Styles.borderShadow), 91 | ), 92 | SizedBox(width: 20), 93 | Expanded( 94 | child: Column( 95 | crossAxisAlignment: CrossAxisAlignment.start, 96 | children: [ 97 | SizedBox(height: 40), 98 | Text(novel.name, style: TextStyle(fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold)), 99 | SizedBox(height: 20), 100 | Row( 101 | children: [ 102 | Text('读至0.2% 继续阅读 ', style: TextStyle(fontSize: 14, color: SQColor.paper)), 103 | Image.asset('img/bookshelf_continue_read.png'), 104 | ], 105 | ), 106 | ], 107 | ), 108 | ) 109 | ], 110 | ), 111 | ), 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/bookshelf/bookshelf_item_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class BookshelfItemView extends StatelessWidget { 6 | final Novel novel; 7 | BookshelfItemView(this.novel); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | var width = (Screen.width - 15 * 2 - 24 * 2) / 3; 12 | return GestureDetector( 13 | onTap: () { 14 | AppNavigator.pushNovelDetail(context, novel); 15 | }, 16 | child: Container( 17 | width: width, 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | DecoratedBox( 22 | child: NovelCoverImage( 23 | novel.imgUrl, 24 | width: width, 25 | height: width / 0.75, 26 | ), 27 | decoration: BoxDecoration(boxShadow: [BoxShadow(color: Color(0x22000000), blurRadius: 5)]), 28 | ), 29 | SizedBox(height: 10), 30 | Text(novel.name, style: TextStyle(fontSize: 14), maxLines: 1, overflow: TextOverflow.ellipsis), 31 | SizedBox(height: 25), 32 | ], 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/bookshelf/bookshelf_scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | import 'bookshelf_item_view.dart'; 7 | import 'bookshelf_header.dart'; 8 | 9 | class BookshelfPage extends StatefulWidget { 10 | @override 11 | State createState() => BookshelfState(); 12 | } 13 | 14 | class BookshelfState extends State with RouteAware { 15 | List favoriteNovels = []; 16 | ScrollController scrollController = ScrollController(); 17 | double navAlpha = 0; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | fetchData(); 23 | 24 | scrollController.addListener(() { 25 | var offset = scrollController.offset; 26 | if (offset < 0) { 27 | if (navAlpha != 0) { 28 | setState(() { 29 | navAlpha = 0; 30 | }); 31 | } 32 | } else if (offset < 50) { 33 | setState(() { 34 | navAlpha = 1 - (50 - offset) / 50; 35 | }); 36 | } else if (navAlpha != 1) { 37 | setState(() { 38 | navAlpha = 1; 39 | }); 40 | } 41 | }); 42 | } 43 | 44 | Future fetchData() async { 45 | try { 46 | List favoriteNovels = []; 47 | List favoriteResponse = await Request.get(action: 'bookshelf'); 48 | favoriteResponse.forEach((data) { 49 | favoriteNovels.add(Novel.fromJson(data)); 50 | }); 51 | 52 | setState(() { 53 | this.favoriteNovels = favoriteNovels; 54 | }); 55 | } catch (e) { 56 | Toast.show(e.toString()); 57 | } 58 | } 59 | 60 | Widget buildActions(Color iconColor) { 61 | return Row(children: [ 62 | Container( 63 | height: kToolbarHeight, 64 | width: 44, 65 | child: Image.asset('img/actionbar_checkin.png', color: iconColor), 66 | ), 67 | Container( 68 | height: kToolbarHeight, 69 | width: 44, 70 | child: Image.asset('img/actionbar_search.png', color: iconColor), 71 | ), 72 | SizedBox(width: 15) 73 | ]); 74 | } 75 | 76 | Widget buildNavigationBar() { 77 | return Stack( 78 | children: [ 79 | Positioned( 80 | right: 0, 81 | child: Container( 82 | margin: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 0, 0), 83 | child: buildActions(SQColor.white), 84 | ), 85 | ), 86 | Opacity( 87 | opacity: navAlpha, 88 | child: Container( 89 | padding: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 0, 0), 90 | height: Screen.navigationBarHeight, 91 | color: SQColor.white, 92 | child: Row( 93 | children: [ 94 | SizedBox(width: 103), 95 | Expanded( 96 | child: Text( 97 | '书架', 98 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 99 | textAlign: TextAlign.center, 100 | ), 101 | ), 102 | buildActions(SQColor.darkGray), 103 | ], 104 | ), 105 | ), 106 | ) 107 | ], 108 | ); 109 | } 110 | 111 | Widget buildFavoriteView() { 112 | if (favoriteNovels.length <= 1) { 113 | return Container(); 114 | } 115 | 116 | List children = []; 117 | var novels = favoriteNovels.sublist(1); 118 | novels.forEach((novel) { 119 | children.add(BookshelfItemView(novel)); 120 | }); 121 | var width = (Screen.width - 15 * 2 - 24 * 2) / 3; 122 | children.add(GestureDetector( 123 | onTap: () { 124 | eventBus.emit(EventToggleTabBarIndex, 1); 125 | }, 126 | child: Container( 127 | color: SQColor.paper, 128 | width: width, 129 | height: width / 0.75, 130 | child: Image.asset('img/bookshelf_add.png'), 131 | ), 132 | )); 133 | return Container( 134 | padding: EdgeInsets.fromLTRB(15, 20, 15, 15), 135 | child: Wrap( 136 | spacing: 23, 137 | children: children, 138 | ), 139 | ); 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return Scaffold( 145 | backgroundColor: SQColor.white, 146 | body: AnnotatedRegion( 147 | value: navAlpha > 0.5 ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light, 148 | child: Stack(children: [ 149 | RefreshIndicator( 150 | onRefresh: fetchData, 151 | child: ListView( 152 | padding: EdgeInsets.only(top: 0), 153 | controller: scrollController, 154 | children: [ 155 | favoriteNovels.length > 0 ? BookshelfHeader(favoriteNovels[0]) : Container(), 156 | buildFavoriteView(), 157 | ], 158 | ), 159 | ), 160 | buildNavigationBar(), 161 | ]), 162 | ), 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/global.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | late SharedPreferences preferences; 4 | -------------------------------------------------------------------------------- /lib/home/home_banner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:carousel_slider/carousel_slider.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | import 'home_model.dart'; 6 | 7 | class HomeBanner extends StatelessWidget { 8 | final List carouselInfos; 9 | 10 | HomeBanner(this.carouselInfos); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | if (carouselInfos.length == 0) { 15 | return SizedBox(); 16 | } 17 | 18 | return Container( 19 | color: Colors.white, 20 | child: CarouselSlider( 21 | items: carouselInfos.map((info) { 22 | return Builder( 23 | builder: (BuildContext context) { 24 | return Container( 25 | width: Screen.width, 26 | margin: EdgeInsets.symmetric(horizontal: 5.0), 27 | child: Image.network( 28 | info.imageUrl ?? '', 29 | fit: BoxFit.cover, 30 | ), 31 | ); 32 | }, 33 | ); 34 | }).toList(), 35 | options: CarouselOptions( 36 | aspectRatio: 2, 37 | autoPlay: true, 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/home/home_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | import 'home_model.dart'; 6 | import 'home_banner.dart'; 7 | import 'home_menu.dart'; 8 | import 'novel_normal_card.dart'; 9 | import 'novel_four_grid_view.dart'; 10 | import 'novel_first_hybird_card.dart'; 11 | import 'novel_second_hybird_card.dart'; 12 | 13 | enum HomeListType { 14 | excellent, 15 | male, 16 | female, 17 | cartoon, 18 | } 19 | 20 | class HomeListView extends StatefulWidget { 21 | final HomeListType type; 22 | 23 | HomeListView(this.type); 24 | 25 | @override 26 | State createState() { 27 | return HomeListViewState(); 28 | } 29 | } 30 | 31 | class HomeListViewState extends State with AutomaticKeepAliveClientMixin { 32 | List carouselInfos = []; 33 | int pageIndex = 1; 34 | List modules = []; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | fetchData(); 40 | } 41 | 42 | @override 43 | bool get wantKeepAlive => true; 44 | 45 | Future fetchData() async { 46 | try { 47 | late var action; 48 | switch (this.widget.type) { 49 | case HomeListType.excellent: 50 | action = 'home_excellent'; 51 | break; 52 | case HomeListType.female: 53 | action = 'home_female'; 54 | break; 55 | case HomeListType.male: 56 | action = 'home_male'; 57 | break; 58 | case HomeListType.cartoon: 59 | action = 'home_cartoon'; 60 | break; 61 | default: 62 | break; 63 | } 64 | var responseJson = await Request.get(action: action); 65 | List moduleData = responseJson['module']; 66 | List modules = []; 67 | moduleData.forEach((data) { 68 | modules.add(HomeModule.fromJson(data)); 69 | }); 70 | 71 | setState(() { 72 | this.modules = modules; 73 | this.carouselInfos = carouselInfos; 74 | }); 75 | } catch (e) { 76 | Toast.show(e.toString()); 77 | } 78 | } 79 | 80 | Widget bookCardWithInfo(HomeModule module) { 81 | Widget? card; 82 | switch (module.style) { 83 | case 1: 84 | card = NovelFourGridView(module); 85 | break; 86 | case 2: 87 | card = NovelSecondHybirdCard(module); 88 | break; 89 | case 3: 90 | card = NovelFirstHybirdCard(module); 91 | break; 92 | case 4: 93 | card = NovelNormalCard(module); 94 | break; 95 | } 96 | return card ?? SizedBox(); 97 | } 98 | 99 | Widget buildModule(BuildContext context, HomeModule module) { 100 | if (module.carousels != null) { 101 | return HomeBanner(module.carousels!); 102 | } else if (module.menus != null) { 103 | return HomeMenu(module.menus!); 104 | } else if (module.books != null) { 105 | return bookCardWithInfo(module); 106 | } 107 | 108 | return Container(); 109 | } 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | super.build(context); 114 | return Container( 115 | child: RefreshIndicator( 116 | onRefresh: fetchData, 117 | child: ListView.builder( 118 | itemCount: modules.length, 119 | itemBuilder: (BuildContext context, int index) { 120 | return buildModule(context, modules[index]); 121 | }, 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/home/home_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | import 'home_model.dart'; 6 | 7 | class HomeMenu extends StatelessWidget { 8 | final List infos; 9 | 10 | HomeMenu(this.infos); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | color: Colors.white, 16 | padding: EdgeInsets.symmetric(vertical: 10), 17 | child: Row( 18 | mainAxisAlignment: MainAxisAlignment.spaceAround, 19 | children: infos.map((info) => menuItem(info)).toList(), 20 | ), 21 | ); 22 | } 23 | 24 | Widget menuItem(MenuInfo info) { 25 | return Column( 26 | children: [ 27 | Image.asset(info.icon), 28 | SizedBox(height: 5), 29 | Text(info.title, style: TextStyle(fontSize: 12, color: SQColor.gray)), 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/home/home_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuqi/model/novel.dart'; 2 | 3 | class HomeModule { 4 | late String id; 5 | late String name; 6 | late int style; 7 | late List content; 8 | 9 | List? carousels; 10 | List? menus; 11 | List? books; 12 | 13 | HomeModule.fromJson(Map data) { 14 | id = data['id']; 15 | name = data['m_s_name']; 16 | content = data['content']; 17 | 18 | if (name == '顶部banner') { 19 | carousels = []; 20 | content.forEach((data) { 21 | carousels!.add(CarouselInfo.fromJson(data)); 22 | }); 23 | } 24 | 25 | if (name == '顶部导航') { 26 | menus = []; 27 | content.forEach((data) { 28 | menus!.add(MenuInfo.fromJson(data)); 29 | }); 30 | } 31 | 32 | if (data['m_s_style'] != null) { 33 | style = data['m_s_style']; 34 | books = []; 35 | content.forEach((data) { 36 | books!.add(Novel.fromJson(data)); 37 | }); 38 | } 39 | } 40 | } 41 | 42 | class MenuInfo { 43 | late String title; 44 | late String icon; 45 | 46 | MenuInfo.fromJson(Map data) { 47 | title = data['toTitle']; 48 | icon = data['icon']; 49 | } 50 | } 51 | 52 | class CarouselInfo { 53 | String? imageUrl; 54 | late String link; 55 | 56 | CarouselInfo.fromJson(Map data) { 57 | imageUrl = data['image_url']; 58 | link = data['link_url']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/home/home_novel_cover_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class HomeNovelCoverView extends StatelessWidget { 6 | final Novel novel; 7 | HomeNovelCoverView(this.novel); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | var width = (Screen.width - 15 * 2 - 15 * 3) / 4; 12 | return GestureDetector( 13 | onTap: () { 14 | AppNavigator.pushNovelDetail(context, novel); 15 | }, 16 | child: Container( 17 | width: width, 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | NovelCoverImage(novel.imgUrl, width: width, height: width / 0.75), 22 | SizedBox(height: 5), 23 | Text( 24 | novel.name, 25 | overflow: TextOverflow.ellipsis, 26 | style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), 27 | maxLines: 1, 28 | ), 29 | SizedBox(height: 3), 30 | Text( 31 | novel.author, 32 | style: TextStyle(fontSize: 12, color: SQColor.gray), 33 | maxLines: 1, 34 | ), 35 | ], 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/home/home_scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | import 'home_list_view.dart'; 7 | 8 | class HomePage extends StatefulWidget { 9 | @override 10 | State createState() => HomePageState(); 11 | } 12 | 13 | class HomePageState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return DefaultTabController( 17 | length: 4, 18 | child: Scaffold( 19 | appBar: AppBar( 20 | systemOverlayStyle: SystemUiOverlayStyle.dark, 21 | title: Container( 22 | padding: EdgeInsets.symmetric(horizontal: 15), 23 | child: TabBar( 24 | labelColor: SQColor.darkGray, 25 | labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), 26 | unselectedLabelColor: SQColor.gray, 27 | indicatorColor: SQColor.secondary, 28 | indicatorSize: TabBarIndicatorSize.label, 29 | indicatorWeight: 3, 30 | indicatorPadding: EdgeInsets.fromLTRB(8, 0, 8, 5), 31 | tabs: [ 32 | Tab(text: '精选'), 33 | Tab(text: '女生'), 34 | Tab(text: '男生'), 35 | Tab(text: '漫画'), 36 | ], 37 | ), 38 | ), 39 | backgroundColor: SQColor.white, 40 | elevation: 0, 41 | ), 42 | body: TabBarView(children: [ 43 | HomeListView(HomeListType.excellent), 44 | HomeListView(HomeListType.female), 45 | HomeListView(HomeListType.male), 46 | HomeListView(HomeListType.cartoon), 47 | ]), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/home/home_section_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeSectionView extends StatelessWidget { 4 | final String title; 5 | HomeSectionView(this.title); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | color: Colors.white, 11 | padding: EdgeInsets.fromLTRB(15, 15, 0, 5), 12 | child: Row( 13 | children: [ 14 | Image.asset('img/home_tip.png'), 15 | SizedBox(width: 10), 16 | Text( 17 | '$title', 18 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 19 | ) 20 | ], 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/home/novel_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class NovelCell extends StatelessWidget { 6 | final Novel novel; 7 | 8 | NovelCell(this.novel); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GestureDetector( 13 | onTap: () { 14 | AppNavigator.pushNovelDetail(context, novel); 15 | }, 16 | child: Container( 17 | padding: EdgeInsets.all(15), 18 | child: Row( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | NovelCoverImage(novel.imgUrl, width: 70, height: 93), 22 | SizedBox(width: 15), 23 | Expanded( 24 | child: buildRight(), 25 | ), 26 | ], 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | Widget buildRight() { 33 | return Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | Text( 37 | novel.name, 38 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 39 | ), 40 | SizedBox(height: 5), 41 | Text( 42 | novel.introduction, 43 | maxLines: 2, 44 | overflow: TextOverflow.ellipsis, 45 | style: TextStyle( 46 | fontSize: 14, 47 | color: SQColor.gray, 48 | ), 49 | ), 50 | SizedBox(height: 5), 51 | Row( 52 | children: [ 53 | Text( 54 | novel.author, 55 | style: TextStyle(fontSize: 14, color: SQColor.gray), 56 | ), 57 | Expanded(child: Container()), 58 | buildTag(novel.status, novel.statusColor()), 59 | SizedBox(width: 5), 60 | buildTag(novel.type, SQColor.gray), 61 | ], 62 | ) 63 | ], 64 | ); 65 | } 66 | 67 | Widget buildTag(String title, Color color) { 68 | return Container( 69 | padding: EdgeInsets.fromLTRB(5, 2, 5, 3), 70 | decoration: BoxDecoration( 71 | border: Border.all(color: Color.fromARGB(99, color.red, color.green, color.blue), width: 0.5), 72 | borderRadius: BorderRadius.circular(3), 73 | ), 74 | child: Text( 75 | title, 76 | style: TextStyle(fontSize: 11, color: color), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/home/novel_first_hybird_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'novel_cell.dart'; 4 | import 'novel_grid_item.dart'; 5 | import 'home_section_view.dart'; 6 | import 'home_model.dart'; 7 | 8 | class NovelFirstHybirdCard extends StatelessWidget { 9 | final HomeModule cardInfo; 10 | 11 | NovelFirstHybirdCard(this.cardInfo); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | var novels = cardInfo.books; 16 | if (novels == null || novels.length < 3) { 17 | return Container(); 18 | } 19 | 20 | List children = []; 21 | var bottomNovels = novels.sublist(1); 22 | bottomNovels.forEach((novel) { 23 | children.add(NovelGridItem(novel)); 24 | }); 25 | 26 | return Container( 27 | color: Colors.white, 28 | child: Column( 29 | children: [ 30 | HomeSectionView(cardInfo.name), 31 | NovelCell(novels[0]), 32 | Container( 33 | padding: EdgeInsets.fromLTRB(15, 10, 15, 10), 34 | child: Wrap(spacing: 15, runSpacing: 15, children: children), 35 | ), 36 | Container( 37 | height: 10, 38 | color: Color(0xfff5f5f5), 39 | ) 40 | ], 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/home/novel_four_grid_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'home_model.dart'; 4 | import 'home_section_view.dart'; 5 | import 'home_novel_cover_view.dart'; 6 | 7 | class NovelFourGridView extends StatelessWidget { 8 | final HomeModule cardInfo; 9 | 10 | NovelFourGridView(this.cardInfo); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | var novels = cardInfo.books; 15 | if (novels == null || novels.length < 8) { 16 | return Container(); 17 | } 18 | var children = novels.map((novel) => HomeNovelCoverView(novel)).toList(); 19 | return Container( 20 | color: Colors.white, 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | HomeSectionView(cardInfo.name), 25 | Container( 26 | padding: EdgeInsets.fromLTRB(15, 10, 15, 10), 27 | child: Wrap(spacing: 15, runSpacing: 20, children: children), 28 | ), 29 | Container( 30 | height: 10, 31 | color: Color(0xfff5f5f5), 32 | ) 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/home/novel_grid_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class NovelGridItem extends StatelessWidget { 6 | final Novel novel; 7 | 8 | NovelGridItem(this.novel); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | var width = (Screen.width - 15 * 2 - 15) / 2; 13 | return GestureDetector( 14 | onTap: () { 15 | AppNavigator.pushNovelDetail(context, this.novel); 16 | }, 17 | child: Container( 18 | width: width, 19 | child: Row( 20 | children: [ 21 | NovelCoverImage(novel.imgUrl, width: 50, height: 66), 22 | SizedBox(width: 10), 23 | Expanded( 24 | child: Column( 25 | crossAxisAlignment: CrossAxisAlignment.start, 26 | children: [ 27 | Text( 28 | novel.name, 29 | maxLines: 2, 30 | style: TextStyle(fontSize: 16, height: 0.9, fontWeight: FontWeight.bold), 31 | ), 32 | Text( 33 | novel.recommendCountStr(), 34 | style: TextStyle(fontSize: 12, color: SQColor.gray), 35 | ) 36 | ], 37 | ), 38 | ) 39 | ], 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/home/novel_normal_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'home_model.dart'; 4 | import 'novel_cell.dart'; 5 | import 'home_section_view.dart'; 6 | 7 | class NovelNormalCard extends StatelessWidget { 8 | final HomeModule cardInfo; 9 | 10 | NovelNormalCard(this.cardInfo); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | var novels = cardInfo.books; 15 | if (novels == null || novels.length < 3) { 16 | return SizedBox(); 17 | } 18 | 19 | List children = [ 20 | HomeSectionView(cardInfo.name), 21 | ]; 22 | for (var i = 0; i < novels.length; i++) { 23 | var novel = novels[i]; 24 | children.add(NovelCell(novel)); 25 | children.add(Divider(height: 1)); 26 | } 27 | children.add(Container(height: 10, color: Color(0xfff5f5f5))); 28 | 29 | return Container( 30 | color: Colors.white, 31 | child: Column( 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: children, 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/home/novel_second_hybird_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'novel_grid_item.dart'; 4 | import 'home_section_view.dart'; 5 | import 'home_novel_cover_view.dart'; 6 | import 'home_model.dart'; 7 | 8 | class NovelSecondHybirdCard extends StatelessWidget { 9 | final HomeModule cardInfo; 10 | 11 | NovelSecondHybirdCard(this.cardInfo); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | var novels = cardInfo.books; 16 | if (novels == null || novels.length < 5) { 17 | return Container(); 18 | } 19 | 20 | var topNovels = novels.sublist(0, 4); 21 | List children = []; 22 | topNovels.forEach((novel) { 23 | children.add(HomeNovelCoverView(novel)); 24 | }); 25 | 26 | var bottomNovels = novels.sublist(4); 27 | bottomNovels.forEach((novel) { 28 | children.add(NovelGridItem(novel)); 29 | }); 30 | 31 | return Container( 32 | color: Colors.white, 33 | child: Column( 34 | children: [ 35 | HomeSectionView(cardInfo.name), 36 | Container( 37 | padding: EdgeInsets.fromLTRB(15, 10, 15, 10), 38 | child: Wrap(spacing: 15, runSpacing: 15, children: children), 39 | ), 40 | Container( 41 | height: 10, 42 | color: Color(0xfff5f5f5), 43 | ) 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:io'; 3 | import 'package:flutter/services.dart'; 4 | 5 | import 'package:shuqi/app/app_scene.dart'; 6 | 7 | void main() { 8 | runApp(AppPage()); 9 | 10 | if (Platform.isAndroid) { 11 | SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent); 12 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/me/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | class LoginPage extends StatefulWidget { 7 | @override 8 | State createState() => LoginPageState(); 9 | } 10 | 11 | class LoginPageState extends State { 12 | TextEditingController phoneEditer = TextEditingController(); 13 | TextEditingController codeEditer = TextEditingController(); 14 | int coldDownSeconds = 0; 15 | Timer? timer; 16 | 17 | Future fetchSmsCode() async { 18 | if (phoneEditer.text.length == 0) { 19 | Toast.show('请输入手机号'); 20 | return; 21 | } 22 | try { 23 | setState(() { 24 | coldDownSeconds = 60; 25 | }); 26 | coldDown(); 27 | } catch (e) { 28 | Toast.show(e.toString()); 29 | } 30 | } 31 | 32 | login() async { 33 | var phone = phoneEditer.text; 34 | var code = codeEditer.text; 35 | 36 | try { 37 | var response = await Request.post(action: 'login', params: { 38 | 'phone': phone, 39 | 'code': code, 40 | }); 41 | UserManager.instance.login(response); 42 | 43 | Navigator.pop(context); 44 | } catch (e) { 45 | Toast.show(e.toString()); 46 | } 47 | } 48 | 49 | @override 50 | void dispose() { 51 | if (timer != null) { 52 | timer!.cancel(); 53 | } 54 | super.dispose(); 55 | } 56 | 57 | coldDown() { 58 | timer = Timer(Duration(seconds: 1), () { 59 | setState(() { 60 | --coldDownSeconds; 61 | }); 62 | 63 | coldDown(); 64 | }); 65 | } 66 | 67 | Widget buildPhone() { 68 | return Container( 69 | padding: EdgeInsets.symmetric(horizontal: 8), 70 | decoration: BoxDecoration( 71 | color: SQColor.paper, 72 | borderRadius: BorderRadius.circular(5), 73 | ), 74 | child: TextField( 75 | controller: phoneEditer, 76 | keyboardType: TextInputType.phone, 77 | style: TextStyle(fontSize: 14, color: SQColor.darkGray), 78 | decoration: InputDecoration( 79 | hintText: '请输入手机号', 80 | hintStyle: TextStyle(color: SQColor.gray), 81 | border: InputBorder.none, 82 | ), 83 | ), 84 | ); 85 | } 86 | 87 | Widget buildCode() { 88 | return Container( 89 | padding: EdgeInsets.only(left: 8), 90 | decoration: BoxDecoration( 91 | color: SQColor.paper, 92 | borderRadius: BorderRadius.circular(5), 93 | ), 94 | child: Row( 95 | children: [ 96 | Flexible( 97 | child: TextField( 98 | controller: codeEditer, 99 | keyboardType: TextInputType.phone, 100 | style: TextStyle(fontSize: 14, color: SQColor.darkGray), 101 | decoration: InputDecoration( 102 | hintText: '请输入短信验证码', 103 | hintStyle: TextStyle(color: SQColor.gray), 104 | border: InputBorder.none, 105 | ), 106 | ), 107 | ), 108 | Container(color: Color(0xffdae3f2), width: 1, height: 40), 109 | Theme( 110 | data: ThemeData(primaryColor: SQColor.primary), 111 | child: CodeButton( 112 | onPressed: fetchSmsCode, 113 | coldDownSeconds: coldDownSeconds, 114 | ), 115 | ), 116 | ], 117 | ), 118 | ); 119 | } 120 | 121 | Widget buildBody() { 122 | return Column( 123 | children: [ 124 | SizedBox(height: 20), 125 | Container( 126 | margin: EdgeInsets.symmetric(horizontal: 15), 127 | child: Column( 128 | crossAxisAlignment: CrossAxisAlignment.stretch, 129 | children: [ 130 | buildPhone(), 131 | SizedBox(height: 10), 132 | buildCode(), 133 | SizedBox(height: 10), 134 | Container( 135 | decoration: BoxDecoration( 136 | borderRadius: BorderRadius.circular(5), 137 | color: SQColor.primary, 138 | ), 139 | height: 40, 140 | child: TextButton( 141 | onPressed: login, 142 | child: Text( 143 | '登录', 144 | style: TextStyle(fontSize: 16, color: Color(0xffffffff)), 145 | ), 146 | ), 147 | ), 148 | ], 149 | ), 150 | ) 151 | ], 152 | ); 153 | } 154 | 155 | @override 156 | Widget build(BuildContext context) { 157 | return Scaffold( 158 | appBar: AppBar(title: Text('登录'), elevation: 0), 159 | backgroundColor: Colors.white, 160 | body: buildBody(), 161 | ); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/me/me_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class MeCell extends StatelessWidget { 6 | final VoidCallback? onPressed; 7 | final String iconName; 8 | final String title; 9 | 10 | MeCell({required this.title, required this.iconName, this.onPressed}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return DefaultCell( 15 | title: title, 16 | leftWidget: Image.asset(iconName), 17 | isSeparatorVisible: true, 18 | isArrowVisible: true, 19 | onPressed: onPressed, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/me/me_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | class MeHeader extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | var user = UserManager.currentUser; 10 | 11 | return GestureDetector( 12 | onTap: () { 13 | if (UserManager.instance.isLogin) { 14 | AppNavigator.pushWeb(context, 'https://github.com/huanxsd/flutter_shuqi', 'Github'); 15 | } else { 16 | AppNavigator.pushLogin(context); 17 | } 18 | }, 19 | child: Container( 20 | color: SQColor.white, 21 | padding: EdgeInsets.fromLTRB(20, 30, 15, 15), 22 | child: Row( 23 | children: [ 24 | CircleAvatar( 25 | radius: 40, 26 | backgroundImage: (user?.avatarUrl != null ? CachedNetworkImageProvider(user!.avatarUrl) : AssetImage('img/placeholder_avatar.png')) as ImageProvider?, 27 | ), 28 | SizedBox(width: 25), 29 | Expanded( 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Text( 34 | user != null ? user.nickname : '登录', 35 | style: TextStyle(fontSize: 18), 36 | ), 37 | SizedBox(height: 10), 38 | buildItems(), 39 | ], 40 | ), 41 | ) 42 | ], 43 | ), 44 | ), 45 | ); 46 | } 47 | 48 | Widget buildItems() { 49 | var user = UserManager.currentUser; 50 | 51 | return Row( 52 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 53 | children: [ 54 | buildItem(user != null ? user.wealth.toStringAsFixed(1) : '0.0', '书豆余额'), 55 | buildItem(user != null ? user.coupon.toString() : '0', '书券(张)'), 56 | buildItem(user != null ? user.monthlyTicket.toString() : '0', '月票'), 57 | Container(), 58 | ], 59 | ); 60 | } 61 | 62 | Widget buildItem(String title, String subtitle) { 63 | return Column( 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | Text( 67 | title, 68 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 69 | ), 70 | SizedBox(height: 5), 71 | Text( 72 | subtitle, 73 | style: TextStyle(fontSize: 12, color: SQColor.gray), 74 | ), 75 | ], 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/me/me_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | import 'me_header.dart'; 6 | import 'setting_page.dart'; 7 | import 'me_cell.dart'; 8 | 9 | class MePage extends StatelessWidget { 10 | Widget buildCells(BuildContext context) { 11 | return Container( 12 | child: Column( 13 | children: [ 14 | MeCell( 15 | title: '钱包', 16 | iconName: 'img/me_wallet.png', 17 | onPressed: () {}, 18 | ), 19 | MeCell( 20 | title: '消费充值记录', 21 | iconName: 'img/me_record.png', 22 | onPressed: () {}, 23 | ), 24 | MeCell( 25 | title: '购买的书', 26 | iconName: 'img/me_buy.png', 27 | onPressed: () {}, 28 | ), 29 | MeCell( 30 | title: '我的会员', 31 | iconName: 'img/me_vip.png', 32 | onPressed: () {}, 33 | ), 34 | MeCell( 35 | title: '绑兑换码', 36 | iconName: 'img/me_coupon.png', 37 | onPressed: () {}, 38 | ), 39 | MeCell( 40 | title: '阅读之约', 41 | iconName: 'img/me_date.png', 42 | onPressed: () {}, 43 | ), 44 | MeCell( 45 | title: '我的收藏', 46 | iconName: 'img/me_favorite.png', 47 | onPressed: () {}, 48 | ), 49 | MeCell( 50 | title: '设置', 51 | iconName: 'img/me_setting.png', 52 | onPressed: () { 53 | Navigator.push(context, MaterialPageRoute(builder: (context) { 54 | return SettingPage(); 55 | })); 56 | }, 57 | ), 58 | MeCell( 59 | title: 'Flutter版 书旗小说', 60 | iconName: 'img/me_feedback.png', 61 | onPressed: () { 62 | AppNavigator.pushWeb(context, 'https://github.com/huanxsd/flutter_shuqi', 'Flutter'); 63 | }, 64 | ), 65 | MeCell( 66 | title: 'SwiftUI版 书旗小说', 67 | iconName: 'img/me_action.png', 68 | onPressed: () { 69 | AppNavigator.pushWeb(context, 'https://github.com/huanxsd/swiftui-shuqi-reader', 'SwiftUI'); 70 | }, 71 | ), 72 | ], 73 | ), 74 | ); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return Scaffold( 80 | body: AnnotatedRegion( 81 | value: SystemUiOverlayStyle.dark, 82 | child: Container( 83 | color: Colors.white, 84 | child: ListView( 85 | children: [ 86 | MeHeader(), 87 | SizedBox(height: 10), 88 | buildCells(context), 89 | ], 90 | ), 91 | ), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/me/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class SettingPage extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | List children = []; 9 | 10 | if (UserManager.instance.isLogin) { 11 | children.add(GestureDetector( 12 | onTap: () { 13 | Navigator.pop(context); 14 | UserManager.instance.logout(); 15 | }, 16 | child: Container( 17 | height: 50, 18 | color: Colors.white, 19 | child: Center( 20 | child: Text('退出登录', style: TextStyle(fontSize: 16, color: SQColor.red)), 21 | ), 22 | ), 23 | )); 24 | } 25 | return Scaffold( 26 | appBar: AppBar(title: Text('设置'), elevation: 0.5), 27 | body: Container( 28 | child: ListView( 29 | children: children, 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/me/web_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:webview_flutter/webview_flutter.dart'; 4 | import 'package:share/share.dart'; 5 | 6 | class WebPage extends StatefulWidget { 7 | final String url; 8 | final String? title; 9 | 10 | WebPage({required this.url, this.title}); 11 | 12 | @override 13 | _WebPageState createState() => _WebPageState(); 14 | } 15 | 16 | class _WebPageState extends State { 17 | WebViewController? controller; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | 23 | controller = WebViewController() 24 | ..setJavaScriptMode(JavaScriptMode.unrestricted) 25 | ..loadRequest(Uri.parse(widget.url)); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | systemOverlayStyle: SystemUiOverlayStyle.dark, 33 | backgroundColor: Colors.white, 34 | title: Text(widget.title ?? '', style: TextStyle(color: Colors.black87)), 35 | leading: IconButton( 36 | onPressed: () => Navigator.maybePop(context), 37 | icon: Icon(Icons.arrow_back_ios, color: Colors.black87), 38 | ), 39 | actions: [ 40 | GestureDetector( 41 | onTap: () { 42 | Share.share(this.widget.url); 43 | }, 44 | child: Image.asset('img/icon_menu_share.png'), 45 | ) 46 | ], 47 | ), 48 | body: WebViewWidget(controller: controller!), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/model/article.dart: -------------------------------------------------------------------------------- 1 | class Article { 2 | late int id; 3 | late int novelId; 4 | late String title; 5 | late String content; 6 | late int price; 7 | late int index; 8 | late int nextArticleId; 9 | late int preArticleId; 10 | 11 | late List> pageOffsets; 12 | 13 | Article.fromJson(Map data) { 14 | id = data['id']; 15 | novelId = data['novel_id']; 16 | title = data['title']; 17 | content = data['content']; 18 | content = '  ' + content; 19 | content = content.replaceAll('\n', '\n  '); 20 | price = data['welth'] ?? 0; 21 | index = data['index']; 22 | nextArticleId = data['next_id']; 23 | preArticleId = data['prev_id']; 24 | } 25 | 26 | String stringAtPageIndex(int index) { 27 | var offset = pageOffsets[index]; 28 | return this.content.substring(offset['start']!, offset['end']); 29 | } 30 | 31 | int get pageCount { 32 | return pageOffsets.length; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/model/chapter.dart: -------------------------------------------------------------------------------- 1 | class Chapter { 2 | late int id; 3 | late String title; 4 | late int index; 5 | 6 | Chapter.fromJson(Map data) { 7 | id = data['id']; 8 | title = data['title']; 9 | index = data['index'] ?? 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/model/novel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuqi/app/sq_color.dart'; 3 | 4 | import 'chapter.dart'; 5 | 6 | class Novel { 7 | late String id; 8 | late String name; 9 | late String imgUrl; 10 | late String firstChapter; 11 | late Chapter lastChapter; 12 | late String author; 13 | late double price; 14 | late double score; 15 | late String type; 16 | late String introduction; 17 | late int chapterCount; 18 | late int recommendCount; 19 | late int commentCount; 20 | late int firstArticleId; 21 | 22 | late List roles; 23 | late String status; 24 | late double wordCount; 25 | late List tags; 26 | late bool isLimitedFree; 27 | 28 | Novel.fromJson(Map data) { 29 | id = data['bid'] ?? ''; 30 | firstArticleId = data['first_article_id'] ?? 0; 31 | name = data['bookname']; 32 | imgUrl = data['book_cover']; 33 | firstChapter = data['topic_first'] ?? ''; 34 | if (data['lastChapter'] != null) { 35 | lastChapter = Chapter.fromJson(data['lastChapter']); 36 | } 37 | score = data['score'] ?? 0; 38 | author = data['author_name']; 39 | price = double.parse(data['price'] ?? '0'); 40 | type = data['class_name'] ?? ''; 41 | introduction = data['introduction'] ?? ''; 42 | chapterCount = int.parse(data['chapterNum'] ?? '0'); 43 | recommendCount = int.parse(data['recommend_num'] ?? '0'); 44 | commentCount = int.parse(data['comment_count'] ?? '0'); 45 | 46 | status = data['stat_name'] ?? ''; 47 | wordCount = data['wordCount'] ?? 0; 48 | tags = (data['tag'] ?? []).cast()?.toList(); 49 | 50 | isLimitedFree = data['is_free'] == 1; 51 | } 52 | 53 | String recommendCountStr() { 54 | if (recommendCount >= 10000) { 55 | return (recommendCount / 10000).toStringAsFixed(1) + '万人推荐'; 56 | } else { 57 | return recommendCount.toString() + '人推荐'; 58 | } 59 | } 60 | 61 | Color statusColor() { 62 | return status == '连载' ? SQColor.blue : SQColor.primary; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/model/novel_comment.dart: -------------------------------------------------------------------------------- 1 | class NovelComment { 2 | late String nickname; 3 | late String avatar; 4 | late String content; 5 | 6 | NovelComment.fromJson(Map data) { 7 | nickname = data['nickName']; 8 | avatar = data['userPhoto']; 9 | content = data['text']; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_comment_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | class NovelCommentCell extends StatelessWidget { 7 | final NovelComment comment; 8 | 9 | NovelCommentCell(this.comment); 10 | 11 | like() {} 12 | 13 | Widget buildButton(String image, String title, VoidCallback onPress, bool isSelected) { 14 | return Row( 15 | children: [ 16 | Image.asset(image), 17 | SizedBox(width: 5), 18 | Text( 19 | title, 20 | style: TextStyle(fontSize: 14, color: isSelected ? Color(0xfff5a623) : SQColor.gray), 21 | ) 22 | ], 23 | ); 24 | } 25 | 26 | Widget buildContent() { 27 | return Container( 28 | padding: EdgeInsets.all(15), 29 | child: Column( 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | Row( 33 | children: [ 34 | CircleAvatar( 35 | radius: 13, 36 | backgroundImage: CachedNetworkImageProvider(comment.avatar), 37 | ), 38 | SizedBox(width: 10), 39 | Text(comment.nickname, style: TextStyle(fontSize: 14, color: SQColor.gray)), 40 | ], 41 | ), 42 | Container( 43 | padding: EdgeInsets.fromLTRB(35, 15, 15, 0), 44 | child: Text(comment.content, style: TextStyle(fontSize: 16)), 45 | ), 46 | ], 47 | ), 48 | ); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Column( 54 | children: [ 55 | buildContent(), 56 | Divider(height: 1, indent: 15), 57 | ], 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_detail_cell.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class NovelDetailCell extends StatelessWidget { 6 | final String iconName; 7 | final String title; 8 | final String subtitle; 9 | final Widget? attachedWidget; 10 | 11 | NovelDetailCell({required this.iconName, required this.title, required this.subtitle, this.attachedWidget}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | color: Colors.white, 17 | padding: EdgeInsets.symmetric(horizontal: 15), 18 | child: Column( 19 | children: [ 20 | Container( 21 | height: 50, 22 | child: Row( 23 | children: [ 24 | Image.asset(iconName), 25 | SizedBox(width: 5), 26 | Text(title, style: TextStyle(fontSize: 16)), 27 | SizedBox(width: 10), 28 | Expanded( 29 | child: Text( 30 | subtitle, 31 | style: TextStyle(fontSize: 14, color: SQColor.gray), 32 | maxLines: 1, 33 | overflow: TextOverflow.ellipsis, 34 | ), 35 | ), 36 | attachedWidget != null ? attachedWidget! : Container(), 37 | SizedBox(width: 10), 38 | Image.asset('img/arrow_right.png'), 39 | ], 40 | ), 41 | ), 42 | Divider(height: 1), 43 | ], 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_detail_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'dart:ui' as ui; 4 | 5 | import 'package:shuqi/public.dart'; 6 | 7 | class NovelDetailHeader extends StatelessWidget { 8 | final Novel novel; 9 | NovelDetailHeader(this.novel); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | var width = Screen.width; 14 | var height = 218.0 + Screen.topSafeHeight; 15 | return Container( 16 | width: width, 17 | height: height, 18 | child: Stack( 19 | children: [ 20 | Image( 21 | image: CachedNetworkImageProvider(novel.imgUrl), 22 | fit: BoxFit.fitWidth, 23 | width: width, 24 | height: height, 25 | ), 26 | Container(color: Color(0xbb000000), width: width, height: height), 27 | BackdropFilter( 28 | filter: ui.ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), 29 | child: buildContent(context), 30 | ), 31 | ], 32 | ), 33 | ); 34 | } 35 | 36 | Widget buildContent(BuildContext context) { 37 | var width = Screen.width; 38 | return Container( 39 | width: width, 40 | padding: EdgeInsets.fromLTRB(15, 54 + Screen.topSafeHeight, 10, 0), 41 | color: Colors.transparent, 42 | child: Row( 43 | crossAxisAlignment: CrossAxisAlignment.start, 44 | children: [ 45 | NovelCoverImage(novel.imgUrl, width: 100, height: 133), 46 | SizedBox(width: 15), 47 | Expanded( 48 | child: Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | Text(novel.name, style: TextStyle(fontSize: Utility.fixedFontSize(18), color: Colors.white, fontWeight: FontWeight.bold)), 52 | SizedBox(height: 10), 53 | Text(novel.author, style: TextStyle(fontSize: Utility.fixedFontSize(14), color: SQColor.white)), 54 | SizedBox(height: 10), 55 | Text('${novel.wordCount}万字 ${novel.price}书豆/千字', style: TextStyle(fontSize: Utility.fixedFontSize(14), color: SQColor.white)), 56 | SizedBox(height: 10), 57 | buildScore(), 58 | SizedBox(height: 10), 59 | Row( 60 | crossAxisAlignment: CrossAxisAlignment.center, 61 | children: [ 62 | Image.asset('img/read_icon_vip.png'), 63 | Expanded( 64 | child: Text( 65 | ' 续费包月会员,万本小说免费读 >', 66 | style: TextStyle(fontSize: Utility.fixedFontSize(14), color: Color(0xFFFEA900)), 67 | maxLines: 1, 68 | ), 69 | ), 70 | ], 71 | ), 72 | ], 73 | ), 74 | ) 75 | ], 76 | ), 77 | ); 78 | } 79 | 80 | Widget buildScore() { 81 | List children = [Text('评分:${novel.score}分 ', style: TextStyle(fontSize: Utility.fixedFontSize(14), color: Color(0xfff8e71c)))]; 82 | 83 | var star = novel.score; 84 | 85 | for (var i = 0; i < 5; i++) { 86 | if (star < i) { 87 | break; 88 | } 89 | var img; 90 | if (star <= i + 0.5) { 91 | img = Image.asset('img/detail_star_half.png'); 92 | } else { 93 | img = Image.asset('img/detail_star.png'); 94 | } 95 | children.add(img); 96 | children.add(SizedBox(width: 5)); 97 | } 98 | return Row( 99 | children: children, 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | import 'novel_detail_header.dart'; 7 | import 'novel_summary_view.dart'; 8 | import 'novel_detail_toolbar.dart'; 9 | import 'novel_detail_recommend_view.dart'; 10 | import 'novel_detail_cell.dart'; 11 | import 'novel_comment_cell.dart'; 12 | 13 | class NovelDetailPage extends StatefulWidget { 14 | final String novelId; 15 | 16 | NovelDetailPage(this.novelId); 17 | 18 | @override 19 | NovelDetailPageState createState() => NovelDetailPageState(); 20 | } 21 | 22 | class NovelDetailPageState extends State with RouteAware { 23 | Novel? novel; 24 | List recommendNovels = []; 25 | List comments = []; 26 | ScrollController scrollController = ScrollController(); 27 | double navAlpha = 0; 28 | bool isSummaryUnfold = false; 29 | int commentCount = 0; 30 | int commentMemberCount = 0; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | fetchData(); 36 | 37 | scrollController.addListener(() { 38 | var offset = scrollController.offset; 39 | if (offset < 0) { 40 | if (navAlpha != 0) { 41 | setState(() { 42 | navAlpha = 0; 43 | }); 44 | } 45 | } else if (offset < 50) { 46 | setState(() { 47 | navAlpha = 1 - (50 - offset) / 50; 48 | }); 49 | } else if (navAlpha != 1) { 50 | setState(() { 51 | navAlpha = 1; 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | @override 58 | void dispose() { 59 | scrollController.dispose(); 60 | super.dispose(); 61 | } 62 | 63 | changeSummaryMaxLines() { 64 | setState(() { 65 | isSummaryUnfold = !isSummaryUnfold; 66 | }); 67 | } 68 | 69 | back() { 70 | Navigator.pop(context); 71 | } 72 | 73 | fetchData() async { 74 | var novelId = this.widget.novelId; 75 | 76 | var novelResponse = await Request.post(action: 'novel_detail', params: {'id': novelId}); 77 | 78 | var commentsResponse = await Request.post(action: 'novel_comment', params: {'id': novelId}); 79 | List comments = []; 80 | commentsResponse.forEach((data) { 81 | comments.add(NovelComment.fromJson(data)); 82 | }); 83 | 84 | var recommendResponse = await Request.post(action: 'novel_recommend', params: {'id': novelId}); 85 | List recommendNovels = []; 86 | recommendResponse.forEach((data) { 87 | recommendNovels.add(Novel.fromJson(data)); 88 | }); 89 | 90 | setState(() { 91 | this.novel = Novel.fromJson(novelResponse); 92 | this.comments = comments; 93 | this.recommendNovels = recommendNovels; 94 | }); 95 | } 96 | 97 | Widget buildNavigationBar() { 98 | return Stack( 99 | children: [ 100 | Container( 101 | width: 44, 102 | height: Screen.navigationBarHeight, 103 | padding: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 0, 0), 104 | child: GestureDetector(onTap: back, child: Image.asset('img/pub_back_white.png')), 105 | ), 106 | Opacity( 107 | opacity: navAlpha, 108 | child: Container( 109 | decoration: BoxDecoration(color: SQColor.white, boxShadow: Styles.borderShadow), 110 | padding: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 0, 0), 111 | height: Screen.navigationBarHeight, 112 | child: Row( 113 | children: [ 114 | Container( 115 | width: 44, 116 | child: GestureDetector(onTap: back, child: Image.asset('img/pub_back_gray.png')), 117 | ), 118 | Expanded( 119 | child: Text( 120 | novel!.name, 121 | style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), 122 | textAlign: TextAlign.center, 123 | ), 124 | ), 125 | Container(width: 44), 126 | ], 127 | ), 128 | ), 129 | ) 130 | ], 131 | ); 132 | } 133 | 134 | Widget buildComment() { 135 | return Container( 136 | color: Colors.white, 137 | child: Column( 138 | crossAxisAlignment: CrossAxisAlignment.start, 139 | children: [ 140 | Container( 141 | padding: EdgeInsets.symmetric(vertical: 15), 142 | child: Row( 143 | children: [ 144 | Image.asset('img/home_tip.png'), 145 | SizedBox(width: 13), 146 | Text('书友评价', style: TextStyle(fontSize: 16)), 147 | Expanded(child: Container()), 148 | Image.asset('img/detail_write_comment.png'), 149 | Text(' 写书评', style: TextStyle(fontSize: 14, color: SQColor.primary)), 150 | SizedBox(width: 15), 151 | ], 152 | ), 153 | ), 154 | Divider(height: 1), 155 | Column( 156 | children: comments.map((comment) => NovelCommentCell(comment)).toList(), 157 | ), 158 | Container( 159 | padding: EdgeInsets.symmetric(vertical: 15), 160 | child: Center( 161 | child: Text( 162 | '查看全部评论(${novel!.commentCount}条)', 163 | style: TextStyle(fontSize: 14, color: SQColor.gray), 164 | ), 165 | ), 166 | ) 167 | ], 168 | ), 169 | ); 170 | } 171 | 172 | Widget buildTags() { 173 | var colors = [Color(0xFFF9A19F), Color(0xFF59DDB9), Color(0xFF7EB3E7)]; 174 | var i = 0; 175 | var tagWidgets = novel!.tags.map((tag) { 176 | var color = colors[i % 3]; 177 | var tagWidget = Container( 178 | decoration: BoxDecoration( 179 | border: Border.all(color: Color.fromARGB(99, color.red, color.green, color.blue), width: 0.5), 180 | borderRadius: BorderRadius.circular(3), 181 | ), 182 | padding: EdgeInsets.fromLTRB(6, 3, 6, 3), 183 | child: Text(tag, style: TextStyle(fontSize: 14, color: colors[i % 3])), 184 | ); 185 | i++; 186 | return tagWidget; 187 | }).toList(); 188 | return Container( 189 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 190 | color: SQColor.white, 191 | child: Wrap(runSpacing: 10, spacing: 10, children: tagWidgets), 192 | ); 193 | } 194 | 195 | @override 196 | Widget build(BuildContext context) { 197 | if (this.novel == null) { 198 | return Scaffold(appBar: AppBar(elevation: 0)); 199 | } 200 | var novel = this.novel!; 201 | return Scaffold( 202 | body: AnnotatedRegion( 203 | value: navAlpha > 0.5 ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light, 204 | child: Stack( 205 | children: [ 206 | Column( 207 | children: [ 208 | Expanded( 209 | child: ListView( 210 | controller: scrollController, 211 | padding: EdgeInsets.only(top: 0), 212 | children: [ 213 | NovelDetailHeader(novel), 214 | NovelSummaryView(novel.introduction, isSummaryUnfold, changeSummaryMaxLines), 215 | NovelDetailCell( 216 | iconName: 'img/detail_latest.png', 217 | title: '最新', 218 | subtitle: novel.lastChapter.title, 219 | attachedWidget: Text(novel.status, style: TextStyle(fontSize: 14, color: novel.statusColor())), 220 | ), 221 | NovelDetailCell( 222 | iconName: 'img/detail_chapter.png', 223 | title: '目录', 224 | subtitle: '共${novel.chapterCount}章', 225 | ), 226 | buildTags(), 227 | SizedBox(height: 10), 228 | buildComment(), 229 | SizedBox(height: 10), 230 | NovelDetailRecommendView(recommendNovels), 231 | ], 232 | ), 233 | ), 234 | NovelDetailToolbar(novel), 235 | ], 236 | ), 237 | buildNavigationBar(), 238 | ], 239 | ), 240 | ), 241 | ); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_detail_recommend_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | import 'package:shuqi/home/home_novel_cover_view.dart'; 6 | 7 | class NovelDetailRecommendView extends StatelessWidget { 8 | final List novels; 9 | 10 | NovelDetailRecommendView(this.novels); 11 | 12 | Widget buildItems() { 13 | var children = novels.map((novel) => HomeNovelCoverView(novel)).toList(); 14 | return Container( 15 | padding: EdgeInsets.symmetric(horizontal: 15), 16 | child: Wrap(spacing: 15, runSpacing: 20, children: children), 17 | ); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | color: Colors.white, 24 | padding: EdgeInsets.fromLTRB(0, 0, 0, 15), 25 | child: Column( 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | Container( 29 | padding: EdgeInsets.symmetric(vertical: 15), 30 | child: Row( 31 | children: [ 32 | Image.asset('img/home_tip.png'), 33 | SizedBox(width: 13), 34 | Text('看过本书的人还在看', style: TextStyle(fontSize: 16)), 35 | ], 36 | ), 37 | ), 38 | buildItems(), 39 | ], 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_detail_toolbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | 5 | class NovelDetailToolbar extends StatelessWidget { 6 | final Novel novel; 7 | NovelDetailToolbar(this.novel); 8 | 9 | read() {} 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.only(bottom: Screen.bottomSafeHeight), 14 | decoration: BoxDecoration(color: Colors.white, boxShadow: Styles.borderShadow), 15 | height: 50 + Screen.bottomSafeHeight, 16 | child: Row(children: [ 17 | Expanded( 18 | child: Center( 19 | child: Text( 20 | '加书架', 21 | style: TextStyle(fontSize: 16, color: SQColor.primary), 22 | ), 23 | ), 24 | ), 25 | Expanded( 26 | child: GestureDetector( 27 | onTap: () { 28 | AppNavigator.pushReader(context, novel.firstArticleId); 29 | }, 30 | child: Container( 31 | height: 40, 32 | decoration: BoxDecoration(color: SQColor.primary, borderRadius: BorderRadius.circular(5)), 33 | child: Center( 34 | child: Text( 35 | '开始阅读', 36 | style: TextStyle(fontSize: 16, color: Colors.white), 37 | ), 38 | ), 39 | ), 40 | ), 41 | ), 42 | Expanded( 43 | child: Center( 44 | child: Text( 45 | '下载', 46 | style: TextStyle(fontSize: 16, color: SQColor.primary), 47 | ), 48 | ), 49 | ), 50 | ]), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/novel_detail/novel_summary_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NovelSummaryView extends StatelessWidget { 4 | final String summary; 5 | final bool isUnfold; 6 | final VoidCallback onPressed; 7 | 8 | NovelSummaryView(this.summary, this.isUnfold, this.onPressed); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GestureDetector( 13 | onTap: onPressed, 14 | child: Column( 15 | children: [ 16 | Container( 17 | color: Colors.white, 18 | padding: EdgeInsets.fromLTRB(15, 15, 15, 15), 19 | child: Stack( 20 | alignment: AlignmentDirectional.bottomEnd, 21 | children: [ 22 | Text( 23 | summary, 24 | maxLines: isUnfold ? null : 3, 25 | style: TextStyle(fontSize: 14), 26 | ), 27 | Image.asset('img/detail_fold_bg.png'), 28 | Image.asset(isUnfold ? 'img/detail_up.png' : 'img/detail_down.png'), 29 | ], 30 | ), 31 | ), 32 | Divider(height: 1, indent: 15), 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/public.dart: -------------------------------------------------------------------------------- 1 | library public; 2 | 3 | // Vendor 4 | export 'package:je_kit/je_kit.dart'; 5 | 6 | // Global 7 | export 'package:shuqi/global.dart'; 8 | 9 | // Utility 10 | export 'package:shuqi/utility/event_bus.dart'; 11 | export 'package:shuqi/utility/styles.dart'; 12 | 13 | // Widget 14 | export 'package:shuqi/widget/novel_cover_image.dart'; 15 | export 'package:shuqi/widget/novel_cover_view.dart'; 16 | 17 | // App 18 | export 'package:shuqi/app/constant.dart'; 19 | export 'package:shuqi/app/request.dart'; 20 | export 'package:shuqi/app/app_navigator.dart'; 21 | export 'package:shuqi/app/sq_color.dart'; 22 | export 'package:shuqi/app/user_manager.dart'; 23 | export 'package:shuqi/app/app_scene.dart'; 24 | 25 | // Model 26 | export 'package:shuqi/model/novel.dart'; 27 | export 'package:shuqi/model/chapter.dart'; 28 | export 'package:shuqi/model/novel_comment.dart'; 29 | export 'package:shuqi/model/article.dart'; 30 | -------------------------------------------------------------------------------- /lib/reader/article_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuqi/public.dart'; 2 | 3 | class ArticleProvider { 4 | static Future
fetchArticle(int articleId) async { 5 | var response = await Request.get(action: 'article_$articleId'); 6 | var article = Article.fromJson(response); 7 | 8 | return article; 9 | } 10 | } -------------------------------------------------------------------------------- /lib/reader/battery_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:battery/battery.dart'; 3 | import 'package:device_info/device_info.dart'; 4 | import 'dart:io'; 5 | 6 | import 'package:shuqi/public.dart'; 7 | 8 | class BatteryView extends StatefulWidget { 9 | @override 10 | _BatteryViewState createState() => _BatteryViewState(); 11 | } 12 | 13 | class _BatteryViewState extends State { 14 | double batteryLevel = 0; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | getBatteryLevel(); 21 | } 22 | 23 | getBatteryLevel() async { 24 | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); 25 | if (Platform.isAndroid) { 26 | var androidInfo = await deviceInfo.androidInfo; 27 | if (!androidInfo.isPhysicalDevice) { 28 | return; 29 | } 30 | } 31 | if (Platform.isIOS) { 32 | var iosInfo = await deviceInfo.iosInfo; 33 | if (!iosInfo.isPhysicalDevice) { 34 | return; 35 | } 36 | } 37 | 38 | var level = await Battery().batteryLevel; 39 | setState(() { 40 | this.batteryLevel = level / 100.0; 41 | }); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Container( 47 | width: 27, 48 | height: 12, 49 | child: Stack( 50 | children: [ 51 | Image.asset('img/reader_battery.png'), 52 | Container( 53 | margin: EdgeInsets.fromLTRB(2, 2, 2, 2), 54 | width: 20 * batteryLevel, 55 | color: SQColor.golden, 56 | ) 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/reader/reader_config.dart: -------------------------------------------------------------------------------- 1 | class ReaderConfig { 2 | static ReaderConfig instance = ReaderConfig(); 3 | 4 | double fontSize = 20.0; 5 | } 6 | -------------------------------------------------------------------------------- /lib/reader/reader_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | 6 | class ReaderMenu extends StatefulWidget { 7 | final List chapters; 8 | final int articleIndex; 9 | 10 | final VoidCallback onTap; 11 | final VoidCallback onPreviousArticle; 12 | final VoidCallback onNextArticle; 13 | final void Function(Chapter chapter) onToggleChapter; 14 | 15 | ReaderMenu({required this.chapters, required this.articleIndex, required this.onTap, required this.onPreviousArticle, required this.onNextArticle, required this.onToggleChapter}); 16 | 17 | @override 18 | _ReaderMenuState createState() => _ReaderMenuState(); 19 | } 20 | 21 | class _ReaderMenuState extends State with SingleTickerProviderStateMixin { 22 | late AnimationController animationController; 23 | late Animation animation; 24 | 25 | late double progressValue; 26 | bool isTipVisible = false; 27 | 28 | @override 29 | initState() { 30 | super.initState(); 31 | 32 | progressValue = this.widget.articleIndex / (this.widget.chapters.length - 1); 33 | animationController = AnimationController(duration: const Duration(milliseconds: 200), vsync: this); 34 | animation = Tween(begin: 0.0, end: 1.0).animate(animationController); 35 | animation.addListener(() { 36 | setState(() {}); 37 | }); 38 | animationController.forward(); 39 | } 40 | 41 | @override 42 | void didUpdateWidget(ReaderMenu oldWidget) { 43 | super.didUpdateWidget(oldWidget); 44 | progressValue = this.widget.articleIndex / (this.widget.chapters.length - 1); 45 | } 46 | 47 | @override 48 | void dispose() { 49 | animationController.dispose(); 50 | super.dispose(); 51 | } 52 | 53 | hide() { 54 | animationController.reverse(); 55 | Timer(Duration(milliseconds: 200), () { 56 | this.widget.onTap(); 57 | }); 58 | setState(() { 59 | isTipVisible = false; 60 | }); 61 | } 62 | 63 | buildTopView(BuildContext context) { 64 | return Positioned( 65 | top: -Screen.navigationBarHeight * (1 - animation.value), 66 | left: 0, 67 | right: 0, 68 | child: Container( 69 | decoration: BoxDecoration(color: SQColor.paper, boxShadow: Styles.borderShadow), 70 | height: Screen.navigationBarHeight, 71 | padding: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 5, 0), 72 | child: Row( 73 | children: [ 74 | Container( 75 | width: 44, 76 | child: GestureDetector( 77 | onTap: () { 78 | Navigator.pop(context); 79 | }, 80 | child: Image.asset('img/pub_back_gray.png'), 81 | ), 82 | ), 83 | Expanded(child: Container()), 84 | Container( 85 | width: 44, 86 | child: Image.asset('img/read_icon_voice.png'), 87 | ), 88 | Container( 89 | width: 44, 90 | child: Image.asset('img/read_icon_more.png'), 91 | ), 92 | ], 93 | ), 94 | ), 95 | ); 96 | } 97 | 98 | int currentArticleIndex() { 99 | return ((this.widget.chapters.length - 1) * progressValue).toInt(); 100 | } 101 | 102 | buildProgressTipView() { 103 | if (!isTipVisible) { 104 | return Container(); 105 | } 106 | Chapter chapter = this.widget.chapters[currentArticleIndex()]; 107 | double percentage = chapter.index / (this.widget.chapters.length - 1) * 100; 108 | return Container( 109 | decoration: BoxDecoration(color: Color(0xff00C88D), borderRadius: BorderRadius.circular(5)), 110 | margin: EdgeInsets.fromLTRB(15, 0, 15, 10), 111 | padding: EdgeInsets.all(15), 112 | child: Row( 113 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 114 | children: [ 115 | Text(chapter.title, style: TextStyle(color: Colors.white, fontSize: 16)), 116 | Text('${percentage.toStringAsFixed(1)}%', style: TextStyle(color: SQColor.lightGray, fontSize: 12)), 117 | ], 118 | ), 119 | ); 120 | } 121 | 122 | previousArticle() { 123 | if (this.widget.articleIndex == 0) { 124 | Toast.show('已经是第一章了'); 125 | return; 126 | } 127 | this.widget.onPreviousArticle(); 128 | setState(() { 129 | isTipVisible = true; 130 | }); 131 | } 132 | 133 | nextArticle() { 134 | if (this.widget.articleIndex == this.widget.chapters.length - 1) { 135 | Toast.show('已经是最后一章了'); 136 | return; 137 | } 138 | this.widget.onNextArticle(); 139 | setState(() { 140 | isTipVisible = true; 141 | }); 142 | } 143 | 144 | buildProgressView() { 145 | return Container( 146 | padding: EdgeInsets.fromLTRB(5, 0, 5, 0), 147 | child: Row( 148 | children: [ 149 | GestureDetector( 150 | onTap: previousArticle, 151 | child: Container( 152 | padding: EdgeInsets.all(20), 153 | child: Image.asset('img/read_icon_chapter_previous.png'), 154 | ), 155 | ), 156 | Expanded( 157 | child: Slider( 158 | value: progressValue, 159 | onChanged: (double value) { 160 | setState(() { 161 | isTipVisible = true; 162 | progressValue = value; 163 | }); 164 | }, 165 | onChangeEnd: (double value) { 166 | Chapter chapter = this.widget.chapters[currentArticleIndex()]; 167 | this.widget.onToggleChapter(chapter); 168 | }, 169 | activeColor: SQColor.primary, 170 | inactiveColor: SQColor.gray, 171 | ), 172 | ), 173 | GestureDetector( 174 | onTap: nextArticle, 175 | child: Container( 176 | padding: EdgeInsets.all(20), 177 | child: Image.asset('img/read_icon_chapter_next.png'), 178 | ), 179 | ) 180 | ], 181 | ), 182 | ); 183 | } 184 | 185 | buildBottomView() { 186 | return Positioned( 187 | bottom: -(Screen.bottomSafeHeight + 110) * (1 - animation.value), 188 | left: 0, 189 | right: 0, 190 | child: Column( 191 | children: [ 192 | buildProgressTipView(), 193 | Container( 194 | decoration: BoxDecoration(color: SQColor.paper, boxShadow: Styles.borderShadow), 195 | padding: EdgeInsets.only(bottom: Screen.bottomSafeHeight), 196 | child: Column( 197 | children: [ 198 | buildProgressView(), 199 | buildBottomMenus(), 200 | ], 201 | ), 202 | ) 203 | ], 204 | ), 205 | ); 206 | } 207 | 208 | buildBottomMenus() { 209 | return Row( 210 | mainAxisAlignment: MainAxisAlignment.spaceAround, 211 | children: [ 212 | buildBottomItem('目录', 'img/read_icon_catalog.png'), 213 | buildBottomItem('亮度', 'img/read_icon_brightness.png'), 214 | buildBottomItem('字体', 'img/read_icon_font.png'), 215 | buildBottomItem('设置', 'img/read_icon_setting.png'), 216 | ], 217 | ); 218 | } 219 | 220 | buildBottomItem(String title, String icon) { 221 | return Container( 222 | padding: EdgeInsets.symmetric(vertical: 7), 223 | child: Column( 224 | children: [ 225 | Image.asset(icon), 226 | SizedBox(height: 5), 227 | Text(title, style: TextStyle(fontSize: Utility.fixedFontSize(12), color: SQColor.darkGray)), 228 | ], 229 | ), 230 | ); 231 | } 232 | 233 | @override 234 | Widget build(BuildContext context) { 235 | return Container( 236 | child: Stack( 237 | children: [ 238 | GestureDetector( 239 | onTapDown: (_) { 240 | hide(); 241 | }, 242 | child: Container(color: Colors.transparent), 243 | ), 244 | buildTopView(context), 245 | buildBottomView(), 246 | ], 247 | ), 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /lib/reader/reader_overlayer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | import 'package:shuqi/public.dart'; 5 | import 'battery_view.dart'; 6 | 7 | class ReaderOverlayer extends StatelessWidget { 8 | final Article article; 9 | final int page; 10 | final double topSafeHeight; 11 | 12 | ReaderOverlayer({required this.article, required this.page, required this.topSafeHeight}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | var format = DateFormat('HH:mm'); 17 | var time = format.format(DateTime.now()); 18 | 19 | return Container( 20 | padding: EdgeInsets.fromLTRB(15, 10 + topSafeHeight, 15, 10 + Screen.bottomSafeHeight), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Text(article.title, style: TextStyle(fontSize: Utility.fixedFontSize(14), color: SQColor.golden)), 25 | Expanded(child: Container()), 26 | Row( 27 | children: [ 28 | BatteryView(), 29 | SizedBox(width: 10), 30 | Text(time, style: TextStyle(fontSize: Utility.fixedFontSize(11), color: SQColor.golden)), 31 | Expanded(child: Container()), 32 | Text('第${page + 1}页', style: TextStyle(fontSize: Utility.fixedFontSize(11), color: SQColor.golden)), 33 | ], 34 | ), 35 | ], 36 | ), 37 | ); 38 | } 39 | } -------------------------------------------------------------------------------- /lib/reader/reader_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'dart:async'; 4 | 5 | import 'package:shuqi/public.dart'; 6 | 7 | import 'article_provider.dart'; 8 | import 'reader_utils.dart'; 9 | import 'reader_config.dart'; 10 | 11 | import 'reader_page_agent.dart'; 12 | import 'reader_menu.dart'; 13 | import 'reader_view.dart'; 14 | 15 | enum PageJumpType { stay, firstPage, lastPage } 16 | 17 | class ReaderPage extends StatefulWidget { 18 | final int articleId; 19 | 20 | ReaderPage({required this.articleId}); 21 | 22 | @override 23 | ReaderPageState createState() => ReaderPageState(); 24 | } 25 | 26 | class ReaderPageState extends State with RouteAware { 27 | int pageIndex = 0; 28 | bool isMenuVisiable = false; 29 | PageController pageController = PageController(keepPage: false); 30 | bool isLoading = false; 31 | 32 | double topSafeHeight = 0; 33 | 34 | Article? preArticle; 35 | Article? currentArticle; 36 | Article? nextArticle; 37 | 38 | List chapters = []; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | pageController.addListener(onScroll); 44 | 45 | setup(); 46 | } 47 | 48 | @override 49 | void didChangeDependencies() { 50 | super.didChangeDependencies(); 51 | routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); 52 | } 53 | 54 | @override 55 | void didPop() { 56 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | pageController.dispose(); 62 | routeObserver.unsubscribe(this); 63 | super.dispose(); 64 | } 65 | 66 | void setup() async { 67 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); 68 | // 不延迟的话,安卓获取到的topSafeHeight是错的。 69 | await Future.delayed(const Duration(milliseconds: 100), () {}); 70 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); 71 | 72 | topSafeHeight = Screen.topSafeHeight; 73 | 74 | List chaptersResponse = await Request.get(action: 'catalog'); 75 | chaptersResponse.forEach((data) { 76 | chapters.add(Chapter.fromJson(data)); 77 | }); 78 | 79 | await resetContent(this.widget.articleId, PageJumpType.stay); 80 | } 81 | 82 | resetContent(int articleId, PageJumpType jumpType) async { 83 | currentArticle = await fetchArticle(articleId); 84 | if (currentArticle!.preArticleId > 0) { 85 | preArticle = await fetchArticle(currentArticle!.preArticleId); 86 | } else { 87 | preArticle = null; 88 | } 89 | if (currentArticle!.nextArticleId > 0) { 90 | nextArticle = await fetchArticle(currentArticle!.nextArticleId); 91 | } else { 92 | nextArticle = null; 93 | } 94 | if (jumpType == PageJumpType.firstPage) { 95 | pageIndex = 0; 96 | } else if (jumpType == PageJumpType.lastPage) { 97 | pageIndex = currentArticle!.pageCount - 1; 98 | } 99 | if (jumpType != PageJumpType.stay) { 100 | pageController.jumpToPage((preArticle != null ? preArticle!.pageCount : 0) + pageIndex); 101 | } 102 | 103 | setState(() {}); 104 | } 105 | 106 | onScroll() { 107 | var page = pageController.offset / Screen.width; 108 | 109 | var nextArtilePage = currentArticle!.pageCount + (preArticle != null ? preArticle!.pageCount : 0); 110 | if (page >= nextArtilePage) { 111 | print('到达下个章节了'); 112 | 113 | preArticle = currentArticle; 114 | currentArticle = nextArticle; 115 | nextArticle = null; 116 | pageIndex = 0; 117 | pageController.jumpToPage(preArticle!.pageCount); 118 | fetchNextArticle(currentArticle!.nextArticleId); 119 | setState(() {}); 120 | } 121 | if (preArticle != null && page <= preArticle!.pageCount - 1) { 122 | print('到达上个章节了'); 123 | 124 | nextArticle = currentArticle; 125 | currentArticle = preArticle; 126 | preArticle = null; 127 | pageIndex = currentArticle!.pageCount - 1; 128 | pageController.jumpToPage(currentArticle!.pageCount - 1); 129 | fetchPreviousArticle(currentArticle!.preArticleId); 130 | setState(() {}); 131 | } 132 | } 133 | 134 | fetchPreviousArticle(int articleId) async { 135 | if (preArticle != null || isLoading || articleId == 0) { 136 | return; 137 | } 138 | isLoading = true; 139 | preArticle = await fetchArticle(articleId); 140 | pageController.jumpToPage(preArticle!.pageCount + pageIndex); 141 | isLoading = false; 142 | setState(() {}); 143 | } 144 | 145 | fetchNextArticle(int articleId) async { 146 | if (nextArticle != null || isLoading || articleId == 0) { 147 | return; 148 | } 149 | isLoading = true; 150 | nextArticle = await fetchArticle(articleId); 151 | isLoading = false; 152 | setState(() {}); 153 | } 154 | 155 | Future
fetchArticle(int articleId) async { 156 | var article = await ArticleProvider.fetchArticle(articleId); 157 | var contentHeight = Screen.height - topSafeHeight - ReaderUtils.topOffset - Screen.bottomSafeHeight - ReaderUtils.bottomOffset - 20; 158 | var contentWidth = Screen.width - 15 - 10; 159 | article.pageOffsets = ReaderPageAgent.getPageOffsets(article.content, contentHeight, contentWidth, ReaderConfig.instance.fontSize); 160 | 161 | return article; 162 | } 163 | 164 | onTap(Offset position) async { 165 | double xRate = position.dx / Screen.width; 166 | if (xRate > 0.33 && xRate < 0.66) { 167 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 168 | setState(() { 169 | isMenuVisiable = true; 170 | }); 171 | } else if (xRate >= 0.66) { 172 | nextPage(); 173 | } else { 174 | previousPage(); 175 | } 176 | } 177 | 178 | onPageChanged(int index) { 179 | var page = index - (preArticle != null ? preArticle!.pageCount : 0); 180 | if (page < currentArticle!.pageCount && page >= 0) { 181 | setState(() { 182 | pageIndex = page; 183 | }); 184 | } 185 | } 186 | 187 | previousPage() { 188 | if (pageIndex == 0 && currentArticle!.preArticleId == 0) { 189 | Toast.show('已经是第一页了'); 190 | return; 191 | } 192 | pageController.previousPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut); 193 | } 194 | 195 | nextPage() { 196 | if (pageIndex >= currentArticle!.pageCount - 1 && currentArticle!.nextArticleId == 0) { 197 | Toast.show('已经是最后一页了'); 198 | return; 199 | } 200 | pageController.nextPage(duration: Duration(milliseconds: 250), curve: Curves.easeOut); 201 | } 202 | 203 | Widget buildPage(BuildContext context, int index) { 204 | var page = index - (preArticle != null ? preArticle!.pageCount : 0); 205 | var article; 206 | if (page >= this.currentArticle!.pageCount) { 207 | // 到达下一章了 208 | article = nextArticle; 209 | page = 0; 210 | } else if (page < 0) { 211 | // 到达上一章了 212 | article = preArticle; 213 | page = preArticle!.pageCount - 1; 214 | } else { 215 | article = this.currentArticle; 216 | } 217 | 218 | return GestureDetector( 219 | onTapUp: (TapUpDetails details) { 220 | onTap(details.globalPosition); 221 | }, 222 | child: ReaderView(article: article, page: page, topSafeHeight: topSafeHeight), 223 | ); 224 | } 225 | 226 | buildPageView() { 227 | if (currentArticle == null) { 228 | return Container(); 229 | } 230 | 231 | int itemCount = (preArticle != null ? preArticle!.pageCount : 0) + currentArticle!.pageCount + (nextArticle != null ? nextArticle!.pageCount : 0); 232 | return PageView.builder( 233 | physics: BouncingScrollPhysics(), 234 | controller: pageController, 235 | itemCount: itemCount, 236 | itemBuilder: buildPage, 237 | onPageChanged: onPageChanged, 238 | ); 239 | } 240 | 241 | buildMenu() { 242 | if (!isMenuVisiable) { 243 | return Container(); 244 | } 245 | return ReaderMenu( 246 | chapters: chapters, 247 | articleIndex: currentArticle!.index, 248 | onTap: hideMenu, 249 | onPreviousArticle: () { 250 | resetContent(currentArticle!.preArticleId, PageJumpType.firstPage); 251 | }, 252 | onNextArticle: () { 253 | resetContent(currentArticle!.nextArticleId, PageJumpType.firstPage); 254 | }, 255 | onToggleChapter: (Chapter chapter) { 256 | resetContent(chapter.id, PageJumpType.firstPage); 257 | }, 258 | ); 259 | } 260 | 261 | hideMenu() { 262 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); 263 | setState(() { 264 | this.isMenuVisiable = false; 265 | }); 266 | } 267 | 268 | @override 269 | Widget build(BuildContext context) { 270 | if (currentArticle == null) { 271 | return Scaffold(); 272 | } 273 | 274 | return Scaffold( 275 | body: AnnotatedRegion( 276 | value: SystemUiOverlayStyle.dark, 277 | child: Stack( 278 | children: [ 279 | Positioned(left: 0, top: 0, right: 0, bottom: 0, child: Image.asset('img/read_bg.png', fit: BoxFit.cover)), 280 | buildPageView(), 281 | buildMenu(), 282 | ], 283 | ), 284 | ), 285 | ); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/reader/reader_page_agent.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ReaderPageAgent { 4 | static List> getPageOffsets(String content, double height, double width, double fontSize) { 5 | String tempStr = content; 6 | List> pageConfig = []; 7 | int last = 0; 8 | while (true) { 9 | Map offset = {}; 10 | offset['start'] = last; 11 | TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr); 12 | textPainter.text = TextSpan(text: tempStr, style: TextStyle(fontSize: fontSize)); 13 | textPainter.layout(maxWidth: width - 1); 14 | var end = textPainter.getPositionForOffset(Offset(width, height)).offset; 15 | 16 | if (end == 0) { 17 | break; 18 | } 19 | tempStr = tempStr.substring(end, tempStr.length); 20 | offset['end'] = last + end; 21 | last = last + end; 22 | pageConfig.add(offset); 23 | } 24 | return pageConfig; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/reader/reader_utils.dart: -------------------------------------------------------------------------------- 1 | class ReaderUtils { 2 | static double topOffset = 37; 3 | static double bottomOffset = 37; 4 | } 5 | -------------------------------------------------------------------------------- /lib/reader/reader_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/public.dart'; 4 | import 'reader_overlayer.dart'; 5 | import 'reader_utils.dart'; 6 | import 'reader_config.dart'; 7 | 8 | class ReaderView extends StatelessWidget { 9 | final Article article; 10 | final int page; 11 | final double topSafeHeight; 12 | 13 | ReaderView({required this.article, required this.page, required this.topSafeHeight}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Stack( 18 | children: [ 19 | Positioned(left: 0, top: 0, right: 0, bottom: 0, child: Image.asset('img/read_bg.png', fit: BoxFit.cover)), 20 | ReaderOverlayer(article: article, page: page, topSafeHeight: topSafeHeight), 21 | buildContent(article, page), 22 | ], 23 | ); 24 | } 25 | 26 | buildContent(Article article, int page) { 27 | var content = article.stringAtPageIndex(page); 28 | 29 | if (content.startsWith('\n')) { 30 | content = content.substring(1); 31 | } 32 | return Container( 33 | color: Colors.transparent, 34 | margin: EdgeInsets.fromLTRB(15, topSafeHeight + ReaderUtils.topOffset, 10, Screen.bottomSafeHeight + ReaderUtils.bottomOffset), 35 | child: Text.rich( 36 | TextSpan(children: [TextSpan(text: content, style: TextStyle(fontSize: Utility.fixedFontSize(ReaderConfig.instance.fontSize)))]), 37 | textAlign: TextAlign.justify, 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/utility/event_bus.dart: -------------------------------------------------------------------------------- 1 | //订阅者回调签名 2 | typedef void EventCallback(arg); 3 | 4 | class EventBus { 5 | //私有构造函数 6 | EventBus._internal(); 7 | 8 | //保存单例 9 | static EventBus _singleton = EventBus._internal(); 10 | 11 | //工厂构造函数 12 | factory EventBus() => _singleton; 13 | 14 | //保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列 15 | var _emap = Map?>(); 16 | 17 | //添加订阅者 18 | void on(eventName, EventCallback f) { 19 | if (eventName == null) return; 20 | _emap[eventName] ??= []; 21 | _emap[eventName]!.add(f); 22 | } 23 | 24 | //移除订阅者 25 | void off(eventName, [EventCallback? f]) { 26 | var list = _emap[eventName]; 27 | if (eventName == null || list == null) return; 28 | if (f == null) { 29 | _emap[eventName] = null; 30 | } else { 31 | list.remove(f); 32 | } 33 | } 34 | 35 | //触发事件,事件触发后该事件所有订阅者会被调用 36 | void emit(eventName, [arg]) { 37 | var list = _emap[eventName]; 38 | if (list == null) return; 39 | int len = list.length - 1; 40 | //反向遍历,防止在订阅者在回调中移除自身带来的下标错位 41 | for (var i = len; i > -1; --i) { 42 | list[i](arg); 43 | } 44 | } 45 | } 46 | 47 | //定义一个top-level变量,页面引入该文件后可以直接使用bus 48 | var eventBus = EventBus(); 49 | -------------------------------------------------------------------------------- /lib/utility/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Styles { 4 | static List get borderShadow { 5 | return [BoxShadow(color: Color(0x22000000), blurRadius: 8)]; 6 | } 7 | } -------------------------------------------------------------------------------- /lib/widget/novel_cover_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | 4 | import 'package:shuqi/app/sq_color.dart'; 5 | 6 | class NovelCoverImage extends StatelessWidget { 7 | final String imgUrl; 8 | final double? width; 9 | final double? height; 10 | NovelCoverImage(this.imgUrl, {this.width, this.height}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | child: Image( 16 | image: CachedNetworkImageProvider(imgUrl), 17 | fit: BoxFit.cover, 18 | width: width, 19 | height: height, 20 | ), 21 | decoration: BoxDecoration(border: Border.all(color: SQColor.paper)), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/widget/novel_cover_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:shuqi/model/novel.dart'; 4 | import 'package:shuqi/widget/novel_cover_image.dart'; 5 | import 'package:shuqi/app/app_navigator.dart'; 6 | 7 | class NovelCoverView extends StatelessWidget { 8 | final Novel novel; 9 | 10 | NovelCoverView(this.novel); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return GestureDetector( 15 | onTap: () { 16 | AppNavigator.pushNovelDetail(context, novel); 17 | }, 18 | child: Container( 19 | width: 90, 20 | margin: EdgeInsets.symmetric(horizontal: 7), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | NovelCoverImage( 25 | novel.imgUrl, 26 | width: 90, 27 | height: 120, 28 | ), 29 | SizedBox(height: 10), 30 | Text(novel.name, style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold), maxLines: 2), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mock/article_1000.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "城市的霓虹灯总是在夜晚还没有降临的时候就早早亮起,仿佛是在显示着与农村的不同之处,但是毕云涛却内心颇不宁静,望着整个城市的灯光,再看了看川流不息的车辆,显得格格不入。\n“我滴个姥姥!这……这么多的灯这要多少电费啊!浪费可耻,浪费可耻啊!”\n毕云涛老家是在一个与世隔绝的大山里,在那里电可是稀缺东西,平常老头子都是让自己点煤油灯的,这漫天灯火的情形给毕云涛看见了,简直让他的心都在滴血!\n算了!这些东西都不是自己的,浪费的话也不是让自己掏钱不是?毕云涛这样想了之后心里才微微好受一些,继续举起面板,大吼道:“治病了!治病了!乡村小神医,包治百病啊!专治肾亏阳损,月经不调,头疼脑热,不孕不育,保证药到病除啊!”\n毕云涛吆喝的声调是从村里小卖郎那里学来的,那独特的腔调,啧啧!这一声喊立马引来了不少人驻足观看。\n一个四十多岁的女人挤上前,鼓着红红腮帮子问道:“你真能治不孕不育吗?”\n毕云涛上下打量了这女人一眼,摇了摇头然后说道:“大姐不是我说啊!你面红齿白,体态丰盈,关键是盆骨大,一看就是个好生养的女人,若是不能生育肯定是你男人不行,你让我来搞一搞,保管生个大白胖小子。”\n“我削你个臭小子!”这个时候,从后面冲过来一个干瘦男子,毕云涛吓了一大跳,连忙将自己的面板收起来,这可是他花了五块钱才从隔壁算命摊那里求来的,可不能打坏了。\n男子指着毕云涛的鼻子骂道:“你这个孬货!竟然敢说我不行?还要搞我老婆,我看你今天是想死了!”说着就要冲上前给毕云涛拼命。\n幸亏围观的群众见毕云涛是个白白净净的小伙子,看着一副人畜无害的模样,这才帮着拉住了男人。\n“你干什么!你看你面白肤糙,头皮发油,下盘不稳,说话的时候还有一股恶臭向我扑来,一看就是肾虚的表现,你每天半夜的时候一定还感觉下腹隐隐作痛,甚至脑袋油焖无比吧!”\n本来男人是要上前跟毕云涛拼命的,但是随着毕云涛不断往下说,手中握紧的拳头也慢慢松了下来,然后惊异的望了望自己的老婆子,似乎真被说中了,愤愤的甩了甩衣袖离开。\n旁边的群众见没了热闹,不少人都悻悻离开,而女人却凑上前,对毕云涛说道:“那个……小伙子,你真的能治疗不孕不育吗?你看我男人要怎么治疗啊!”\n“啥?还要治疗?你男人都走了,他不是不肾虚吗?”毕云涛大声喊着,顿时又将不少人都吸引了过来。\n毕云涛真是搞不懂这些城里人到底是怎么想的,明明是肾虚,却偏偏不承认,这些事怎么能欺骗医生呢?\n当他看见方才那个男人正躲在人群后面偷窥,马上又大声说道:“大哥不是我说你啊!肾虚有个什么可耻的呢?不就是肾虚吗?在我毕云涛这里就没有肾虚这个词,就没有男人不行这句话,你上来让我好好瞧瞧,保管能让这位大姐夜夜不眠。”\n女人被说得满脸通红,然而男人还是没有上前,在人群中回应道:“你个臭小子,你刚才说要搞我老婆,老子跟你没完,不跟我道歉休想给我治病!”\n毕云涛无奈的摆了摆手,难道城里都是这个德性吗?要让医生求着病人给他治病?突然想起老头子告诫自己要入乡随俗,毕云涛只好勉为其难的给男人道歉了。\n“我说这位大哥啊!我刚才不是说要搞你老婆,我这里说的搞是让我给搞一副药,吃了这药之后保管能生个大小子。”\n男人这才作罢,大大方方的走上前,勉为其难的让毕云涛治病。\n毕云涛用手指掐着这个男子的手腕,凝心探查,过了一段时间,男子有些不耐,“你行不行,别忽悠人,像你这样的骗子我见多了,不行的话就那啥,赶紧收摊子滚蛋吧!”\n毕云涛却沉默不语,半晌后才开口说道:“这位大哥,我查你病症不是简单的肾虚,你这种是阳关大开,精门不锁所致,加上病症时间较长,差不多有五六年左右,很有可能还有一些心理问题。”\n说着,毕云涛从破旧背包中掏出一枚药瓶,说道:“来,每夜服用一枚丹药,五天之后,你的病症自然会痊愈,然后再对自己有信心点儿,没什么大白小子生不出来的。”\n“你……你这丹药多少钱?”男子吞了一口口水问道,其实毕云涛所说的病症自己都要,他已经对这个小子信了两三分,但即便就是两三分,男子也不想放弃。\n毕云涛眼中的贪婪之色一闪而逝,然后艰难的伸了三根手指,试探着说道:“三……三百!”\n瘦小男子与女人对视一眼,还以为这家伙要多少钱呢!没想到才三百啊!\n“那个啥!这不会是骗子吧?我们还是去大医院再看看吧!”女人心中有些忐忑,这么便宜一定是骗子!\n毕云涛慌了神,以为是自己价叫得太高,忙说道:“要不两百五也成!两百?哎呀,别走啊!一百,良心价一百啊!”\n男子对女人说道:“才一百,就当买了个狗皮膏药吧!”说着,就掏出了一百元递给毕云涛,毕云涛生怕他们反悔,忙接过钱将丹药递出去,毕竟自己再没有收入的话,只有喝西北风去了。\n男子喜不自胜的带着女人走了,但是他们没走多远,就被一名貌美的年轻女子拦下。\n女子看了看他手上的药瓶,慢慢说道:“这位先生,我出一千,你卖一枚丹药给我怎么样?”\n男子心中乐开了花,没想到自己才用一百买的药转手就能卖十倍的价钱,简直不要太惊喜!马上小鸡啄米似的连连点头。\n“没问题没问题,你全要都行,这有五枚丹药,四千你拿走!”\n女子笑了笑,递给他一千,接过一枚丹药就消失在了人群中,让这对夫妻有些摸不着头脑。\n不一会儿,貌美女子出现在一个普通的饭馆中,坐到了一名更加貌美的女子面前,这女子穿着一套浅蓝色的OL制服,下身的裙摆还是有点长度的,但是怎么都不能遮挡住她那双惊人的大长腿,特别普通的装束,但是由于面容娇美,身材火爆,再加上一条惊人的大长腿,让这身服饰都变得有些不平凡了。\n“姐姐,丹药我拿回来了,你看!”女子将手中的丹药递给这长腿美女。", 4 | "id" : 1000, 5 | "next_id" : 1001, 6 | "price" : 0, 7 | "title" : "第一章 落魄青年", 8 | "index" : 0, 9 | "prev_id" : 0, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1001.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "长腿美女接过丹药,然后放在琼鼻边上轻轻一嗅,脸上露出了一丝惊异。\n“这个小郎中我们跟了两天了,姐姐你到底要做什么嘛!”静儿坐下桌子给自己倒了一杯水,看着眼前的长腿美女。\n长腿美女笑了笑,说道:“静儿,你怎么能这么没有耐心呢!依我看这个小郎中很有些不简单,就拿这粒药丸来说吧!其中混合了九种药材,全是阴寒之物,甚至还有蝎子毒素。”\n“什么!这家伙是要害死人吗?这还补什么阳?”静儿一口水差点没有咽下去,一脸诧异的问道。\n长腿美女摇了摇头,嘴角带着一丝微笑,“但是由于它们按照一种特殊的比例搭配,使得这其中的毒素中和不显露出来,肾虚的人吃下之后马上就能激起体内的阳气,反而比一些有名的壮阳药要厉害的多。”说着,长腿美女的脸上也浮现出钦佩之色。\n“咦!姐姐你看那是谁?”静儿指着一名土气的年轻人,脸上满是诧异。\n这人,正是毕云涛!\n毕云涛看了看服务员递过来的菜单,瞥了瞥嘴,好家伙!简单的一碗云吞面居然都要30元,这些人怎么不去抢?\n本来毕云涛是打算离开的,但是一直呱呱叫的肚子却提出了抗议,无奈之下,毕云涛只好点了一碗云吞面。\n等待送餐的过程是极其无聊的,毕云涛也将目光放在了周围食客身上,这一晃眼间,居然让他发现了两个熟悉的身影,两名漂亮的女子!\n毕云涛心中一咯噔,打起了精神,这两人已经跟了自己有一段时间了,还以为自己不知道,这丫的!不会是要谋财害命吧!\n虽然说这两女的姿色都不错,还有一人有着让毕云涛眼热的大长腿,以及纽扣差点被绷飞的胸部,但是……但是要自己钱财的话,自己打死也不会给的!\n毕云涛打起了精神,又继续打量起饭店里的人来,老头子在自己下山的时候对自己说过,要注意体会都市间的人生百态,怎么体会呢?当然是多观察,多参与啦!\n秉着这样的原则,毕云涛继续观察了饭店里的人来。\n“你怎么变黑了?”男子低声问。\n“我哪里有变黑?”女子声音中带着一丝恼怒。\n坐在毕云涛邻座的一对青年男女的对话将毕云涛的注意力引了过去,毕云涛仔细一瞧,这姑娘唇红齿白的,不黑啊!\n“你还说不黑!自从穷游回来之后,变得这么黑了,你难道不准备对我交代点什么吗?”男子喝问道,声音很是低沉。\n女子有些恼怒,大声回应道:“我说了没黑就是没黑,你这人怎么这么不讲道理?”\n女子的声音不可谓不大,顿时将饭店众人的视线都转移了过去,但是有不少人露出迷惑不解的神色,还有一些人则是强忍着笑意。\n毕云涛无奈的摇了摇头,城市套路深啊!分个手都讲这么容易被人拆穿的谎言来,这姑娘黑吗?不黑啊!\n再一看,黑吗?不黑啊!\n管不了那么多,毕云涛慢慢吃起面来,还真别说,这云吞面味道还是不错的,里面好像还包着龙虾,但是委实忒贵了一些,吃到碗里还剩一点点的时候,毕云涛对服务员招了招手,将人叫了过来。\n“咋回事儿啊!”毕云涛气呼呼的拍了拍桌子,一脸的愤怒。\n这服务员是个女子,只有二十来岁的样子,被毕云涛这个样子吓了一大跳,忙问道:“先生怎么了?”\n“怎么了?我还想问你是怎么了呢?你来看看这是怎么了?”毕云涛指着碗里的一根头发丝,长长的,但是不细看倒还看不出来。\n“说吧!咋办吧!”毕云涛拉着脸道。\n女服务员还是第一次遇见这种情况,赶忙道歉:“对不起,先生真是对不起,下次一定不会这样了。”\n“对不起?”毕云涛敞开了嗓子道:“这个城市那么大,一句对不起就能完事了?那我以后也不用上班了,跟老板说句对不起就算完事了,吃了这碗面我也不用给钱了,拍拍屁股就走人,你找到我大不了我就跟你说一句对不起嘛!你说,是不是这个道理?”\n女服务员有些迷糊的点了点头,然后又摇了摇头,忙说道:“先生,那这碗面你是不想给钱了吗?”\n毕云涛本来是想点头的,但是念在这碗面自己吃得也还算不错,大大小小也就意思一下吧!\n“你这是侮辱我?我像是吃白食的吗?这碗面我差不多也吃了一点了,但是要我全给的话我肯定是不愿意的,诺,这是你们的面钱!”说着,毕云涛递给了女服务员三块钱就潇洒离去。\n静儿看着毕云涛离去的身影的眼神中充满了鄙视,“姐,这人的道德素质也太低了吧!我明明就看见他是从邻座的那女子身上弄的一根头发。”\n长腿美女也微微蹙眉,本来还想着这人的医术不错,到时候将他弄到自己的医馆中呢!可是这样的素质……\n想了想长腿美女还是不愿意放弃,两天跟下来,发现这家伙的医术确实有些高明,大不了以后给高薪,高薪养廉嘛!\n“走,继续跟上去!”\n“姐,这样的人你还想让他加入我们医馆啊?”\n“别废话,走吧!”\n毕云涛都不禁佩服起自己的智商来了,边走边剔着牙,那姿态,别提多潇洒了!\n那云吞面委实太贵了,这下节省了一顿饭钱,并且自己这样一来,一定让那对姐妹花知道了自己的家底,毕云涛,没钱!赶紧的将目标转向别人。\n“这位先生,你知道长生医馆咋走吗?”毕云涛开始问起路人来。\n“长生医馆?没听说过。”男子摇了摇头就走了。\n毕云涛望着人来人往,车水马龙的城市犯了难,自己这都找多少天了,也没找到那啥长生医馆啊!\n这次自己下山可是带着任务的!有两个任务。\n一呢!是老头子吩咐自己要找到长生医馆,找到一个叫林长天的人,将一封推荐信交给他。\n本来毕云涛是不愿意的,但后来老头子说这个长生医馆会给自己一大笔酬金,这才勉为其难的答应了下来。\n第二件事呢!其实是毕云涛给自己定下来的任务,那就是找个白白胖胖好生养的女人,现在自己也老大不小,都二十好几的人了,还没个媳妇,说出去是要让人笑话的,这个任务可是迫切至极。", 4 | "prev_id" : 1000, 5 | "id" : 1001, 6 | "next_id" : 1002, 7 | "price" : 0, 8 | "title" : "第二章 不吃霸王餐", 9 | "index" : 1, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1002.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "现在时间也不早了,晚上八点多了,但是由于有霓虹灯火的照耀,整个城市还是如同白昼。\n再找不到长生医馆,毕云涛只有再次睡大街了,赶紧的,毕云涛又拉住一个白花花小妞问道:“这位小姐,你知道长生医馆怎么走吗?”\n“你才是小姐,你全家都是小姐!神经病!”女子瞥了毕云涛一眼,然后扭扭翘臀就走了。\n毕云涛还真是无语了,能不能好好说话了?问个路都被骂,城市人难道就这么不礼貌吗?看样子还是乡村好,任务完成了赶紧回家才是真理。\n这个时候,一名倚在大树边的中年胡渣男子搓着手,脸上带着亲切的笑容,凑了上来,“小哥,找长生医馆吗?”\n毕云涛连连点头,“没错没错!你知道长生医馆吗?”\n“知道知道!老江南市的人,哪里的路我不知道?上车吧?”说着,将毕云涛带到了一辆破旧的小汽车前。\n“要钱不?”毕云涛上车之后,立即反应了过来,捂了捂钱口袋,有些谨慎的问道。\n胡渣男子掏了掏鼻屎,然后再往车窗外一弹,道:“给个油费钱就成,这大晚上的,咱也不可能白送吧!你说是不是这个理儿?”\n毕云涛心疼的点了点头,“也对,就是那啥?油费钱多少?”\n“不多,20就成,当交个朋友吧!”\n听到20的时候,毕云涛本能的一缩脖子,但还是肉痛的催促道:“走吧走吧!”\n在路上的时候,胡渣男子一直跟毕云涛说话,但是毕云涛一句没搭理,为啥?因为毕云涛觉得自己身上在燃烧!\n这车子不断往前开,毕云涛就觉得这钱再一角一角的烧,这一路都是钱烧出来的啊!\n不一会儿,胡渣男就将车子停在了一处旅馆旁边,这里已经接近城市边缘了,旅馆很是破落。\n“这就到了长生医馆了?”毕云涛左右打量,也没发现一个医馆啊!\n“诺!这就是长生医馆,只是前两年被搬走了,被小旅馆承包了下来,具体在哪你可以问问这旅馆老板。”胡渣男子说道。\n“你这不是骗人吗?那长生医馆搬去哪了你就将我拉到哪儿。”毕云涛不下车,在车子上说道。\n“哎哟小哥,这城市那么大,我咋知道长生医馆到底搬到哪儿去了?能知道点线索就不错了,快点给钱吧!”\n毕云涛虽然心中很是愤怒,但是一想也是这个道理,自己都找了几天了,还是没有踪影,能知道点线索也不错,于是结钱后走进旅馆中。\n坐在旅馆前台的是个打扮得花枝招展的老女人,脸上胭脂摸得化不开,一对胸脯夸张的露了半边,很是雪白饱满的样子,毕云涛看得很是眼热。\n“哎哟!小哥这是要住店吗?”老女人从柜台走出,拉着毕云涛的手臂热情的问道,胸前的硕大在毕云涛身上不断摩擦,有些城市人还是蛮热情的嘛!\n毕云涛忙摆手道:“不是不是,我打听个地方,你知道长生医馆怎么走吗?”\n老女人脸色一下子就变得冷淡了许多,放下了拉着毕云涛的手臂,冷冷道:“不知道!”\n“这都什么时候了?大半夜的出去多不安全?为了你的安全着想,你先在我们这里住一晚我才告诉你长生医馆的地址。”\n什么?还要先住一晚?毕云涛心中打鼓,看了看酒店标注的价格,好家伙!住一晚要40!\n算了,为了找到长生医馆也豁出去了!自己好不容易挣来的一百元吃饭花了5元,打车花了20,这一下又要花掉40,心中还是很不舍的。\n“住吧住吧!老板娘你可不要骗我,明天得给我长生医馆的地址。”毕云涛咬牙说道。\n见到毕云涛要住店,老女人脸上的胭脂褶子一下子又化开了,热情的问道:“小哥,咱们开门做生意,怎么会骗人呢?对了,要别的服务吗?快餐一百,包夜三百,都是好货色。”\n什么服务要这么贵?毕云涛闻言,脸色涨得通红,也不知道自己能不能做,要是自己能做的那可就发达了!\n以后发达了再来探索这发财之路吧!现在毕云涛可享受不起。\n“不用不用!就住店吧!”\n看见毕云涛脸上一阵白一阵红的,老女人还以为这人是害羞呢!当下也不说破,看样子自己还得采取一点比较婉转点的方式,比如说待会儿打个电话啥的。\n“叫什么名字?”登记的时候,老女人问道。\n“毕云涛。”\n“啥?”\n“毕云涛!”\n老女人当即会意,也没叫他出示身份证,毕竟有些人做某些事的时候是不想留下痕迹的,直接就开了一间房。\n“没见过这样的雏儿,还什么避孕套,这不就是想要服务吗?”老女人边下楼边嘀咕道。\n……\n“姐姐,这人今晚要住在这里,难道我们也要住进去吗?”坐在一辆豪华跑车里的静儿问到身边的长腿美女。\n长腿美女也没想到毕云涛会住在这么落魄的旅馆,这要让自己住一晚,那还不得全身长满虱子啊!\n“别急,我想个办法将这人招安了,到时候我们也好早早收工。”长腿美女说道。\n静儿闻言,一张小脸气得鼓鼓的,可是也没有办法,只好在车子里等姐姐想办法。\n不一会儿,只见一名穿着极其暴露,打扮得花枝招展的女人往旅馆中走去,跟老女人打了个招呼就上了楼。\n“天哪!这人不会在嫖妓吧!”这一幕刚好被静儿看见,立马轻掩小嘴惊呼道。\n长腿美女也见到了这一幕,明显的有些厌恶之色,但是为了医馆也只好豁出去了!\n“有了!静儿跟我下车!”长腿女子风风火火的下了车就冲进了小旅馆中,静儿没有办法只好跟了上去。\n老女人见到旅馆中冲进来两名美女,本能的一愣,正要说些什么,但是接下来长腿美女的一个动作立马让她哑火。\n啪!几张红票子一下被长腿美女拍在柜台上,“我问你答,刚才那个男子住在几号房?”\n“204。”\n“那个女人是那……那啥的吗?”长腿美女脸上有些不自然。\n老女人顿时一笑,轻轻点头。\n问清楚之后,长腿美女脱下高跟鞋子就往楼上走去了。静儿也学样跟了上去。\n老女人不着痕迹的将这钱收了起来,然后往后一躺,继续眯着眼睛,这种情况她见多了,一般都是捉奸的!\n“姐姐你这是要干嘛?”静儿不解的问道。\n长腿美女脸上露出一丝坏笑,带着点小小的阴险,“静儿你说,待会儿我们报警,让警察将他抓了去,不让他发现,然后再将他救出来,这人是不是得死心塌地的跟着咱们?”\n静儿一阵无语,这样损的主意只有自己的姐姐能想出来。\n马上,长腿美女就拨通了一个只有三位数的号码。#####新书求收藏!", 4 | "prev_id" : 1001, 5 | "id" : 1002, 6 | "next_id" : 1003, 7 | "price" : 0, 8 | "title" : "第三章 设计", 9 | "index" : 2, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1003.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "毕云涛往旅馆房间中那么一趟,感觉整个人都舒畅了许多,这都多少天了?自己都是以天为被,以地为床的啊!这40,花的值!\n砰砰砰!\n这个时候,响起了一阵敲门声。\n毕云涛心中闪过一丝疑惑,这大半夜的,谁敲门啊!难道是老板娘?\n拉开门一看,好家伙!只见一名身着紧身包臀裙的女人依靠在门边,这女人臀部挺翘,上半身也是露出了三分之一左右的雪白,那沟,啧啧!反正毕云涛一下子就来了反应。\n这女人虽然只是一般的姿色,但是这打扮的视觉冲击还是蛮强的,毕云涛感觉整个人一下子就吸了过去一般。\n就在这时,毕云涛突然发现走廊的过道里有两个熟悉的身影一闪而过。\n这两人,不是一直跟着自己的那两个女人吗?真是阴魂不散啊!\n“咋不让我进去?就在门边做?”花枝招展女微微一挺酥胸,差点将胸前布料给崩坏。\n毕云涛完全没搞清楚什么情况,但还是让这人进去了,同时目光狠狠盯在那诱人的地方,狠狠的咽了一口口水。\n女子进屋之后,看了看毕云涛,对眼前这个年轻的帅气小伙子很是满意,这样的年轻小伙儿,有干劲儿!\n“老板你好,我洗了澡的,你要洗澡吗?”花枝招展女妩媚一笑说道,那眼神儿,仿佛要将毕云涛的魂儿都勾去一般。\n毕云涛狠狠打了一个哆嗦,忙点头道:“要洗,要洗!那啥,你就当是自己家里啊!”说着,毕云涛就钻进了厕所。\n同时静儿这边,确认女子进屋之后,立即就报了案,静儿将手机挂掉之后,有些忐忑的问道:“姐,我们这样做不会有些不好吧!”\n“哪有啥不好的!待会儿我们又不是不救他。”长腿美女悄悄说道,两人就蹲在房间的过道拐弯处。\n却说毕云涛这边,凉水冲刷着身体,也让他全身的躁动平息了下来。\n同时毕云涛狠狠的碎了一口。\n“竟然敢跟老子玩仙人跳!我可不傻!今天若不是眼尖,这几十块钱还得被骗了去不成!”\n毕云涛见到这女子,然后又发现了那两个女子的踪影,一下子就反应了过来,这三人一定是同伙儿!这两人终于是要下手了啊!\n旋即,毕云涛掏出一个小灵通,拨通了一个只有三位数的号码。\n“喂,我要报案,我现在遇见仙人跳了!”毕云涛直接说道。\n……\n正在行驶的警车上面坐着三名警察,开车的是一名身材极其火爆的女警,即便是宽松的制服也不能将她傲人的身材完全遮挡住。\n“方姐,这里面有些蹊跷啊!”坐在旁边的一名明显要年老一些的男警疑惑的说道。\n后面的一名年轻警察挂了电话,也说道:“就是,上面又下达了一条报案线索,一人说是有人嫖妓,一人说是遭遇仙人跳,我都有些绕迷糊了。”\n方静若轻蹙柳眉,小嘴微张道:“别管那么多,到了就知道了。”\n方静若到达地点之后,带着人噔噔瞪的就爬上了楼梯,顿时将正在沉睡的老女人吓了一跳。\n“捉个奸而已,至于把警察都叫上吗?”抱怨了一句之后,老女人翻身又睡。\n“我进去,你们到周围看看有没有什么可疑人员!”方静若到了204房间之后,对其余两名警察吩咐道。\n而方才那两名女子,这个时候还躲在拐角处呢!正要看“警察英勇捉嫖妓男,苦心劝回失足女”的好戏码,却没想到另外两名警察往自己这个方向走了过来。\n……\n“我们就聊聊天就成了,你咋还动起手来了呢!我说你这人也真是的,这大半夜的不睡觉要干啥?”毕云涛连忙将裤子往上提。\n“咋这么啰嗦呢?你这明显是快餐还要加时啊!老娘分分钟上百元,可没工夫跟你在这里瞎耽搁。”花枝招展女也来气了,这人洗了澡还要跟自己聊天,这不是耍流氓吗?\n砰!\n门一下子就被踢开了,正在房间中“厮打”的两人一下就愣住了,而花枝招展女见到这人,马上将脸蒙住。\n“警察扫黄都别动!乖乖站到墙角去!”方静若见到眼前这副景象,如何还不知道发生了什么事?看样子这人是真的嫖妓了。\n毕云涛一下子就愣了,眼前这名女警很是漂亮,身材也不知道比花枝招展女好了多少倍,但是这是个什么情况?\n“嗨!这位女同志,你来得正好。”毕云涛慢慢起身,终于将裤子提了上去。\n方静若的眼中明显闪过一丝厌恶之色,手枪指着毕云涛的脑袋厉声喝道:“叫你别动蹲在墙角去!”\n“这……”\n“不要废话,按照我说的做!”\n“这到底是别动还是蹲在墙角啊?”毕云涛抱怨道。\n方静若还是头一次碰见这么不配合的犯人,旋即香腿一抬,狠狠的向毕云涛踹了过去。\n“啥!警察还要打人啊!”毕云涛见状也不含糊,当即一个侧身,躲过这一脚,同时右手出动,电光火石间就将女警的腿腕抓住。\n蹲在墙角的失足女可吓傻了,这人还敢跟警察动手来了!这可坏了!\n方静若一双杏眼瞪得溜大,似乎对毕云涛敢跟自己动手是又惊又怒,可是从脚腕出传来一阵大力,无论如何都拧不开。\n“我警告你这是袭警啊!快点放开!”方静若早就慌了神,但脸上还是保持着镇定之色。\n毕云涛一想也是这么个理,这人说不定是来帮自己的,只是弄错了,于是将漂亮女警的腿放开。\n但是让他万万没想到的是,当他放开女警的腿之后,这女警右脚落地,左脚有飞起,对着自己的英俊脸蛋就是一脚!", 4 | "prev_id" : 1002, 5 | "id" : 1003, 6 | "next_id" : 1004, 7 | "price" : 0, 8 | "title" : "第四章 将计就计", 9 | "index" : 3, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1004.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "“嘿!你还来劲儿了不是?”毕云涛就是这么个牛脾气,立即故技重施,再次伸手一抓,方静若的左腿落入了自己的手中,然后再那么往下一扯。\n啪!方静若整个人一下子就落在了床上,甚至还在床上弹了几下。\n方静若疼得龇牙咧嘴,这旅馆的床也不是多软和,特别是自己的双峰被硌得十分难受。\n就在方静若准备起身的时候,没想到后背上一下子压过来一个人。\n“还来不?”毕云涛压在方静若的身上,得意洋洋的问道。\n“你……你等着!我……我要杀了你!”方静若还从来没有被人这样欺凌呢!特别是这个嫖客还压在自己的身上,简直不要太恶心。\n“啥?你还要找我麻烦?”毕云涛不由分说,直接一巴掌打在方静若的翘臀上,顿时掀起一阵阵的臀浪。\n方静若感觉到臀部上一阵火辣辣的疼,如何还不知道发生了什么事!又羞又怒之下,恨不得找个地缝钻进去了。\n毕云涛见这女警不说话,还以为自己打重了呢!于是手上的力气减轻了几分。\n啪啪啪啪啪!一阵带着节奏的快乐小夜曲在小旅馆中弹奏起来。\n……\n“老实点儿!”两名警察押着静儿两姐妹,往204房间走去。\n“警官,我都说了,我们两个是无辜的。”长腿美女完全不清楚这是怎么回事。\n右边那名稍微年长的警察道:“我说你们两个小姑娘这么漂亮,怎么好的不学,竟学些这些坑蒙拐骗的玩意儿?啥都别说,待会儿一起去进局子。”\n等到了房间一看,不仅是长腿美女两姐妹愣住了,连两名警官都愣住了。\n只见一名男子正压在方静若的身上,真在不断的抽打呢!\n“干什么,干什么?赶紧给我下来。”中年警官给吓坏了,连忙上前将毕云涛给拉了下来。\n“警官同志,你来得正好,这名警官见到我,不分青红皂白就要对我动手,一定要给我主持公道啊!”毕云涛一副人畜无害的样子说道。\n方静若疼得龇牙咧嘴,慢慢从床上爬了起来,然后立马向毕云涛冲过来。\n“我今天要打死你!”说着,方静若就向毕云涛抓去。\n“不好了!这人怎么了?”静儿指着倒地的浓妆女惊呼道。只见浓妆女一头栽倒在地,现在正昏迷不醒呢!\n众人的目光立即被吸引了过去,年长的一名警官上前探查,而方静若狠狠的瞪了毕云涛一眼,忙跑了过去。\n“张叔,怎么了?”方静若问道。\n“不好!这人在不断的抽搐!”被唤作张叔的警官脸色一变,看样子情况非常的紧急,搞不好还会弄出人命来!\n方静若急得像是热锅上的蚂蚁,连忙掏出手机给总部的人打电话。\n毕云涛正要上前探查,但是没想到长腿美女抢先一步说道:“我来看看吧!我是医生。”\n“你?你真是医生?”方静若有些不相信道,这女人虽然有着不输于自己的容貌,但是这么年轻,这哪像什么医生啊!最多就是个护士。\n长腿美女没有多说什么,从包里掏出一张工作证递给方静若,没想到方静若只一看,小嘴顿时轻掩,看向长腿美女的眼神中也充满了惊异,惊呼道:“你……你是林氏医馆的!”\n长腿美女没有回答方静若的疑问,而是上前探查浓妆女的情况,毕云涛却一头雾水,也凑上前看了看,只看到“林雪”两个字,立即问道:“啥是林氏医馆?”\n方静若才被毕云涛打了屁股,还想着待会将他带回局子好好整治呢!如何肯搭理他?狠狠瞪了他一眼。\n“哼!林氏医馆都不知道,简直就是孤陋寡闻!这么跟你说吧!我们林氏医馆是江南市的老字号医馆,我们医馆说是第二,江南市就没其他的医馆敢说第一!”静儿看了看毕云涛,一脸傲娇的说道。\n毕云涛见在场的其他几人都没有反驳,心中也是打起了小鼓,这两人的原来都是同道中人啊!但是为什么这些天一直追着自己不放呢?也不知道她们知不知道长生医馆的地址,不过看样子她们就算知道也不会跟自己说的,毕云涛也就没有自找没趣。\n只见林雪将浓妆女的手腕抬起,然后再看了看瞳孔,脸上的凝重之色也越来越浓。\n“她本身就有癫痫,并且还有轻微的心脏病,刚才受到了过度的惊吓,碰巧又遇上了癫痫发作,这两种病症冲撞在一起,必须马上送往医院用药,不然很有可能引发人命。”\n方静若连忙道:“已经叫了救护车了,车子马上就到!”\n在一旁观看的毕云涛眉头皱得很紧,这个浓妆女的情况比想象中还要严重得多,弄不好会出人命的。\n“让让!我来救她一命!”毕云涛就如同一头蛮牛,直接将几人冲撞开来,然后在观察起浓妆女来。\n“走开,别捣乱!待会儿出了人命你可担待不起!”方静若急了,立即上前拉住毕云涛,然后示意两个警察用手铐将他拷住。\n林雪却轻轻一笑,制止道:“这位警官先别动手,反正也是在等救护车,我倒要看看他怎么救治的。”\n方静若闻言立即就不动了,将目光放在林雪的身上,带着奇异的色彩,不知道在想些什么。\n“姐姐,这人真是不自量力,现在什么药物都没有,他怎么救治啊!”静儿嗤之以鼻的说道。\n林雪没有多说什么,目光一直凝视在毕云涛的身上,她也很好奇,毕云涛到底会用什么方法救治这个女人。\n接下来的情景让众人齐齐傻眼,只见毕云涛从他那破旧的衣角上抽出一根针,然后再放在嘴巴里砸吧砸吧几下,最后对着浓妆女的太阳穴轻轻一扎。\n“这是什么鬼?”静儿诧异道。\n在场众人都被毕云涛这奇异的治疗方法给吓到了,幸亏毕云涛手中的针只有一根牙签大小,不然才不会让他任意施展呢!\n毕云涛往浓妆女的太阳穴扎了一针后,又从另外胸口上取出一根针来,如法炮制往她的右太阳穴扎了一针,只见原本昏迷不醒的浓妆女一下子就弹了一下,然后又重新归于平静。\n“林医生,他这样不会弄死人吧!”方静若问到林雪,林雪也是一脸的懵逼,她大概知道毕云涛这应该是传说中的针灸,但是这针灸未免太落魄了吧!\n这都是从哪里抽出来的针啊!还有这针也不像是金针,最最让林雪无语的是,这小郎中独特的消毒居然是用口水,真是恶心!\n然后只见毕云涛抬起手掌,往浓妆女的脑袋上重重一拍,浓妆女一下子就清醒了过来。#####PS;老实说这本书前面我不怎么满意,大家接着看下去,若是看到三十章还不满意,大家尽管来找我!", 4 | "prev_id" : 1003, 5 | "id" : 1004, 6 | "next_id" : 1005, 7 | "price" : 0, 8 | "title" : "第五章 出手救治", 9 | "index" : 4, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1005.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "“没啥大问题了,医疗费20。”见到浓妆女醒过来,毕云涛伸出手掌来。\n众人齐翻白眼,林雪眼中全是震惊之色,忙上前抓住浓妆女,只是探查她的病情。\n“你……你怎么做到的?”林雪忙问道。\n毕云涛双手一摊,说道:“就是这样啊!你不全都看见了吗?”\n年轻的警察有些疑惑的问道方静若:“张叔,这人还抓吗?”\n张叔低头沉吟,现在的情况他也搞不清楚了,这两名女子是林氏医馆的人,林氏医馆跟江南市的很多权贵都有结交,很明显,不是好惹的,至于小郎中跟浓妆女,也没发生什么事儿。\n“算了,收队吧!”张叔说道。\n“张叔,我不服!他……他还打了我屁股的!”方静若气呼呼的指着毕云涛,那眼神仿佛要将毕云涛给吃了一般。\n毕云涛浓眉一竖,道:“咋地?你还要赖上我不成?我跟你说,你屁股不够大,生不出儿子来的,我不要!”\n方静若眼睛瞪着溜大,看着毕云涛的眼神都快喷出了火花,两名警官都被吓傻了,方静若虽然人很漂亮,但是在警队里可是出了名的火爆,这男的肯定是凶多吉少了。\n但接下来的一幕却让众人傻眼,只见方静若走上前,嫣然一笑,对毕云涛说道:“不错!我就是要你负责,你难道不觉得我很漂亮吗?”\n林雪跟静儿两人一脸迷糊,不知道这是个什么情况。\n“那个……,没啥事儿的话,我们就先走了啊!还有很多病人等着我们去救治呢!”静儿给林雪递了个眼色,林雪当即会意。\n张叔点了点头,示意她们两人可以离去,然后只见静儿跟林雪两人噔噔瞪几声响就下了楼。\n毕云涛认真的打量起方静若来,然后再点了点头:“你很漂亮,但是生不出儿子。”\n方静若强忍着怒意,直接说道:“今天你这么羞辱我,这件事肯定不可能就这样完了的,有本事就给我留个电话!”\n毕云涛不得已之下,只好给方静若留了一个电话。\n得到毕云涛电话号码之后的方静若立马就带着两名警官走了,当然,临走之前他们还驱走了那名浓妆女。\n毕云涛看了看乱糟糟的房间,一时头大,这都什么事儿啊!\n第二天一大早,毕云涛就起床了,兴冲冲的走到老板娘哪里问长生医馆的地址。\n但是老板娘的回答让毕云涛差点打人。\n只见老板娘随意说道:“这个城市那么大,我咋知道长生医馆搬去哪里了?”\n毕云涛摸了摸脑袋,这话他好像听昨夜那个司机说过啊!\n没有办法,毕云涛只好再次踏上了寻找长生医馆的路途。\n就在毕云涛刚出旅馆的时候,突然之间,从街角钻出一个极其漂亮的女子。\n这女子大约二十来岁的样子,这女人腰细臀翘,眼睛更是水汪汪的,小嘴娇嫩微启,细眉没有经过任何的修饰,但却带着一丝妩媚,拥有东方女性独特的气质之美,简直美得冒泡!\n女子穿着简单的浅蓝色牛仔裤,上身也只是一件长袖红色格子衬衫,长袖挽起,露出洁白的玉臂,一个字:漂亮!\n毕云涛简直看呆了,这人好似画中走出来的仙子一般,并且,并且她还带着微笑向自己走来呢!\n咳咳!毕云涛连忙打起精神,将腰杆挺直了,就算是擦肩而过,也要留个潇洒帅气的形象不是?\n“嘿!一起去吃个饭吧!”漂亮女子脸上带着笑容开口道。\n只是不知道为什么,毕云涛老是觉得这女人自己仿佛在哪里见过,并且她的笑容,很不自然,似乎是憋出来的。\n毕云涛一愣,连忙左右望了望,没人啊!难道……难道美女要请吃饭!\n“那个……你跟我说话吗?”毕云涛羞涩的问道。\n“走吧!我请你吃饭!”漂亮女子直接拉起毕云涛的衣袖往前面拽,毕云涛吓了一大跳,连忙说道:“那啥啊!我们都不认识,你干吗请我吃饭,你别以为我乡下来的就好骗啊!昨天那啥还有两个骗子给我打发走了……”\n漂亮女子一头黑线,转过头来冷冷道:“不记得我了吗?昨天晚上你还打了我呢!”\n啥?毕云涛马上回忆起来,眼前的女子的样貌慢慢跟自己记忆中的那个人融合。\n毕云涛立即惊呼道:“噢!你!你就是昨天晚上被我打屁股的那个女警啊!”\n这声音一喊出来,顿时吸引了不少人的目光,引得路人纷纷回头偷看。\n方静若恨不得找个地缝钻进去,连忙道:“你小点声,这事我们还没完呢!还有我叫方静若,你今后若是敢提半个“被打屁股的女警”的话,我马上翻脸!”说着,方静若还扬了扬小粉拳。\n毕云涛立即点头,说道:“好!念在你要请我吃饭的份上,昨天晚上的事情我就当做没发生了,走吧!”\n方静若气得直咬银牙,但马上脸上又将脸上的怒容强行压了下去,若不是自己有求于他,见到毕云涛的那刻就要跟他拼命,更加别说请吃饭了。\n方静若带着毕云涛走到了一家装扮典雅的餐厅外,很有些小资情调的样子。\n毕云涛看了看眼前这个装扮典雅的餐厅,摇头说道:“方警官,那啥,昨天晚上的事儿我真的不追究了,这个餐厅恐怕有些贵,咱们将就下找个小饭馆就得了。”\n听见毕云涛这么说,方静若对毕云涛的印象稍微好转了一些,这小子还知道给自己节省钱,看样子也不是那么的坏嘛!\n咬了咬牙,方静若长袖一挥,说道:“别啰嗦,进去吃饭就是。”\n进了餐厅之后,马上就有服务员将两人带到一个桌子旁,然后恭敬的拿出菜单递给方静若。\n方静若把菜单拿给毕云涛,示意让毕云涛来点。\n毕云涛看了看,好家伙!图片上的美食真是诱人,但是价格都不菲呢!翻遍全部菜单,毕云涛都没有找到一份低于50的菜品。\n“你……,你点吧!我随便就行。”毕云涛说道。\n虽然毕云涛很想宰这女警一次,但是他现在还没搞清楚方静若的真正目的呢!一切还是看情况再说。", 4 | "prev_id" : 1004, 5 | "id" : 1005, 6 | "next_id" : 1006, 7 | "price" : 0, 8 | "title" : "第六章 美女请吃饭", 9 | "index" : 5, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/article_1006.json: -------------------------------------------------------------------------------- 1 | { 2 | "data" : { 3 | "content" : "方静若叫了几个菜,不一会儿,服务员就将菜都盛上来了,毕云涛马上就狼吞虎咽起来。\n二十多分钟之后,桌子上的菜品都被席卷而空了,方静若根本没有吃到多少,但也不能怪毕云涛,谁叫这饭就这么好吃呢!\n方静若去结账的时候,毕云涛特意看了几眼,好家伙,居然掏出了四张红票子!在他们那里,这都能买半边猪了。\n走出餐厅之后,毕云涛剔着牙,懒洋洋的问道:“说吧!你有什么事情找我?我才不信你是来给我道歉请我吃得饭呢!”\n方静若忍住想要暴打毕云涛一顿的冲动,脸上挤出一个笑容道:“看来你还不傻嘛!不过我还真是有事,我想你来帮我去看个病人。”\n“病人?什么病人?”毕云涛疑惑的问道。\n方静若道:“边走边说吧!我在前面停了一辆电动摩托车。”\n毕云涛跟方静若走着,两人的回头率还是挺高的,毕竟任谁看见了这么个娇美的美女旁边旁边站着个土气少年都心里有些不平吧!\n方静若边走边说道:“她的病症很是奇怪,不知道是不是小时候动了胎气,生下来的时候就体弱多病,找了很多的大夫看都没能根治,倒是花了不少的钱,我也是昨天看见你有些偏方的样子,这才找到你的,不过你放心,就算不能治好,我也不会怪罪你的。”\n毕云涛撇了撇嘴,他不能治好的病不能说没有,但也没有多少,于是打趣的问道:“那如果我治好了呢?”\n“治好了你再说吧!”方静若白了毕云涛一眼,然后取出车钥匙将路旁边的那个小电动摩托车打开。\n毕云涛到不觉得难为情,直接就坐在了电动摩托车后面,双手紧紧的将方静若抱紧。\n“我说你别抱得我那么紧啊!”方静若脸蛋一下子就红了,她还从没被男人抱过呢!\n毕云涛却不依不饶道:“俺这辈子还没有坐过这种车子呢!抱紧点安全点。”\n方静若一阵无语,但也没什么办法,只好让毕云涛继续抱着,她发动车子往路上开去。\n没多久的时间,方静若就带着毕云涛到了一个小巷子中,这个小巷子周围都是那种落魄的水泥房。\n方静若带着毕云涛进入一个楼梯间中,两人一直爬楼梯。\n毕云涛就这样跟在后面,看着方静若的翘臀在自己的面前不断的晃啊晃的,狠狠的吞了一口口水,还真别说,这个女人的臀虽然不大,但是十分的翘,也说不定能生个大白儿子呢!\n“还有多久啊!”方静若带着毕云涛爬了五六层楼了。\n方静若道:“马上就到了,你到时候可得帮我好好看看啊!如果能把我妹妹看好,到时候我……。”\n“做我媳妇?”毕云涛立即说道,经过他仔细的思考,这个女警也是不错的,能当自己的老婆也还将就吧!\n方静若脸上一红,忙说道:“你少癞蛤蟆吃天鹅肉了,再说了,我妹妹的病你可不一定能治好,这些年我舅妈一家不知道给她找了多少医生了,都没有作用。”\n终于来到房门前,方静若却没有进去,而是看了看门前一大堆的鞋子。\n“我舅妈她们肯定来了,进去吧!”方静若道。\n敲了敲门,开门的果然是方静若的后妈,是一个中年女人,有些矮胖矮胖的。\n“静若,你可终于回来了!你舅舅找了一个医生,这医生是他专门从市里给请过来的,现在正在给你妹妹治病呢!这次灵灵的病一定能治好了!”\n舅妈脸上带着笑容,似乎方静若妹妹的病真的治好了一般。\n方静若脸上勉强挤出一丝苦笑,但还是没多说什么。\n“这位是?”舅妈这才看见跟在方静若身后的毕云涛,脸上有些疑惑。\n方静若连忙道:“舅妈,你先让我们进去吧!这人是我的朋友。”\n进了屋子之后,毕云涛这才发现屋子里挤满了人,方静若走到一名尖耳猴腮的中年人面前,恭恭敬敬的喊了一声舅舅。\n舅舅轻轻点头,没有多说什么,眼光一直放在坐在桌子旁边的那名穿着白大褂的医生身上,这医生大约三十来岁的样子,年纪不大,但还他身旁还有一名助手,这助手正背着医药箱。\n医生正在号脉,病人是一名年约十五六岁的少女,毕云涛仔细打量起这少女来,少女年纪轻轻,但是已经很是漂亮,是个美人胚子,估计长大之后跟她姐姐一样,又是一个大美女。\n只是这少女脸色苍白,嘴唇红中带灰,浑身打着哆嗦,一副畏寒怕冷的模样。\n整个屋子显得特别的寂静,众人的视线都集中在那个白衣大褂的医生身上,这个医生也很是有趣,双眉时而紧蹙,时而舒缓,弄得方静若双手也跟着慢慢紧了起来。\n毕云涛却将目光一直放在方静若妹妹的身上,现在七八月的天气,这小女孩却穿得这么多,还这么冷的样子,一定是某个地方出了问题!\n“唉!”白衣大褂医生深深的叹了一口气,毕云涛也发现方静若的身子明显的一僵,这医生继续说道:“你妹妹的情况很不乐观。”\n“林医生,您倒是详细说说啊!”方静若的舅舅弯着腰,一副恭敬的样子。\n林医生摇了摇头,然后才说道:“这小女孩已经被寒毒入侵到身体内部了,是小时候动了胎气吧!”\n方静若脸色一变,连忙追问道:“医生你怎么知道?有什么办法救我妹妹吗?多少钱我都会治的!”\n说到后来,方静若的都带着哭腔了。一副楚楚可怜的样子。\n“这……这有些难办啊!”林医生脸上有些难色。\n方静若的舅舅连忙往林医生的白衣大褂口袋里塞了一团红票子,然后带着谄笑说道:“林医生,您看我这侄女也是为了给妹妹治病,这些年都不知道吃了多少苦,受了多少累,您就帮帮她吧!”\n林医生推脱再三,然后才勉为其难的将钱收下,摸了摸小胡须,叹气说道:“你妹妹这种情况,是天生畏寒所致,已经是没有办法治疗了,但是我最近听朋友说,在美利坚有一种药能治疗这种病,你们去美利坚治病,说不定能将这位小女孩的病治好。”", 4 | "prev_id" : 1005, 5 | "id" : 1006, 6 | "next_id" : 0, 7 | "price" : 0, 8 | "title" : "第七章 没有免费的午餐", 9 | "index" : 6, 10 | "novel_id" : 1945 11 | } 12 | } -------------------------------------------------------------------------------- /mock/catalog.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "200", 3 | "message": "success", 4 | "data": [ 5 | { 6 | "id": 1000, 7 | "index": 0, 8 | "title": "第一章 落魄青年" 9 | }, 10 | { 11 | "id": 1001, 12 | "index": 1, 13 | "title": "第二章 不吃霸王餐" 14 | }, 15 | { 16 | "id": 1002, 17 | "index": 2, 18 | "title": "第三章 设计" 19 | }, 20 | { 21 | "id": 1003, 22 | "index": 3, 23 | "title": "第四章 将计就计" 24 | }, 25 | { 26 | "id": 1004, 27 | "index": 4, 28 | "title": "第五章 出手救治" 29 | }, 30 | { 31 | "id": 1005, 32 | "index": 5, 33 | "title": "第六章 美女请吃饭" 34 | }, 35 | { 36 | "id": 1006, 37 | "index": 6, 38 | "title": "第七章 没有免费的午餐" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /mock/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "200", 3 | "message": "success", 4 | "data": { 5 | 6 | "token": "k3h6lvk3l78lxiutq5578", 7 | "id": 1854, 8 | "nickname": "素敌", 9 | "avatar": "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2831320512,2443437629&fm=26&gp=0.jpg", 10 | "is_vip": 0, 11 | "wealth": 53.4, 12 | "coupon": 3, 13 | "ticket": 6 14 | } 15 | } -------------------------------------------------------------------------------- /mock/novel_comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "200", 3 | "message": "success", 4 | "info": { 5 | "count": 341, 6 | "page": 1, 7 | "size": 3, 8 | "total": 1022, 9 | "userInfos": { 10 | "1017114501": { 11 | "nickName": "素敌", 12 | "userPhoto": "http://thirdqq.qlogo.cn/qqapp/100730840/3EF36447BB55841FF226E24E6021A262/100" 13 | } 14 | }, 15 | "vTime": 1546752139 16 | }, 17 | "data": [{ 18 | "mid": "a46b796c3b4911e8abbc6c92bf28e005", 19 | "textType": "0", 20 | "isJing": 1, 21 | "isTop": 1, 22 | "isAuthor": 0, 23 | "status": "online", 24 | "statusOrigin": 14, 25 | "bookName": "逆天神医", 26 | "authorName": "月亮不发光", 27 | "bookId": "6900908", 28 | "authorId": "2914205", 29 | "score": 5, 30 | "uid": "926636390", 31 | "nickName": "大爷", 32 | "userPhoto": "https://tfs.alipayobjects.com/images/partner/T1Lh8fXaXuXXXXXXXX", 33 | "zanNum": "6358", 34 | "pubTime": "1523204811", 35 | "replyNum": "135", 36 | "rootUid": "0", 37 | "existShenReply": 1, 38 | "replyInfo": { 39 | "repliedNickName": "大爷", 40 | "mid": "746be2b047ce11e8abc06c92bf28e013", 41 | "text": "确实是换了作者的", 42 | "type": "2", 43 | "nickName": "书友953662548", 44 | "repliedText": "神思悠远,故事线拉的极长,但是却又合情合理。由远入深,从现代拉回修真世界,最为精彩的点莫过于这个了。追了那么久,之前觉得不怎么样,后面文笔爆发的简直妙,感觉像是换了个作者似的" 45 | }, 46 | "summary": "", 47 | "text": "神思悠远,故事线拉的极长,但是却又合情合理。由远入深,从现代拉回修真世界,最为精彩的点莫过于这个了。追了那么久,之前觉得不怎么样,后面文笔爆发的简直妙,感觉像是换了个作者似的", 48 | "auditStatus": 0, 49 | "btype": "1" 50 | }, { 51 | "mid": "39029be2851911e789c16c92bf28e013", 52 | "textType": "0", 53 | "isJing": 1, 54 | "isTop": 1, 55 | "isAuthor": 0, 56 | "status": "online", 57 | "statusOrigin": 14, 58 | "bookName": "逆天神医", 59 | "authorName": "月亮不发光", 60 | "bookId": "6900908", 61 | "authorId": "2914205", 62 | "score": 5, 63 | "uid": "755372373", 64 | "nickName": "三年后", 65 | "userPhoto": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLBMatIicktRKPXG1Gs3qp7zhrEA08UBicuHz6cvyz1hR5gmuAL4yORdnOCyfGvzNChSic09P7fzwVMww/0", 66 | "zanNum": "9716", 67 | "pubTime": "1503172903", 68 | "replyNum": "93", 69 | "rootUid": "0", 70 | "existShenReply": 0, 71 | "replyInfo": { 72 | "repliedNickName": "三年后", 73 | "mid": "5a97c5e6854311e789c16c92bf28e013", 74 | "text": "一定,无论何时都不会恶意灌水,不求快,力求好", 75 | "type": "1", 76 | "nickName": "月亮不发光", 77 | "repliedText": "兄弟我一直在看你的书希望你尽量更新快点,书写的不错比其他人写的要好我看你的书已经冲了不少钱了希望给我一个好结局" 78 | }, 79 | "summary": "", 80 | "text": "兄弟我一直在看你的书希望你尽量更新快点,书写的不错比其他人写的要好我看你的书已经冲了不少钱了希望给我一个好结局", 81 | "auditStatus": 0, 82 | "btype": "1" 83 | }, { 84 | "mid": "b25e6f785edd11e789c16c92bf28e013", 85 | "textType": "0", 86 | "isJing": 1, 87 | "isTop": 0, 88 | "isAuthor": 0, 89 | "status": "online", 90 | "statusOrigin": 14, 91 | "bookName": "逆天神医", 92 | "authorName": "月亮不发光", 93 | "bookId": "6900908", 94 | "authorId": "2914205", 95 | "score": 0, 96 | "uid": "771969073", 97 | "nickName": "风雨同行", 98 | "userPhoto": "http://wx.qlogo.cn/mmopen/psrH80icCFB1YMSZpah4jtxjse5ia90cjYX0E05OFuZBqbxibtkH8RR4ibxwGRo3heV8IUXAkE34dpUoib7DMiatwjfkzz85tB5ITR/0", 99 | "zanNum": "8528", 100 | "pubTime": "1498969193", 101 | "replyNum": "65", 102 | "rootUid": "0", 103 | "existShenReply": 0, 104 | "replyInfo": { 105 | "repliedNickName": "书友 871256594", 106 | "mid": "a6430f9eefa811e7abc06c92bf28e013", 107 | "text": "是啊!你不服?", 108 | "type": "1", 109 | "nickName": "月亮不发光", 110 | "repliedText": "评论是刷的吧" 111 | }, 112 | "summary": "", 113 | "text": "天哪,,,能不能一天更新五十章,我出3000元钱,像这么慢慢看,而且是精彩的时候没有了,人都要疯不。[d][d][d]", 114 | "auditStatus": 0, 115 | "btype": "1" 116 | }] 117 | } -------------------------------------------------------------------------------- /mock/novel_detail.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": 200, 3 | "message": "success", 4 | "data": { 5 | "bid": "1945", 6 | "bookname": "逆天神医", 7 | "comment_count":"486547", 8 | "book_cover": "http://img-tailor.11222.cn/bcv/big/201901031812421599.jpg", 9 | "authorId": "2914205", 10 | "author_name": "月亮不发光", 11 | "hide": false, 12 | "shelfStatus": "1", 13 | "coverIsOpen": true, 14 | "readIsOpen": true, 15 | "stat_name": "完结", 16 | "listIsOpen": true, 17 | "wordCount": 437.29, 18 | "score":4.9, 19 | "state": "1", 20 | "classId": "122", 21 | "class_name": "都市异能", 22 | "chapterNum": "2111", 23 | "lastInsTime": 1546697715, 24 | "payMode": "3", 25 | "price": "0.5", 26 | "first_article_id":1000, 27 | "tag": [ 28 | "神医", 29 | "医术", 30 | "都市生活" 31 | ], 32 | "introduction": "【年度神作】真正的神医是什么?是会杀人的神医!\r\n杀一人,救百人,杀不杀?\r\n杀!\r\n这是一个不一样的神医;他妙手回春,有人将他的名字刻在长生牌位之上,日夜供奉。\r\n他受人唾骂,有人恨不得将他剥皮抽筋,日夜诅咒。\r\n他左擒济世壶,一手金针渡人苦海;他右持杀生剑,一剑送人断头台。\r\n可救人病,亦可要人命!\r\n", 33 | "firstChapter": { 34 | "id": 719306, 35 | "title": "第一章落魄青年", 36 | "updateTime": 1479970514, 37 | "shortContUrlSuffix": "" 38 | }, 39 | "lastChapter": { 40 | "id": 1096632, 41 | "title": "第二千一百一十一章红尘仙劫(上)", 42 | "updateTime": 1546697715, 43 | "shortContUrlSuffix": "" 44 | }, 45 | "disType": 3, 46 | "disInfo": { 47 | "isBuyAll": false, 48 | "minDiscount": "88" 49 | }, 50 | "miguInfo": {}, 51 | "extraDiscount": 10, 52 | "userMonthlyType": 3, 53 | "serverTime": 1546752139, 54 | "topClass": "502", 55 | "shareCoverUrl": "http://d.shuqi.com/1_5doSc", 56 | "formats": "1", 57 | "weekClick": "228232", 58 | "recIntro": "杀人即是救人,这么做到底是对是错,留给世人评说。一个神医的孤独之旅。", 59 | "similar": "91%", 60 | "intro": "", 61 | "superVipEndTime": 0, 62 | "isSupportVipCoupon": 1, 63 | "oriIsReward": true, 64 | "finalPrice": "", 65 | "audiobookInfo": { 66 | "playCount": "0", 67 | "cpIntro": "" 68 | }, 69 | "relationBookId": 0, 70 | "relationTopclass": 0, 71 | "relationAudiobookId": 0, 72 | "recommendTicketNum": 6643, 73 | "monthTicketNum": 2325, 74 | "rewardNum": 211452, 75 | "rewardRank": 0, 76 | "recommendTicketRank": 0, 77 | "monthTicketRank": 3, 78 | "cpName": "原创版权", 79 | "cpId": "10091", 80 | "fullDirectInfo": {}, 81 | "monthlyEnd": 0, 82 | "limitFreeEnd": 0, 83 | "isOriginBook": 2, 84 | "originalInfo": {}, 85 | "shortContUrlPrefix": "http://content.shuqireader.com/sapi/chapter/contentshort/", 86 | "numClick": "228232", 87 | "readFeatureOpt": "0", 88 | "isBuy": false, 89 | "isReward": true, 90 | "isMonthlyBook": false, 91 | "isSupportRecommendTicket": true, 92 | "isSupportMonthTicket": true 93 | } 94 | } -------------------------------------------------------------------------------- /mock/novel_recommend.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 1, 3 | "info": { 4 | "rid": "be28e1789da7554856a99e05b176fd23" 5 | }, 6 | "data": [{ 7 | "author_name": "北城", 8 | "bookname": "超级女神的护花高手", 9 | "bookid": "7060196", 10 | "authorid": "93971", 11 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201801171631281432.jpg", 12 | "tags": "爽文,学生,异术超能", 13 | "category": "都市异能" 14 | }, { 15 | "author_name": "雪染墨月", 16 | "bookname": "最强高手纵横花都", 17 | "bookid": "7686657", 18 | "authorid": "3916503", 19 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201806271920338052.jpg", 20 | "tags": "热血,爽文,暧昧", 21 | "category": "都市异能" 22 | }, { 23 | "author_name": "随心动", 24 | "bookname": "怜天悯地", 25 | "bookid": "4036593", 26 | "authorid": "1961475", 27 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/middle\/2015032510134648.jpg", 28 | "tags": "热血,架空", 29 | "category": "奇幻修真" 30 | }, { 31 | "author_name": "秦刑", 32 | "bookname": "狼蛛游戏", 33 | "bookid": "7525937", 34 | "authorid": "4009106", 35 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201806082121416525.jpg", 36 | "tags": "悬疑,惊魂,诡事", 37 | "category": "恐怖惊悚" 38 | }, { 39 | "author_name": "任青", 40 | "bookname": "荒帝", 41 | "bookid": "4593988", 42 | "authorid": "119135", 43 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201606121613037034.jpg", 44 | "tags": "热血,重生,架空", 45 | "category": "东方玄幻" 46 | }, { 47 | "author_name": "木头老爷", 48 | "bookname": "丹皇邪少", 49 | "bookid": "5348043", 50 | "authorid": "2771900", 51 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201603111536513382.jpg", 52 | "tags": "热血,争霸,美女", 53 | "category": "都市异能" 54 | }, { 55 | "author_name": "微微鸿气", 56 | "bookname": "武霸神荒", 57 | "bookid": "4205467", 58 | "authorid": "40346", 59 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201702161516516965.jpg", 60 | "tags": "热血,传奇,修炼,架空", 61 | "category": "异界大陆" 62 | }, { 63 | "author_name": "紅颜若雪", 64 | "bookname": "二哥修仙传", 65 | "bookid": "7796739", 66 | "authorid": "4141918", 67 | "book_cover": "http:\/\/img-tailor.11222.cn\/bcv\/big\/201809262114271722.jpg", 68 | "tags": "热血,爽文,升级", 69 | "category": "古典仙侠" 70 | }], 71 | "spend": { 72 | "total": 0.0585 73 | } 74 | } -------------------------------------------------------------------------------- /mock/sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "200", 3 | "message": "success" 4 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shuqi 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | carousel_slider: 15 | cached_network_image: 16 | shared_preferences: 17 | webview_flutter: 18 | share: 19 | fluttertoast: 20 | battery: 21 | intl: 22 | device_info: 23 | je_kit: 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | flutter: 30 | uses-material-design: true 31 | 32 | assets: 33 | - img/ 34 | - mock/ -------------------------------------------------------------------------------- /screenshot/android_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/android_0.png -------------------------------------------------------------------------------- /screenshot/android_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/android_1.png -------------------------------------------------------------------------------- /screenshot/android_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/android_2.png -------------------------------------------------------------------------------- /screenshot/ios_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/ios_0.png -------------------------------------------------------------------------------- /screenshot/ios_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/ios_1.png -------------------------------------------------------------------------------- /screenshot/ios_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayden320/flutter_shuqi/2455285b8c26347fd5f144b3634e3b08b12eb761/screenshot/ios_2.png --------------------------------------------------------------------------------