├── adb ├── lib ├── Utils │ ├── AppText.dart │ ├── mvp.dart │ ├── Utils.dart │ ├── SparrowException.dart │ └── entity_factory.dart ├── Widget │ ├── Content.dart │ ├── ColoredTabBar.dart │ ├── ReadArticle.dart │ ├── ColorLoader.dart │ └── MyExpansionTitle.dart ├── View │ ├── IHomeView.dart │ ├── IEditView.dart │ ├── ITeamView.dart │ ├── IAllDocView.dart │ ├── IGroupView.dart │ ├── IAllBookView.dart │ ├── INotificationView.dart │ ├── ISearchView.dart │ ├── IBookView.dart │ ├── IMainView.dart │ ├── IUserView.dart │ ├── AllBookView.dart │ ├── SearchView.dart │ ├── NotificationView.dart │ ├── TeamView.dart │ ├── AllDocView.dart │ ├── EditView.dart │ ├── GroupView.dart │ └── BookView.dart ├── model │ ├── TocTree.dart │ ├── CreateDoc_entity.dart │ ├── Vo │ │ └── FollowVo.dart │ ├── UserInfo_entity.dart │ ├── Token_entity.dart │ ├── ExistToken_entity.dart │ ├── FollowUserResponse.dart │ ├── Followed_entity.dart │ ├── DocDetailSerializer_entity.dart │ ├── Team_entity.dart │ ├── User_entity.dart │ ├── BookSuccessResponse_entity.dart │ ├── Follow_entity.dart │ ├── AllBook_entity.dart │ ├── BooksAndColumn.dart │ ├── RealBook_entity.dart │ ├── Book_entity.dart │ ├── ThirdData_entity.dart │ └── UserSearch_entity.dart ├── presenter │ ├── GroupPresenter.dart │ ├── TeamPresenter.dart │ ├── EditPresenter.dart │ ├── SearchPresenter.dart │ ├── AllDocPresenter.dart │ ├── AllBookPresenter.dart │ ├── NotificationPresenter.dart │ ├── BookPresenter.dart │ ├── AbsPresenter.dart │ ├── UserPresenter.dart │ └── MainViewPresenter.dart └── Api.dart ├── android ├── gradle.properties ├── .settings │ └── org.eclipse.buildship.core.prefs ├── app │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ └── styles.xml │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── cn │ │ │ └── yaminets │ │ │ └── microsparrow │ │ │ └── MainActivity.java │ ├── .classpath │ ├── .project │ └── build.gradle ├── key.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .project ├── settings.gradle └── build.gradle ├── key.jks ├── key.jks.old ├── res ├── icon.jpg ├── 工作台.png ├── backgroup.jpg ├── compare.txt └── logo.svg ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── main.m │ ├── Info.plist │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── AppDelegate.m ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Podfile ├── .metadata ├── README.md ├── test └── widget_test.dart ├── .gitignore └── pubspec.yaml /adb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Utils/AppText.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Widget/Content.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/key.jks -------------------------------------------------------------------------------- /key.jks.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/key.jks.old -------------------------------------------------------------------------------- /res/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/res/icon.jpg -------------------------------------------------------------------------------- /res/工作台.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/res/工作台.png -------------------------------------------------------------------------------- /res/backgroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/res/backgroup.jpg -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /lib/View/IHomeView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | abstract class IHomeView implements IPresenter{ 4 | 5 | } -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=1574150143 2 | keyPassword=1574150143 3 | keyAlias=key 4 | storeFile=D:\\projects\\micro_sparrow\\key.jks -------------------------------------------------------------------------------- /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/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/model/TocTree.dart: -------------------------------------------------------------------------------- 1 | class TocTree { 2 | String title; 3 | int childCount; 4 | String url; 5 | String style; 6 | List child = new List(); 7 | 8 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/View/IEditView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | class IEditView implements IView{ 4 | void reslutOfPost(bool param0, String s) {} 5 | 6 | } -------------------------------------------------------------------------------- /lib/Utils/mvp.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/View/ITeamView.dart'; 2 | 3 | abstract class IView { 4 | } 5 | 6 | abstract class IPresenter{ 7 | init(IView view); 8 | } -------------------------------------------------------------------------------- /lib/View/ITeamView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | abstract class ITeamView implements IView{ 4 | void getTeamHttpResult(body, bool param1) {} 5 | } -------------------------------------------------------------------------------- /lib/Utils/Utils.dart: -------------------------------------------------------------------------------- 1 | class Utils{ 2 | static bool checkNull(Object o){ 3 | if(o == null){ 4 | return true; 5 | }else{ 6 | return false; 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /lib/View/IAllDocView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | class IAllDocView implements IView{ 4 | void getDocHttpResult(body, bool param1, String s) {} 5 | 6 | } -------------------------------------------------------------------------------- /lib/View/IGroupView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | abstract class IGroupView implements IView{ 4 | void getGroupHttpResult(param0, bool param1) {} 5 | 6 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /lib/View/IAllBookView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | 3 | class IAllBookView implements IView{ 4 | void getBookHttpResult(body, bool param1, String s) {} 5 | 6 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wohuidemadaye/micro_sparrow/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/View/INotificationView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | import 'package:micro_sparrow/model/Notification_entity.dart'; 3 | 4 | class INotificationView implements IView{ 5 | void resultOfNoti(bool param0, NotificationEntity entity, String ok) {} 6 | 7 | } -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/View/ISearchView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | import 'package:micro_sparrow/model/UserSearch_entity.dart'; 3 | 4 | class ISearchView implements IView{ 5 | void resultOfSearch(bool param0, UsersearchEntity entity, String ok) {} 6 | 7 | void resultOfInvite(bool param0, param1, String ok) {} 8 | 9 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/Utils/SparrowException.dart: -------------------------------------------------------------------------------- 1 | class SparrowException{ 2 | static const String GET_COOKIES_FAIL = "获取cookies失败"; 3 | static const String GET_TOPIC_DATA_FAIL = "获取列表数据失败"; 4 | static const String NETWORK_ERROR = "网络错误"; 5 | 6 | static const String NULL_DATA = "没有数据"; 7 | 8 | static const String OK = "获取数据成功"; 9 | 10 | static const String LOGIN = "请求登录"; 11 | 12 | static const String DEL_FAIL = "删除失败,请检查网络"; 13 | 14 | } -------------------------------------------------------------------------------- /lib/Widget/ColoredTabBar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColoredTabBar extends Container implements PreferredSizeWidget { 4 | ColoredTabBar(this.color, this.tabBar); 5 | 6 | final Color color; 7 | final TabBar tabBar; 8 | 9 | @override 10 | Size get preferredSize => tabBar.preferredSize; 11 | 12 | @override 13 | Widget build(BuildContext context) => Container( 14 | color: color, 15 | child: tabBar, 16 | ); 17 | } -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/model/CreateDoc_entity.dart: -------------------------------------------------------------------------------- 1 | class CreatedocEntity { 2 | num public; 3 | String title; 4 | String body; 5 | String slug; 6 | 7 | CreatedocEntity({this.public, this.title, this.body, this.slug}); 8 | 9 | CreatedocEntity.fromJson(Map json) { 10 | public = json['public']; 11 | title = json['title']; 12 | body = json['body']; 13 | slug = json['slug']; 14 | } 15 | 16 | String toJson() { 17 | return '{"title":"$title",' + '"public":' + this.public.toString() + ',"body":"$body"}'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /lib/View/IBookView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | import 'package:micro_sparrow/model/AllDoc_entity.dart'; 3 | import 'package:micro_sparrow/model/DocDetailSerializer_entity.dart'; 4 | import 'package:micro_sparrow/model/TocTree.dart'; 5 | 6 | class IBookView implements IView{ 7 | void resultOfBook(bool param0, List body,String message) {} 8 | 9 | void resultOfAllDoc(bool param0, AlldocEntity entity, String ok) {} 10 | 11 | void resultOfDelDoc(bool param0, int position, String ok) {} 12 | 13 | void resultOfReadDoc(bool param0, DocdetailserializerEntity entity, String ok) {} 14 | 15 | 16 | } -------------------------------------------------------------------------------- /lib/model/Vo/FollowVo.dart: -------------------------------------------------------------------------------- 1 | class FollowVo { 2 | String actionType; 3 | String targetType; 4 | num targetId; 5 | 6 | FollowVo({this.actionType, this.targetType, this.targetId}); 7 | 8 | FollowVo.fromJson(Map json) { 9 | actionType = json['action_type']; 10 | targetType = json['target_type']; 11 | targetId = json['target_id']; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = new Map(); 16 | data['action_type'] = this.actionType; 17 | data['target_type'] = this.targetType; 18 | data['target_id'] = this.targetId; 19 | return data; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/Utils/entity_factory.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/model/Doc_entity.dart'; 2 | import 'package:micro_sparrow/model/ThirdData_entity.dart'; 3 | import 'package:micro_sparrow/model/EventEntity.dart'; 4 | 5 | class EntityFactory { 6 | static T generateOBJ(json) { 7 | if (1 == 0) { 8 | return null; 9 | } else if (T.toString() == "DocEntity") { 10 | return DocEntity.fromJson(json) as T; 11 | } else if (T.toString() == "ThirddataEntity") { 12 | return ThirddataEntity.fromJson(json) as T; 13 | } else if (T.toString() == "EventEntity") { 14 | return EventEntity.fromJson(json) as T; 15 | } else { 16 | return null; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/View/IMainView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | import 'package:micro_sparrow/model/Doc_entity.dart'; 3 | import 'package:micro_sparrow/model/EventEntity.dart'; 4 | import 'package:micro_sparrow/model/Notification_entity.dart'; 5 | import 'package:micro_sparrow/model/ThirdData_entity.dart'; 6 | import 'package:micro_sparrow/model/UserInfo_entity.dart'; 7 | 8 | class IMainView implements IView{ 9 | void resultOfEvent(bool param0, param1, String s) {} 10 | 11 | void resultOfHistoryDoc(bool param0, DocEntity docEntity, String ok) {} 12 | 13 | void resultOfNews(bool param0, ThirddataEntity thirddataEntity, String ok) {} 14 | 15 | void resultOfMoreDoc(bool param0, DocEntity entity, String ok) {} 16 | 17 | void resultOfMoreNews(bool param0, ThirddataEntity entity, String ok) {} 18 | 19 | void resultOfMyInfo(bool param0, UserinfoEntity entity, String ok) {} 20 | 21 | void resultOfLogout() {} 22 | 23 | void resultOfNotification(bool param0, NotificationEntity entity, String ok) {} 24 | 25 | 26 | } -------------------------------------------------------------------------------- /lib/View/IUserView.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/mvp.dart'; 2 | import 'package:micro_sparrow/model/BooksAndColumn.dart'; 3 | import 'package:micro_sparrow/model/FollowUserResponse.dart'; 4 | import 'package:micro_sparrow/model/Follow_entity.dart'; 5 | import 'package:micro_sparrow/model/User_entity.dart'; 6 | import 'package:micro_sparrow/model/Vo/FollowVo.dart'; 7 | 8 | abstract class IUserView implements IView{ 9 | void resultOfUserData(bool param0, UserEntity entity, String s) {} 10 | 11 | void resultOfBoosAndColumn(bool param0, BooksAndColumnEntity booksAndColumnEntity, String s) {} 12 | 13 | void resultOfFollowUser(bool param0, FollowEntity followEntity, String s) {} 14 | 15 | void resultOfFollowersUser(bool param0, FollowEntity followEntity, String s) {} 16 | 17 | void checkIsFollowed(bool param0, Map isFollowed, String s) {} 18 | 19 | void followUser(bool param0, FollowUserResponse response, String s) {} 20 | 21 | void deleteFollowUser(bool param0, FollowVo followVo, String s) {} 22 | 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微语雀-语雀的第三方APP应用 2 | 一款基于Flutter跨平台的语雀客户端,目前已经完成绝大部分功能,现在正在进行项目的代码重构,请耐心等待发布。 3 | 4 | # 进度 5 | 最近语雀平台的数据类型和接口一直在改,不断地出现bug,因此我在考虑一种稳定的设计方案。 6 | 7 | **重构**:由于刚开始对于flutter和dart的不熟悉,导致现在代码量日益趋大的同时维护相当困难,因此现在在对项目进行重构。 8 | 9 | # 目前已经完成的功能 10 | 11 | - **无token实现登录,获取用户的信息** 12 | - **首页多种信息的展示** 13 | - **团队界面** 14 | - **知识库界面** 15 | - **部分的消息通知类型** 16 | - **文档查看添加删除编辑** 17 | - **一些动画** 18 | 19 | # 部分功能展示 20 | 21 | ![展示1.png](https://upload-images.jianshu.io/upload_images/7248113-ce4c79c9ab6d6bd5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 22 | 23 | ![展示2.png](https://upload-images.jianshu.io/upload_images/7248113-1f51f382f81c46e3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | ![展示3.png](https://upload-images.jianshu.io/upload_images/7248113-74543b84a8cfe9ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 26 | 27 | ![展示4.png](https://upload-images.jianshu.io/upload_images/7248113-b490992a74146940.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:micro_sparrow/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/presenter/GroupPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:micro_sparrow/Api.dart'; 4 | import 'package:micro_sparrow/Utils/mvp.dart'; 5 | import 'package:micro_sparrow/View/IGroupView.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import 'package:http/http.dart' as http; 8 | 9 | 10 | class GroupPresenter implements IPresenter{ 11 | 12 | IGroupView view; 13 | 14 | @override 15 | init(IView view) { 16 | this.view = view; 17 | } 18 | 19 | void getGroupHttpData(String id) async{ 20 | SharedPreferences preferences = await SharedPreferences.getInstance(); 21 | String cookies = preferences.getString("cookies"); 22 | if(cookies == null){ 23 | view.getGroupHttpResult(null,false); 24 | return; 25 | } 26 | Map hearders = new Map(); 27 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 28 | hearders["User-Agent"] = "sparrow"; 29 | hearders["cookie"] = cookies; 30 | http.Response response = await http.get(Uri.parse(Api.book(id)), 31 | headers: hearders); 32 | var body = json.decode(response.body); 33 | view.getGroupHttpResult(body,true); 34 | } 35 | 36 | 37 | 38 | } -------------------------------------------------------------------------------- /lib/presenter/TeamPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:micro_sparrow/Api.dart'; 4 | import 'package:micro_sparrow/View/ITeamView.dart'; 5 | import 'package:micro_sparrow/Utils/mvp.dart'; 6 | import 'package:http/http.dart' as http; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | 10 | class TeamPresenter implements IPresenter{ 11 | 12 | ITeamView view; 13 | 14 | @override 15 | init(IView view) { 16 | this.view = view; 17 | return null; 18 | } 19 | 20 | void getTeamHttpData() async{ 21 | SharedPreferences preferences = await SharedPreferences.getInstance(); 22 | String cookies = preferences.getString("cookies"); 23 | if(cookies == null){ 24 | view.getTeamHttpResult(null,false); 25 | return; 26 | } 27 | Map hearders = new Map(); 28 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 29 | hearders["User-Agent"] = "sparrow"; 30 | hearders["cookie"] = cookies; 31 | http.Response response = await http.get(Uri.parse(Api.team), 32 | headers: hearders); 33 | var body = json.decode(response.body); 34 | view.getTeamHttpResult(body,true); 35 | } 36 | 37 | 38 | 39 | 40 | } -------------------------------------------------------------------------------- /lib/presenter/EditPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Api.dart'; 2 | import 'package:micro_sparrow/Utils/mvp.dart'; 3 | import 'package:micro_sparrow/View/IEditView.dart'; 4 | import 'package:micro_sparrow/model/CreateDoc_entity.dart'; 5 | import 'package:micro_sparrow/model/DocDetailSerializer_entity.dart'; 6 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 7 | 8 | class EditPresenter extends AbsPresenter implements IPresenter{ 9 | 10 | IEditView _view; 11 | DocdetailserializerEntity _detail; 12 | 13 | void setEntity(DocdetailserializerEntity entity){ 14 | this._detail = entity; 15 | } 16 | 17 | 18 | @override 19 | init(IView view) { 20 | this._view = view; 21 | } 22 | 23 | void createDoc(String id,CreatedocEntity doc) async{ 24 | if(_detail == null || _detail.data==null){ 25 | var result = await postHttpData(Api.createDoc(id), doc.toJson()); 26 | _detail = DocdetailserializerEntity.fromJson(result); 27 | } 28 | updateDoc(id,doc); 29 | } 30 | 31 | void updateDoc(String id,CreatedocEntity doc) async{ 32 | String url = Api.updateDoc(id, _detail.data.id.toString()); 33 | await putHttpData(url, doc.toJson()); 34 | _view.reslutOfPost(true,"保存成功"); 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /lib/presenter/SearchPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Api.dart'; 2 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 3 | import 'package:micro_sparrow/Utils/mvp.dart'; 4 | import 'package:micro_sparrow/View/ISearchView.dart'; 5 | import 'package:micro_sparrow/model/UserSearch_entity.dart'; 6 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 7 | 8 | class SearchPresenter extends AbsPresenter implements IPresenter{ 9 | 10 | ISearchView _view; 11 | 12 | @override 13 | init(IView view) { 14 | this._view = view; 15 | } 16 | 17 | void searchUser(String text) async{ 18 | var result = await getCookieHttpData(Api.searchUser(text)); 19 | UsersearchEntity entity = UsersearchEntity.fromJson(result); 20 | if(entity.data == null){ 21 | _view.resultOfSearch(false,null,SparrowException.NETWORK_ERROR); 22 | return; 23 | } 24 | _view.resultOfSearch(true,entity,SparrowException.OK); 25 | } 26 | 27 | void inviteUser(String repoId,String login) async{ 28 | String url = "https://www.yuque.com/api/v2/groups/$repoId/users/$login"; 29 | print(url); 30 | var result = await putHttpData(url, '{"role": 1}'); 31 | print(result); 32 | _view.resultOfInvite(true,null,SparrowException.OK); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/presenter/AllDocPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:micro_sparrow/Api.dart'; 4 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 5 | import 'package:micro_sparrow/Utils/mvp.dart'; 6 | import 'package:micro_sparrow/View/IAllDocView.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | import 'package:http/http.dart' as http; 9 | 10 | 11 | class AllDocPresenter implements IPresenter{ 12 | 13 | IAllDocView _view; 14 | 15 | 16 | @override 17 | init(IView view) { 18 | _view = view; 19 | } 20 | 21 | void getHttpData() async{ 22 | SharedPreferences preferences = await SharedPreferences.getInstance(); 23 | String cookies = preferences.getString("cookies"); 24 | if(cookies == null){ 25 | _view.getDocHttpResult(null,false,SparrowException.GET_COOKIES_FAIL); 26 | return; 27 | } 28 | Map hearders = new Map(); 29 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 30 | hearders["User-Agent"] = "sparrow"; 31 | hearders["cookie"] = cookies; 32 | http.Response response = await http.get(Uri.parse(Api.docApi), 33 | headers: hearders); 34 | var body = json.decode(response.body); 35 | _view.getDocHttpResult(body,true,"获取知识库数据成功"); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /lib/presenter/AllBookPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:micro_sparrow/Api.dart'; 4 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 5 | import 'package:micro_sparrow/Utils/mvp.dart'; 6 | import 'package:micro_sparrow/View/IAllBookView.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | import 'package:http/http.dart' as http; 9 | 10 | 11 | class AllBookPresenter implements IPresenter{ 12 | 13 | IAllBookView _view; 14 | 15 | @override 16 | init(IView view) { 17 | this._view = view; 18 | } 19 | 20 | void getHttpData() async { 21 | SharedPreferences preferences = await SharedPreferences.getInstance(); 22 | String cookies = preferences.getString("cookies"); 23 | if(cookies == null){ 24 | _view.getBookHttpResult(null,false,SparrowException.GET_COOKIES_FAIL); 25 | return; 26 | } 27 | Map hearders = new Map(); 28 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 29 | hearders["User-Agent"] = "sparrow"; 30 | hearders["cookie"] = cookies; 31 | http.Response response = await http.get(Uri.parse(Api.bookApi), 32 | headers: hearders); 33 | var body = json.decode(response.body); 34 | _view.getBookHttpResult(body,true,"获取知识库数据成功"); 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /lib/Widget/ReadArticle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 4 | 5 | class ReadArticle extends StatefulWidget{ 6 | ReadArticle({Key key, this.url}) : super(key: key); 7 | 8 | final String url; 9 | 10 | @override 11 | State createState() { 12 | return new _newPage(); 13 | } 14 | 15 | 16 | } 17 | 18 | class _newPage extends State{ 19 | @override 20 | Widget build(BuildContext context) { 21 | return WebviewScaffold(url: widget.url, 22 | withLocalStorage: true, 23 | enableAppScheme: true, 24 | withZoom: true, 25 | withJavascript: true, 26 | allowFileURLs: true, 27 | appBar:new AppBar( 28 | backgroundColor: Colors.white, 29 | elevation: 0, 30 | brightness: Brightness.light, 31 | actions: [ 32 | IconButton(icon: Icon(Icons.more_vert,color: Colors.black45,), onPressed: () {}, 33 | ) 34 | ], 35 | leading: IconButton(icon: Icon(Icons.arrow_back,color: Colors.black45,), onPressed: (){ 36 | Navigator.pop(context); 37 | }), 38 | ), 39 | initialChild: new Center(child: ColorLoader(radius: 15,dotRadius: 6,),), 40 | appCacheEnabled: true,); 41 | } 42 | } -------------------------------------------------------------------------------- /res/compare.txt: -------------------------------------------------------------------------------- 1 | language=zh-cn; UM_distinctid=1678374ff8b3-02104ab3717bb68-4c312979-1fa400-1678374ff8c1b0; CNZZDATA1272061571=1509760947-1544096540-%7C1547427945; _uab_collina=154409874222177758464272; _yuque_session=M2TWKm18XA3-1ymDgl3qqUX6GDQOnD4crjp_WokvfAa7PZlzzwHe_lSGH6damsrmydLTr4TL2tNRvevA4FsoWVhm2oEfuDUjhpcEaG7i4PlU1PBaDxfjXjQlqmCy5XdFjtFOdRcx1Mt7v1Tn7SjKf-s23PGDCntGIfzwTRJu_K7XKSeOpTJ0bCXbWUE_SwcalfPXEJ4CEUIGBPsg4EWVteeeTqfR7ytdkIKnkXGoA0xVmjl-PCxsZ6niQ1mJ2XHPuqmODC5bJiuf2kGbWjLOlqsT2AxiAaYaRG1kg_ECUATgIAg3E07ADSrdPAKE0pR6; 2 | ctoken: LGxVFYAc4fZwb4sggsOxgD2k, language: zh-cn, UM_distinctid: 1684838b5e4c7-00aecfe303b7b3-4f5b6822-448e0-1684838b5e53ce, _uab_collina: 154739990275810253982784, CNZZDATA1272061571: 720719514-1547396493-%7C1547427945, tree: a385%0174d6e627-feb2-4510-adbe-6f2661dca074%014, _TRACERT_COOKIE__SESSION: 05a56ceb-8a58-417a-81d1-099e4f3b68c4 3 | ctoken=qObGNjYZUp7RiNpP2bfFi1lx; _TRACERT_COOKIE__SESSION=34b040ed-ffc1-4229-aabf-ffd5120eaf29; tree=a385%01756ee92a-0c1e-42e3-97e6-15971a2e00f0%012 4 | "ctoken=rdkhOQnlnKmVCCJuia9MlXMl; language=zh-cn; UM_distinctid=1684c8c39e81a7-0f52cda98c1749-10306653-1fa400-1684c8c39ee603; CNZZDATA1272061571=886972564-1547472084-https%253A%252F%252Fwww.baidu.com%252F%7C1547472084; _TRACERT_COOKIE__SESSION=f3a12b95-c82e-432f-9567-dc68c7e2990b; _uab_collina=154747248745062873048011; tree=a385%01a2ea324e-da76-43d6-aab6-3f396bcc7db3%013" 5 | 6 | -------------------------------------------------------------------------------- /lib/presenter/NotificationPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Api.dart'; 2 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 3 | import 'package:micro_sparrow/Utils/mvp.dart'; 4 | import 'package:micro_sparrow/View/INotificationView.dart'; 5 | import 'package:micro_sparrow/model/Notification_entity.dart'; 6 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 7 | 8 | class NotificationPresenter extends AbsPresenter implements IPresenter{ 9 | 10 | INotificationView _view; 11 | 12 | @override 13 | init(IView view) { 14 | this._view = view; 15 | } 16 | 17 | void getNotification() async{ 18 | var result = await getCookieHttpData(Api.unReadNotification); 19 | NotificationEntity entity = new NotificationEntity.fromJson(result); 20 | if(entity == null){ 21 | _view.resultOfNoti(false,null,SparrowException.NETWORK_ERROR); 22 | return; 23 | } 24 | _view.resultOfNoti(true,entity,SparrowException.OK); 25 | } 26 | 27 | void confirm(num subjectId,num notiId) async { 28 | String url = "https://www.yuque.com/api/group_users/"+ subjectId.toString() + "/accept"; 29 | //在处理请求之后,通过访问下面的url来让消息已读 30 | String delNoti = "https://www.yuque.com/go/notification/" + notiId.toString(); 31 | var result = await putHttpCsrfData(url, ""); 32 | await getCookieHttpData(delNoti); 33 | } 34 | 35 | void remove(num notiId) async { 36 | String delNoti = "https://www.yuque.com/go/notification/" + notiId.toString(); 37 | var result = await getCookieHttpData(delNoti); 38 | print(result); 39 | } 40 | 41 | 42 | 43 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | micro_sparrow 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/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 | -------------------------------------------------------------------------------- /lib/model/UserInfo_entity.dart: -------------------------------------------------------------------------------- 1 | class UserinfoEntity { 2 | Data data; 3 | 4 | UserinfoEntity({this.data}); 5 | 6 | UserinfoEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | 18 | void initData(){ 19 | this.data = new Data( 20 | id: 0, 21 | type: "", 22 | name: "", 23 | description: "", 24 | avatarUrl: "https://cdn.nlark.com/yuque/0/2018/png/219264/1543920414824-" 25 | "f13df41e-223f-42c0-87f2-811d21ef60c3.png?x-oss-process=image/resize,m_fill,w_48,h_48/format,png" 26 | ); 27 | } 28 | } 29 | 30 | class Data { 31 | num id; 32 | String type; 33 | String login; 34 | String name; 35 | String description; 36 | String avatarUrl; 37 | String createdAt; 38 | String updatedAt; 39 | 40 | Data({this.id, this.type, this.login, this.name, this.description, this.avatarUrl, this.createdAt, this.updatedAt}); 41 | 42 | Data.fromJson(Map json) { 43 | id = json['id']; 44 | type = json['type']; 45 | login = json['login']; 46 | name = json['name']; 47 | description = json['description']; 48 | avatarUrl = json['avatar_url']; 49 | createdAt = json['created_at']; 50 | updatedAt = json['updated_at']; 51 | } 52 | 53 | Map toJson() { 54 | final Map data = new Map(); 55 | data['id'] = this.id; 56 | data['type'] = this.type; 57 | data['login'] = this.login; 58 | data['name'] = this.name; 59 | data['description'] = this.description; 60 | data['avatar_url'] = this.avatarUrl; 61 | data['created_at'] = this.createdAt; 62 | data['updated_at'] = this.updatedAt; 63 | return data; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/model/Token_entity.dart: -------------------------------------------------------------------------------- 1 | class TokenEntity { 2 | Data data; 3 | 4 | TokenEntity({this.data}); 5 | 6 | TokenEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | } 18 | 19 | class Data { 20 | num id; 21 | String type; 22 | num userId; 23 | String description; 24 | String createdAt; 25 | String updatedAt; 26 | num spaceId; 27 | String scope; 28 | String token; 29 | String application; 30 | String sSerializer; 31 | 32 | Data({this.id, this.type, this.userId, this.description, this.createdAt, this.updatedAt, this.spaceId, this.scope, this.token, this.application, this.sSerializer}); 33 | 34 | Data.fromJson(Map json) { 35 | id = json['id']; 36 | type = json['type']; 37 | userId = json['user_id']; 38 | description = json['description']; 39 | createdAt = json['created_at']; 40 | updatedAt = json['updated_at']; 41 | spaceId = json['space_id']; 42 | scope = json['scope']; 43 | token = json['token']; 44 | application = json['application']; 45 | sSerializer = json['_serializer']; 46 | } 47 | 48 | Map toJson() { 49 | final Map data = new Map(); 50 | data['id'] = this.id; 51 | data['type'] = this.type; 52 | data['user_id'] = this.userId; 53 | data['description'] = this.description; 54 | data['created_at'] = this.createdAt; 55 | data['updated_at'] = this.updatedAt; 56 | data['space_id'] = this.spaceId; 57 | data['scope'] = this.scope; 58 | data['token'] = this.token; 59 | data['application'] = this.application; 60 | data['_serializer'] = this.sSerializer; 61 | return data; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/model/ExistToken_entity.dart: -------------------------------------------------------------------------------- 1 | class ExisttokenEntity { 2 | List data; 3 | 4 | ExisttokenEntity({this.data}); 5 | 6 | ExisttokenEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | String type; 25 | num userId; 26 | String description; 27 | String applicationId; 28 | String lastUsedAt; 29 | String createdAt; 30 | String updatedAt; 31 | num spaceId; 32 | String application; 33 | String sSerializer; 34 | 35 | Data({this.id, this.type, this.userId, this.description, this.applicationId, this.lastUsedAt, this.createdAt, this.updatedAt, this.spaceId, this.application, this.sSerializer}); 36 | 37 | Data.fromJson(Map json) { 38 | id = json['id']; 39 | type = json['type']; 40 | userId = json['user_id']; 41 | description = json['description']; 42 | applicationId = json['application_id']; 43 | lastUsedAt = json['last_used_at']; 44 | createdAt = json['created_at']; 45 | updatedAt = json['updated_at']; 46 | spaceId = json['space_id']; 47 | application = json['application']; 48 | sSerializer = json['_serializer']; 49 | } 50 | 51 | Map toJson() { 52 | final Map data = new Map(); 53 | data['id'] = this.id; 54 | data['type'] = this.type; 55 | data['user_id'] = this.userId; 56 | data['description'] = this.description; 57 | data['application_id'] = this.applicationId; 58 | data['last_used_at'] = this.lastUsedAt; 59 | data['created_at'] = this.createdAt; 60 | data['updated_at'] = this.updatedAt; 61 | data['space_id'] = this.spaceId; 62 | data['application'] = this.application; 63 | data['_serializer'] = this.sSerializer; 64 | return data; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; 9 | FlutterMethodChannel* batteryChannel = [FlutterMethodChannel 10 | methodChannelWithName:@"samples.flutter.io/yami" 11 | binaryMessenger:controller]; 12 | [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { 13 | // TODOif ([@"getBatteryLevel" isEqualToString:call.method]) { 14 | if ([@"getCookies" isEqualToString:call.method]) { 15 | NSString *cookies = [self getCookies]; 16 | result(cookies); 17 | }else if ([@"URLDecodedString" isEqualToString:call.method]){ 18 | NSString *code = call.arguments[@"code"]; 19 | NSString *str = [self URLDecodedString:code]; 20 | result(str); 21 | }else { 22 | result(FlutterMethodNotImplemented); 23 | } 24 | }]; 25 | [GeneratedPluginRegistrant registerWithRegistry:self]; 26 | // Override point for customization after application launch. 27 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 28 | } 29 | 30 | - (NSString *)getCookies{ 31 | NSArray *cookiesArray = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; 32 | if ([cookiesArray count] == 0) { 33 | return @""; 34 | } 35 | NSDictionary *cookieDict = [NSHTTPCookie requestHeaderFieldsWithCookies:cookiesArray]; 36 | NSString *cookie = [cookieDict objectForKey:@"Cookie"]; 37 | //分隔符逗号 38 | return cookie; 39 | } 40 | 41 | 42 | -(NSString *)URLDecodedString:(NSString *)str 43 | { 44 | NSString *decodedString=(__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)str, CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); 45 | 46 | return decodedString; 47 | } 48 | @end 49 | -------------------------------------------------------------------------------- /android/app/src/main/java/cn/yaminets/microsparrow/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.yaminets.microsparrow; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | import io.flutter.app.FlutterActivity; 8 | import io.flutter.plugin.common.MethodCall; 9 | import io.flutter.plugin.common.MethodChannel; 10 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 11 | import io.flutter.plugin.common.MethodChannel.Result; 12 | import android.webkit.CookieManager; 13 | import java.io.UnsupportedEncodingException; 14 | import java.net.URLDecoder; 15 | 16 | 17 | 18 | public class MainActivity extends FlutterActivity { 19 | private static final String CHANNEL = "samples.flutter.io/yami"; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( 25 | new MethodCallHandler() { 26 | @Override 27 | public void onMethodCall(MethodCall call, Result result) { 28 | if (call.method.equals("getCookies")) { 29 | String cookies = getCookies(); 30 | result.success(cookies); 31 | } else if(call.method.equals("URLDecodedString")){ 32 | String code = call.argument("code"); 33 | String str = URLDecodedString(code); 34 | result.success(str); 35 | } else { 36 | result.notImplemented(); 37 | } 38 | } 39 | }); 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ 41 | getWindow().setStatusBarColor(0); 42 | } 43 | GeneratedPluginRegistrant.registerWith(this); 44 | 45 | } 46 | 47 | private String getCookies(){ 48 | CookieManager cookieManager = CookieManager.getInstance(); 49 | String cookieStr = cookieManager.getCookie("https://www.yuque.com/dashboard"); 50 | return cookieStr; 51 | } 52 | 53 | private String URLDecodedString(String code){ 54 | try { 55 | String keyWord = URLDecoder.decode(code, "utf-8"); 56 | return keyWord; 57 | } catch (UnsupportedEncodingException e) { 58 | e.printStackTrace(); 59 | } 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/Api.dart: -------------------------------------------------------------------------------- 1 | class Api{ 2 | static String login = "https://www.yuque.com/login"; 3 | static String event = "https://www.yuque.com/api/events?offset=0"; 4 | static String team = "https://www.yuque.com/api/mine/groups?offset=0&q="; 5 | static String token = "https://www.yuque.com/api/access_tokens"; 6 | static String bookAllDoc(String id){ 7 | return "https://www.yuque.com/api/docs?book_id=" + id + "&include_last_editor=true&include_share=true&include_user=true"; 8 | } 9 | static String book(String id){ 10 | return "https://www.yuque.com/api/groups/" + id + "/books?include_summary=true"; 11 | } 12 | static String bookTopic(String bookId){ 13 | return "https://www.yuque.com/go/book/" + bookId; 14 | } 15 | 16 | static String bookApi = "https://www.yuque.com/api/mine/books?offset=0&q="; 17 | 18 | static String docApi = "https://www.yuque.com/api/mine/docs?offset=0&limit=15&q=&type=recent_updated"; 19 | static String historyDoc (int offset,int limit){ 20 | return "https://www.yuque.com/api/mine/docs?offset=" + offset.toString() + "&limit=" + limit.toString() + "&type=recent_updated"; 21 | } 22 | static String news (int offset){ 23 | return "https://www.yuque.com/api/space_posts?offset=" + offset.toString(); 24 | } 25 | static String getToken = "https://www.yuque.com/api/access_tokens?type=personal"; 26 | static String getTokenInfo(String id){ 27 | return "https://www.yuque.com/api/access_tokens/"+id; 28 | } 29 | 30 | static String createDoc(String id){ 31 | return "https://www.yuque.com/api/v2/repos/"+id+"/docs"; 32 | } 33 | 34 | static String updateDoc(String repo_id,String docId){ 35 | return "https://www.yuque.com/api/v2/repos/" + repo_id + "/docs/" + docId; 36 | } 37 | 38 | static String deleteBook(String repoId,String bookId){ 39 | return "https://www.yuque.com/api/v2/repos/$repoId/docs/$bookId"; 40 | } 41 | 42 | static String getBookInfo(String slug,String bookId){ 43 | return "https://www.yuque.com/api/docs/$slug?book_id=$bookId"; 44 | } 45 | 46 | static String getMyInfo(){ 47 | return "https://www.yuque.com/api/v2/user"; 48 | } 49 | 50 | static String unReadNotification = "https://www.yuque.com/api/notifications?offset=0&type=unread"; 51 | 52 | static String searchUser(String userName){ 53 | return "https://www.yuque.com/api/zsearch?p=1&q=$userName&scope=%2F&type=user"; 54 | } 55 | 56 | static String getUserInfo(String id){ 57 | return "https://www.yuque.com/api/v2/users/$id"; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | def keystorePropertiesFile = rootProject.file("key.properties") 27 | def keystoreProperties = new Properties() 28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 29 | android { 30 | compileSdkVersion 27 31 | 32 | lintOptions { 33 | disable 'InvalidPackage' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "cn.yaminets.microsparrow" 39 | minSdkVersion 16 40 | targetSdkVersion 27 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 44 | } 45 | 46 | signingConfigs { 47 | release { 48 | keyAlias keystoreProperties['keyAlias'] 49 | keyPassword keystoreProperties['keyPassword'] 50 | storeFile file(keystoreProperties['storeFile']) 51 | storePassword keystoreProperties['storePassword'] 52 | } 53 | } 54 | 55 | buildTypes { 56 | release { 57 | // TODO: Add your own signing config for the release build. 58 | // Signing with the debug keys for now, so `flutter run --release` works. 59 | signingConfig signingConfigs.debug 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | testImplementation 'junit:junit:4.12' 70 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 71 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 72 | } 73 | -------------------------------------------------------------------------------- /lib/model/FollowUserResponse.dart: -------------------------------------------------------------------------------- 1 | class FollowUserResponse { 2 | Data data; 3 | 4 | FollowUserResponse({this.data}); 5 | 6 | FollowUserResponse.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | } 18 | 19 | class Data { 20 | num id; 21 | num userId; 22 | String actionType; 23 | String actionName; 24 | String targetType; 25 | num targetId; 26 | String actionOption; 27 | String title; 28 | dynamic targetBookId; 29 | dynamic targetGroupId; 30 | String createdAt; 31 | String updatedAt; 32 | String targetGroup; 33 | String targetBook; 34 | String sUrl; 35 | String sSerializer; 36 | 37 | Data({this.id, this.userId, this.actionType, this.actionName, this.targetType, this.targetId, this.actionOption, this.title, this.targetBookId, this.targetGroupId, this.createdAt, this.updatedAt, this.targetGroup, this.targetBook, this.sUrl, this.sSerializer}); 38 | 39 | Data.fromJson(Map json) { 40 | id = json['id']; 41 | userId = json['user_id']; 42 | actionType = json['action_type']; 43 | actionName = json['action_name']; 44 | targetType = json['target_type']; 45 | targetId = json['target_id']; 46 | actionOption = json['action_option']; 47 | title = json['title']; 48 | targetBookId = json['target_book_id']; 49 | targetGroupId = json['target_group_id']; 50 | createdAt = json['created_at']; 51 | updatedAt = json['updated_at']; 52 | targetGroup = json['target_group']; 53 | targetBook = json['target_book']; 54 | sUrl = json['_url']; 55 | sSerializer = json['_serializer']; 56 | } 57 | 58 | Map toJson() { 59 | final Map data = new Map(); 60 | data['id'] = this.id; 61 | data['user_id'] = this.userId; 62 | data['action_type'] = this.actionType; 63 | data['action_name'] = this.actionName; 64 | data['target_type'] = this.targetType; 65 | data['target_id'] = this.targetId; 66 | data['action_option'] = this.actionOption; 67 | data['title'] = this.title; 68 | data['target_book_id'] = this.targetBookId; 69 | data['target_group_id'] = this.targetGroupId; 70 | data['created_at'] = this.createdAt; 71 | data['updated_at'] = this.updatedAt; 72 | data['target_group'] = this.targetGroup; 73 | data['target_book'] = this.targetBook; 74 | data['_url'] = this.sUrl; 75 | data['_serializer'] = this.sSerializer; 76 | return data; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/model/Followed_entity.dart: -------------------------------------------------------------------------------- 1 | class FollowedEntity { 2 | List data; 3 | 4 | FollowedEntity({this.data}); 5 | 6 | FollowedEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | num userId; 25 | String actionType; 26 | String actionName; 27 | String targetType; 28 | num targetId; 29 | String actionOption; 30 | String title; 31 | String targetBookId; 32 | String targetGroupId; 33 | String createdAt; 34 | String updatedAt; 35 | String targetGroup; 36 | String targetBook; 37 | String sUrl; 38 | String sSerializer; 39 | 40 | Data({this.id, this.userId, this.actionType, this.actionName, this.targetType, this.targetId, this.actionOption, this.title, this.targetBookId, this.targetGroupId, this.createdAt, this.updatedAt, this.targetGroup, this.targetBook, this.sUrl, this.sSerializer}); 41 | 42 | Data.fromJson(Map json) { 43 | id = json['id']; 44 | userId = json['user_id']; 45 | actionType = json['action_type']; 46 | actionName = json['action_name']; 47 | targetType = json['target_type']; 48 | targetId = json['target_id']; 49 | actionOption = json['action_option']; 50 | title = json['title']; 51 | targetBookId = json['target_book_id']; 52 | targetGroupId = json['target_group_id']; 53 | createdAt = json['created_at']; 54 | updatedAt = json['updated_at']; 55 | targetGroup = json['target_group']; 56 | targetBook = json['target_book']; 57 | sUrl = json['_url']; 58 | sSerializer = json['_serializer']; 59 | } 60 | 61 | Map toJson() { 62 | final Map data = new Map(); 63 | data['id'] = this.id; 64 | data['user_id'] = this.userId; 65 | data['action_type'] = this.actionType; 66 | data['action_name'] = this.actionName; 67 | data['target_type'] = this.targetType; 68 | data['target_id'] = this.targetId; 69 | data['action_option'] = this.actionOption; 70 | data['title'] = this.title; 71 | data['target_book_id'] = this.targetBookId; 72 | data['target_group_id'] = this.targetGroupId; 73 | data['created_at'] = this.createdAt; 74 | data['updated_at'] = this.updatedAt; 75 | data['target_group'] = this.targetGroup; 76 | data['target_book'] = this.targetBook; 77 | data['_url'] = this.sUrl; 78 | data['_serializer'] = this.sSerializer; 79 | return data; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: micro_sparrow 2 | description: A new Flutter application for language sparrow. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | http: ^0.12.0+1 19 | intl: ^0.15.7 20 | flutter_webview_plugin: ^0.3.0+2 21 | shared_preferences: ^0.4.3 22 | transparent_image: ^0.1.0 23 | flutter_markdown: ^0.2.0 24 | sqflite: ^1.0.0 25 | 26 | 27 | 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^0.1.2 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | 37 | 38 | # For information on the generic Dart part of this file, see the 39 | # following page: https://www.dartlang.org/tools/pub/pubspec 40 | 41 | # The following section is specific to Flutter. 42 | flutter: 43 | 44 | # The following line ensures that the Material Icons font is 45 | # included with your application, so that you can use the icons in 46 | # the material Icons class. 47 | uses-material-design: true 48 | 49 | 50 | # To add assets to your application, add an assets section, like this: 51 | assets: 52 | - res/backgroup.jpg 53 | - res/icon.jpg 54 | - res/工作台.png 55 | - res/logo.svg 56 | 57 | # An image asset can refer to one or more resolution-specific "variants", see 58 | # https://flutter.io/assets-and-images/#resolution-aware. 59 | 60 | # For details regarding adding assets from package dependencies, see 61 | # https://flutter.io/assets-and-images/#from-packages 62 | 63 | # To add custom fonts to your application, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts from package dependencies, 81 | # see https://flutter.io/custom-fonts/#from-packages -------------------------------------------------------------------------------- /lib/model/DocDetailSerializer_entity.dart: -------------------------------------------------------------------------------- 1 | class DocdetailserializerEntity { 2 | Data data; 3 | 4 | DocdetailserializerEntity({this.data}); 5 | 6 | DocdetailserializerEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | 18 | setData(String title,String content,int id){ 19 | this.data = new Data(title: title,body: content,id: id); 20 | } 21 | } 22 | 23 | class Data { 24 | num id; 25 | String slug; 26 | String title; 27 | num bookId; 28 | num userId; 29 | String format; 30 | String body; 31 | String bodyDraft; 32 | String bodyHtml; 33 | num public; 34 | num status; 35 | String contentUpdatedAt; 36 | String createdAt; 37 | String updatedAt; 38 | String publishedAt; 39 | num wordCount; 40 | String description; 41 | String sSerializer; 42 | 43 | Data({this.id, this.slug, this.title, this.bookId, this.userId, this.format, this.body, this.bodyDraft, this.bodyHtml, this.public, this.status, this.contentUpdatedAt, this.createdAt, this.updatedAt, this.publishedAt, this.wordCount, this.description, this.sSerializer}); 44 | 45 | Data.fromJson(Map json) { 46 | id = json['id']; 47 | slug = json['slug']; 48 | title = json['title']; 49 | bookId = json['book_id']; 50 | userId = json['user_id']; 51 | format = json['format']; 52 | body = json['body']; 53 | bodyDraft = json['body_draft']; 54 | bodyHtml = json['body_html']; 55 | public = json['public']; 56 | status = json['status']; 57 | contentUpdatedAt = json['content_updated_at']; 58 | createdAt = json['created_at']; 59 | updatedAt = json['updated_at']; 60 | publishedAt = json['published_at']; 61 | wordCount = json['word_count']; 62 | description = json['description']; 63 | sSerializer = json['_serializer']; 64 | } 65 | 66 | Map toJson() { 67 | final Map data = new Map(); 68 | data['id'] = this.id; 69 | data['slug'] = this.slug; 70 | data['title'] = this.title; 71 | data['book_id'] = this.bookId; 72 | data['user_id'] = this.userId; 73 | data['format'] = this.format; 74 | data['body'] = this.body; 75 | data['body_draft'] = this.bodyDraft; 76 | data['body_html'] = this.bodyHtml; 77 | data['public'] = this.public; 78 | data['status'] = this.status; 79 | data['content_updated_at'] = this.contentUpdatedAt; 80 | data['created_at'] = this.createdAt; 81 | data['updated_at'] = this.updatedAt; 82 | data['published_at'] = this.publishedAt; 83 | data['word_count'] = this.wordCount; 84 | data['description'] = this.description; 85 | data['_serializer'] = this.sSerializer; 86 | return data; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/model/Team_entity.dart: -------------------------------------------------------------------------------- 1 | class TeamEntity { 2 | List data; 3 | 4 | TeamEntity({this.data}); 5 | 6 | TeamEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | String type; 25 | String login; 26 | String name; 27 | String description; 28 | String avatarUrl; 29 | num ownerId; 30 | num booksCount; 31 | num publicBooksCount; 32 | num topicsCount; 33 | num publicTopicsCount; 34 | num membersCount; 35 | num public; 36 | String createdAt; 37 | String updatedAt; 38 | String pinnedAt; 39 | num groupUserId; 40 | String sSerializer; 41 | 42 | Data({this.id, this.type, this.login, this.name, this.description, this.avatarUrl, this.ownerId, this.booksCount, this.publicBooksCount, this.topicsCount, this.publicTopicsCount, this.membersCount, this.public, this.createdAt, this.updatedAt, this.pinnedAt, this.groupUserId, this.sSerializer}); 43 | 44 | Data.fromJson(Map json) { 45 | id = json['id']; 46 | type = json['type']; 47 | login = json['login']; 48 | name = json['name']; 49 | description = json['description']; 50 | avatarUrl = json['avatar_url']; 51 | ownerId = json['owner_id']; 52 | booksCount = json['books_count']; 53 | publicBooksCount = json['public_books_count']; 54 | topicsCount = json['topics_count']; 55 | publicTopicsCount = json['public_topics_count']; 56 | membersCount = json['members_count']; 57 | public = json['public']; 58 | createdAt = json['created_at']; 59 | updatedAt = json['updated_at']; 60 | pinnedAt = json['pinned_at']; 61 | groupUserId = json['group_user_id']; 62 | sSerializer = json['_serializer']; 63 | } 64 | 65 | Map toJson() { 66 | final Map data = new Map(); 67 | data['id'] = this.id; 68 | data['type'] = this.type; 69 | data['login'] = this.login; 70 | data['name'] = this.name; 71 | data['description'] = this.description; 72 | data['avatar_url'] = this.avatarUrl; 73 | data['owner_id'] = this.ownerId; 74 | data['books_count'] = this.booksCount; 75 | data['public_books_count'] = this.publicBooksCount; 76 | data['topics_count'] = this.topicsCount; 77 | data['public_topics_count'] = this.publicTopicsCount; 78 | data['members_count'] = this.membersCount; 79 | data['public'] = this.public; 80 | data['created_at'] = this.createdAt; 81 | data['updated_at'] = this.updatedAt; 82 | data['pinned_at'] = this.pinnedAt; 83 | data['group_user_id'] = this.groupUserId; 84 | data['_serializer'] = this.sSerializer; 85 | return data; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/model/User_entity.dart: -------------------------------------------------------------------------------- 1 | class UserEntity { 2 | Data data; 3 | 4 | UserEntity({this.data}); 5 | 6 | UserEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | } 18 | 19 | class Data { 20 | num id; 21 | String type; 22 | num spaceId; 23 | num accountId; 24 | String login; 25 | String name; 26 | String avatarUrl; 27 | String largeAvatarUrl; 28 | String mediumAvatarUrl; 29 | String smallAvatarUrl; 30 | num booksCount; 31 | num publicBooksCount; 32 | num followersCount; 33 | num followingCount; 34 | num public; 35 | String description; 36 | String createdAt; 37 | String updatedAt; 38 | String sSerializer; 39 | 40 | Data({this.id, this.type, this.spaceId, this.accountId, this.login, this.name, this.avatarUrl, this.largeAvatarUrl, this.mediumAvatarUrl, this.smallAvatarUrl, this.booksCount, this.publicBooksCount, this.followersCount, this.followingCount, this.public, this.description, this.createdAt, this.updatedAt, this.sSerializer}); 41 | 42 | Data.fromJson(Map json) { 43 | id = json['id']; 44 | type = json['type']; 45 | spaceId = json['space_id']; 46 | accountId = json['account_id']; 47 | login = json['login']; 48 | name = json['name']; 49 | avatarUrl = json['avatar_url']; 50 | largeAvatarUrl = json['large_avatar_url']; 51 | mediumAvatarUrl = json['medium_avatar_url']; 52 | smallAvatarUrl = json['small_avatar_url']; 53 | booksCount = json['books_count']; 54 | publicBooksCount = json['public_books_count']; 55 | followersCount = json['followers_count']; 56 | followingCount = json['following_count']; 57 | public = json['public']; 58 | description = json['description']; 59 | createdAt = json['created_at']; 60 | updatedAt = json['updated_at']; 61 | sSerializer = json['_serializer']; 62 | } 63 | 64 | Map toJson() { 65 | final Map data = new Map(); 66 | data['id'] = this.id; 67 | data['type'] = this.type; 68 | data['space_id'] = this.spaceId; 69 | data['account_id'] = this.accountId; 70 | data['login'] = this.login; 71 | data['name'] = this.name; 72 | data['avatar_url'] = this.avatarUrl; 73 | data['large_avatar_url'] = this.largeAvatarUrl; 74 | data['medium_avatar_url'] = this.mediumAvatarUrl; 75 | data['small_avatar_url'] = this.smallAvatarUrl; 76 | data['books_count'] = this.booksCount; 77 | data['public_books_count'] = this.publicBooksCount; 78 | data['followers_count'] = this.followersCount; 79 | data['following_count'] = this.followingCount; 80 | data['public'] = this.public; 81 | data['description'] = this.description; 82 | data['created_at'] = this.createdAt; 83 | data['updated_at'] = this.updatedAt; 84 | data['_serializer'] = this.sSerializer; 85 | return data; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/View/AllBookView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:micro_sparrow/View/BookView.dart'; 3 | import 'package:micro_sparrow/View/GroupView.dart'; 4 | import 'package:micro_sparrow/View/IAllBookView.dart'; 5 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 6 | import 'package:micro_sparrow/model/AllBook_entity.dart'; 7 | import 'package:micro_sparrow/presenter/AllBookPresenter.dart'; 8 | 9 | class AllBookView extends StatefulWidget{ 10 | @override 11 | State createState() { 12 | // TODO: implement createState 13 | return new _AllBookView(); 14 | } 15 | 16 | } 17 | 18 | class _AllBookView extends State implements IAllBookView{ 19 | 20 | AllBookPresenter _presenter = new AllBookPresenter(); 21 | AllbookEntity _allBookEntity; 22 | bool isLoading = true; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _presenter.init(this); 28 | _presenter.getHttpData(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return new CustomScrollView( 34 | slivers: [ 35 | SliverAppBar( 36 | centerTitle: true, 37 | title: new Text("知识库"), 38 | floating: false, 39 | pinned: true, 40 | ), 41 | Builder(builder: (BuildContext context){ 42 | return isLoading ? SliverFillRemaining(child: new Center(child: ColorLoader(radius: 15,dotRadius: 6,),),): 43 | SliverList(delegate: SliverChildBuilderDelegate((BuildContext context,int position){ 44 | return getItem(position); 45 | },childCount: getLength())); 46 | }) 47 | ], 48 | ); 49 | } 50 | 51 | Widget getItem(int position) { 52 | return new Column( 53 | children: [ 54 | ListTile( 55 | onTap: (){ 56 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 57 | return new BookView( 58 | id: _allBookEntity.data[position].id.toString(), 59 | title: _allBookEntity.data[position].name, 60 | ); 61 | })); 62 | }, 63 | leading: CircleAvatar( 64 | backgroundColor: Colors.green, 65 | child: new Container( 66 | child: Icon(Icons.collections_bookmark,color: Colors.white,), 67 | ), 68 | ), 69 | title: new Text(_allBookEntity.data[position].name), 70 | subtitle: new Text(_allBookEntity.data[position].itemsCount.toString()+ "篇文章 · " + _allBookEntity.data[position].watchesCount.toString() + "关注"), 71 | ), 72 | new Divider(height: 1,) 73 | ], 74 | ); 75 | } 76 | 77 | getLength() { 78 | if(_allBookEntity==null || _allBookEntity.data == null){ 79 | return 0; 80 | }else{ 81 | return _allBookEntity.data.length; 82 | } 83 | } 84 | 85 | @override 86 | void getBookHttpResult(body, bool param1, String s) { 87 | isLoading = false; 88 | if(param1){ 89 | setState(() { 90 | _allBookEntity = AllbookEntity.fromJson(body); 91 | }); 92 | } 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/model/BookSuccessResponse_entity.dart: -------------------------------------------------------------------------------- 1 | class BookResponseEntity { 2 | Data data; 3 | 4 | BookResponseEntity({this.data}); 5 | 6 | BookResponseEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | } 18 | 19 | class Data { 20 | num id; 21 | String slug; 22 | String title; 23 | num bookId; 24 | num userId; 25 | String format; 26 | String body; 27 | String bodyDraft; 28 | num public; 29 | num status; 30 | num likesCount; 31 | num commentsCount; 32 | String contentUpdatedAt; 33 | String deletedAt; 34 | String createdAt; 35 | String updatedAt; 36 | String publishedAt; 37 | String firstPublishedAt; 38 | double wordCount; 39 | String cover; 40 | String description; 41 | String customDescription; 42 | String creator; 43 | String book; 44 | String sSerializer; 45 | 46 | Data({this.id, this.slug, this.title, this.bookId, this.userId, this.format, this.body, this.bodyDraft, this.public, this.status, this.likesCount, this.commentsCount, this.contentUpdatedAt, this.deletedAt, this.createdAt, this.updatedAt, this.publishedAt, this.firstPublishedAt, this.wordCount, this.cover, this.description, this.customDescription, this.creator, this.book, this.sSerializer}); 47 | 48 | Data.fromJson(Map json) { 49 | id = json['id']; 50 | slug = json['slug']; 51 | title = json['title']; 52 | bookId = json['book_id']; 53 | userId = json['user_id']; 54 | format = json['format']; 55 | body = json['body']; 56 | bodyDraft = json['body_draft']; 57 | public = json['public']; 58 | status = json['status']; 59 | likesCount = json['likes_count']; 60 | commentsCount = json['comments_count']; 61 | contentUpdatedAt = json['content_updated_at']; 62 | deletedAt = json['deleted_at']; 63 | createdAt = json['created_at']; 64 | updatedAt = json['updated_at']; 65 | publishedAt = json['published_at']; 66 | firstPublishedAt = json['first_published_at']; 67 | wordCount = json['word_count']; 68 | cover = json['cover']; 69 | description = json['description']; 70 | customDescription = json['custom_description']; 71 | creator = json['creator']; 72 | book = json['book']; 73 | sSerializer = json['_serializer']; 74 | } 75 | 76 | Map toJson() { 77 | final Map data = new Map(); 78 | data['id'] = this.id; 79 | data['slug'] = this.slug; 80 | data['title'] = this.title; 81 | data['book_id'] = this.bookId; 82 | data['user_id'] = this.userId; 83 | data['format'] = this.format; 84 | data['body'] = this.body; 85 | data['body_draft'] = this.bodyDraft; 86 | data['public'] = this.public; 87 | data['status'] = this.status; 88 | data['likes_count'] = this.likesCount; 89 | data['comments_count'] = this.commentsCount; 90 | data['content_updated_at'] = this.contentUpdatedAt; 91 | data['deleted_at'] = this.deletedAt; 92 | data['created_at'] = this.createdAt; 93 | data['updated_at'] = this.updatedAt; 94 | data['published_at'] = this.publishedAt; 95 | data['first_published_at'] = this.firstPublishedAt; 96 | data['word_count'] = this.wordCount; 97 | data['cover'] = this.cover; 98 | data['description'] = this.description; 99 | data['custom_description'] = this.customDescription; 100 | data['creator'] = this.creator; 101 | data['book'] = this.book; 102 | data['_serializer'] = this.sSerializer; 103 | return data; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/View/SearchView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:micro_sparrow/View/ISearchView.dart'; 3 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 4 | import 'package:micro_sparrow/model/UserSearch_entity.dart'; 5 | import 'package:micro_sparrow/presenter/SearchPresenter.dart'; 6 | 7 | class SearchView extends StatefulWidget{ 8 | SearchView({Key key, this.id}) : super(key: key); 9 | 10 | final String id; 11 | 12 | @override 13 | State createState() { 14 | return new _SearchPage(); 15 | } 16 | 17 | 18 | } 19 | 20 | class _SearchPage extends State implements ISearchView{ 21 | 22 | TextEditingController _textEditingController = new TextEditingController(); 23 | SearchPresenter _presenter = new SearchPresenter(); 24 | bool content = false; 25 | bool isLoading = false; 26 | UsersearchEntity _usersearchEntity; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | _presenter.init(this); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | appBar: AppBar( 38 | backgroundColor: Colors.white, 39 | elevation: 1, 40 | iconTheme: IconThemeData(color: Colors.black87), 41 | brightness: Brightness.light, 42 | title: new TextField( 43 | controller: _textEditingController, 44 | decoration: new InputDecoration.collapsed( 45 | hintText: '用户', 46 | ), 47 | ), 48 | actions: [ 49 | IconButton(icon: Icon(Icons.search), onPressed: (){ 50 | setState(() { 51 | isLoading = true; 52 | }); 53 | _presenter.searchUser(_textEditingController.text); 54 | }) 55 | ], 56 | ), 57 | body: new Center( 58 | child: _getContent(), 59 | ), 60 | ); 61 | } 62 | 63 | @override 64 | void resultOfSearch(bool param0, UsersearchEntity entity, String ok) { 65 | setState(() { 66 | isLoading = false; 67 | }); 68 | if(param0){ 69 | setState(() { 70 | if(entity.data.numhits==0){ 71 | content = false; 72 | }else{ 73 | content = true; 74 | } 75 | _usersearchEntity = entity; 76 | }); 77 | } 78 | } 79 | 80 | Widget getItem(int position) { 81 | return new ListTile( 82 | leading: CircleAvatar( 83 | backgroundImage: Image.network(_usersearchEntity.data.hits[position].avatarUrl).image, 84 | ), 85 | title: Text(_usersearchEntity.data.hits[position].name.replaceAll('', "").replaceAll('', "")), 86 | subtitle: Text(_usersearchEntity.data.hits[position].login), 87 | trailing: new FlatButton(onPressed: (){ 88 | _presenter.inviteUser(widget.id, _usersearchEntity.data.hits[position].login); 89 | },child: Text("邀请加入",style: TextStyle(color: Colors.blue),),), 90 | ); 91 | } 92 | 93 | getLength() { 94 | if(_usersearchEntity.data == null){ 95 | return 0; 96 | }else{ 97 | return _usersearchEntity.data.numhits; 98 | } 99 | } 100 | 101 | _getContent() { 102 | if(!isLoading){ 103 | if(content){ 104 | return ListView.builder(itemBuilder: (BuildContext context,int position){ 105 | return getItem(position); 106 | },itemCount: getLength(),); 107 | }else{ 108 | return Text("这里什么都没有"); 109 | } 110 | }else{ 111 | return ColorLoader(radius: 15,dotRadius: 6,); 112 | } 113 | 114 | } 115 | 116 | @override 117 | void resultOfInvite(bool param0, param1, String ok) { 118 | // TODO: implement resultOfInvite 119 | } 120 | 121 | 122 | 123 | } -------------------------------------------------------------------------------- /lib/View/NotificationView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:micro_sparrow/View/INotificationView.dart'; 3 | import 'package:micro_sparrow/View/ITeamView.dart'; 4 | import 'package:micro_sparrow/model/Notification_entity.dart'; 5 | import 'package:micro_sparrow/presenter/NotificationPresenter.dart'; 6 | 7 | class NotificationView extends StatefulWidget{ 8 | @override 9 | State createState() { 10 | return new _NotificationPage(); 11 | } 12 | 13 | } 14 | 15 | class _NotificationPage extends State implements INotificationView{ 16 | 17 | NotificationEntity _entity; 18 | NotificationPresenter _presenter = new NotificationPresenter(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _presenter.init(this); 24 | _presenter.getNotification(); 25 | } 26 | 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: new AppBar( 32 | title: Text("消息通知"), 33 | centerTitle: true, 34 | ), 35 | body: new ListView.builder(itemBuilder: (BuildContext context,int position){ 36 | return getItem(position); 37 | },itemCount: getLength(),), 38 | ); 39 | } 40 | 41 | Widget getItem(int position) { 42 | return Dismissible(key: new Key(position.toString()), 43 | onDismissed: (direction){ 44 | _presenter.remove(_entity.data.notifications[position].id); 45 | _entity.data.notifications.removeAt(position); 46 | }, 47 | child: Column( 48 | children: [ 49 | getContent(position), 50 | new Divider(height: 1,) 51 | ], 52 | ) 53 | ); 54 | } 55 | 56 | getLength() { 57 | if(_entity == null){ 58 | return 0; 59 | }else{ 60 | return _entity.data.notifications.length; 61 | } 62 | } 63 | 64 | @override 65 | void resultOfNoti(bool param0, NotificationEntity entity, String ok) { 66 | if(param0){ 67 | setState(() { 68 | _entity = entity; 69 | }); 70 | } 71 | } 72 | 73 | getContent(int position) { 74 | if(_entity.data.notifications[position].notifyType == "group_invitation"){ 75 | return new ListTile( 76 | onTap: (){ 77 | }, 78 | leading: CircleAvatar( 79 | backgroundImage: Image.network(_entity.data.notifications[position].actor.avatarUrl).image, 80 | ), 81 | trailing: new InkWell( 82 | child: Icon(Icons.check,color: Colors.blue,), 83 | onTap: (){ 84 | _presenter.confirm(_entity.data.notifications[position].subjectId,_entity.data.notifications[position].id); 85 | setState(() { 86 | _entity.data.notifications.removeAt(position); 87 | }); 88 | }, 89 | ), 90 | subtitle: Text(_entity.data.notifications[position].createdAt), 91 | title: Text.rich(TextSpan( 92 | children: [ 93 | TextSpan( 94 | text: _entity.data.notifications[position].actor.name, 95 | style: TextStyle(fontWeight: FontWeight.bold), 96 | ), 97 | TextSpan( 98 | text: " 邀请你加入 " 99 | ), 100 | TextSpan( 101 | text: _entity.data.notifications[position].secondSubject.name, 102 | style: TextStyle(fontWeight: FontWeight.bold), 103 | ), 104 | TextSpan( 105 | text: " 团队 " 106 | ), 107 | ] 108 | )), 109 | ); 110 | }else{ 111 | return new ListTile( 112 | leading: CircleAvatar( 113 | backgroundColor: Colors.green, 114 | child: Icon(Icons.mood_bad,color: Colors.white,), 115 | ), 116 | title: Text("暂不支持的消息通知类型"), 117 | ); 118 | } 119 | } 120 | 121 | 122 | } -------------------------------------------------------------------------------- /res/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lib/View/TeamView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:micro_sparrow/View/GroupView.dart'; 3 | import 'package:micro_sparrow/View/ITeamView.dart'; 4 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 5 | import 'package:micro_sparrow/presenter/TeamPresenter.dart'; 6 | import 'package:micro_sparrow/model/Team_entity.dart'; 7 | 8 | 9 | 10 | 11 | class TeamView extends StatefulWidget{ 12 | 13 | @override 14 | State createState() { 15 | return new _teamPage(); 16 | } 17 | 18 | 19 | } 20 | 21 | // ignore: camel_case_types 22 | class _teamPage extends State implements ITeamView{ 23 | 24 | TeamPresenter presenter = new TeamPresenter(); 25 | TeamEntity teamEntity = new TeamEntity(); 26 | bool isLoading = true; 27 | 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | presenter.init(this); 33 | presenter.getTeamHttpData(); 34 | } 35 | 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return new CustomScrollView( 40 | slivers: [ 41 | new SliverAppBar( 42 | title: new Text("团队"), 43 | floating: false, 44 | pinned: true, 45 | centerTitle: true, 46 | actions: [ 47 | IconButton( 48 | icon: Icon(Icons.add,color: Colors.white,), 49 | onPressed: (){ 50 | }, 51 | ) 52 | ], 53 | ), 54 | Builder(builder: (BuildContext context){ 55 | return isLoading ? SliverFillRemaining(child: new Center(child: ColorLoader(radius: 15,dotRadius: 6,),),) : 56 | new SliverList( 57 | delegate:SliverChildBuilderDelegate((BuildContext context,int position){ 58 | return _getTeamItem(position); 59 | },childCount: _getTeamLength()) 60 | ); 61 | }), 62 | ], 63 | ); 64 | } 65 | 66 | Widget _getTeamItem(int position) { 67 | return new InkWell( 68 | onTap: (){ 69 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 70 | return new GroupView( 71 | imageUrl: teamEntity.data[position].avatarUrl, 72 | id: teamEntity.data[position].id.toString(), 73 | title: teamEntity.data[position].name, 74 | peopleNum: teamEntity.data[position].membersCount.toString(), 75 | subscripe: teamEntity.data[position].description, 76 | ); 77 | })); 78 | }, 79 | child: new Column( 80 | children: [ 81 | new ListTile( 82 | leading: Hero(tag: teamEntity.data[position].id.toString(), 83 | child: CircleAvatar( 84 | backgroundImage: NetworkImage(teamEntity.data[position].avatarUrl), 85 | ) 86 | ), 87 | title: new Text(teamEntity.data[position].name), 88 | subtitle: new Text(teamEntity.data[position].description), 89 | trailing: _getLock(position), 90 | ), 91 | new Divider(height: 1,) 92 | ], 93 | ) 94 | ); 95 | } 96 | 97 | @override 98 | void getTeamHttpResult(body, bool param1) { 99 | if(param1){ 100 | setState(() { 101 | teamEntity = TeamEntity.fromJson(body); 102 | isLoading = false; 103 | if(teamEntity.data.length == 0){ 104 | print("team is null "); 105 | }else{ 106 | print("get teams data successfully"); 107 | } 108 | }); 109 | } 110 | } 111 | 112 | _getTeamLength() { 113 | if(teamEntity.data == null || teamEntity.data.length ==null){ 114 | return 0; 115 | }else{ 116 | return teamEntity.data.length; 117 | } 118 | } 119 | 120 | _getLock(int position) { 121 | if(teamEntity.data[position].public == 0){ 122 | return Icon(Icons.lock,color: Colors.blue,); 123 | }else{ 124 | return Icon(Icons.lock_open,color: Colors.blue,); 125 | } 126 | } 127 | 128 | 129 | 130 | } -------------------------------------------------------------------------------- /lib/model/Follow_entity.dart: -------------------------------------------------------------------------------- 1 | class FollowEntity { 2 | List data; 3 | 4 | FollowEntity({this.data}); 5 | 6 | FollowEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | String type; 25 | String login; 26 | String name; 27 | String description; 28 | String avatar; 29 | Profile profile; 30 | String avatarUrl; 31 | String largeAvatarUrl; 32 | String mediumAvatarUrl; 33 | String smallAvatarUrl; 34 | num followersCount; 35 | num followingCount; 36 | num role; 37 | num status; 38 | String createdAt; 39 | String updatedAt; 40 | String sSerializer; 41 | 42 | Data({this.id, this.type, this.login, this.name, this.description, this.avatar, this.profile, this.avatarUrl, this.largeAvatarUrl, this.mediumAvatarUrl, this.smallAvatarUrl, this.followersCount, this.followingCount, this.role, this.status, this.createdAt, this.updatedAt, this.sSerializer}); 43 | 44 | Data.fromJson(Map json) { 45 | id = json['id']; 46 | type = json['type']; 47 | login = json['login']; 48 | name = json['name']; 49 | description = json['description']; 50 | avatar = json['avatar']; 51 | profile = json['profile'] != null ? new Profile.fromJson(json['profile']) : null; 52 | avatarUrl = json['avatar_url']; 53 | largeAvatarUrl = json['large_avatar_url']; 54 | mediumAvatarUrl = json['medium_avatar_url']; 55 | smallAvatarUrl = json['small_avatar_url']; 56 | followersCount = json['followers_count']; 57 | followingCount = json['following_count']; 58 | role = json['role']; 59 | status = json['status']; 60 | createdAt = json['created_at']; 61 | updatedAt = json['updated_at']; 62 | sSerializer = json['_serializer']; 63 | } 64 | 65 | Map toJson() { 66 | final Map data = new Map(); 67 | data['id'] = this.id; 68 | data['type'] = this.type; 69 | data['login'] = this.login; 70 | data['name'] = this.name; 71 | data['description'] = this.description; 72 | data['avatar'] = this.avatar; 73 | if (this.profile != null) { 74 | data['profile'] = this.profile.toJson(); 75 | } 76 | data['avatar_url'] = this.avatarUrl; 77 | data['large_avatar_url'] = this.largeAvatarUrl; 78 | data['medium_avatar_url'] = this.mediumAvatarUrl; 79 | data['small_avatar_url'] = this.smallAvatarUrl; 80 | data['followers_count'] = this.followersCount; 81 | data['following_count'] = this.followingCount; 82 | data['role'] = this.role; 83 | data['status'] = this.status; 84 | data['created_at'] = this.createdAt; 85 | data['updated_at'] = this.updatedAt; 86 | data['_serializer'] = this.sSerializer; 87 | return data; 88 | } 89 | } 90 | 91 | class Profile { 92 | num id; 93 | num userId; 94 | String location; 95 | String jobTitle; 96 | String department; 97 | String createdAt; 98 | String updatedAt; 99 | String sSerializer; 100 | 101 | Profile({this.id, this.userId, this.location, this.jobTitle, this.department, this.createdAt, this.updatedAt, this.sSerializer}); 102 | 103 | Profile.fromJson(Map json) { 104 | id = json['id']; 105 | userId = json['user_id']; 106 | location = json['location']; 107 | jobTitle = json['job_title']; 108 | department = json['department']; 109 | createdAt = json['created_at']; 110 | updatedAt = json['updated_at']; 111 | sSerializer = json['_serializer']; 112 | } 113 | 114 | Map toJson() { 115 | final Map data = new Map(); 116 | data['id'] = this.id; 117 | data['user_id'] = this.userId; 118 | data['location'] = this.location; 119 | data['job_title'] = this.jobTitle; 120 | data['department'] = this.department; 121 | data['created_at'] = this.createdAt; 122 | data['updated_at'] = this.updatedAt; 123 | data['_serializer'] = this.sSerializer; 124 | return data; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/model/AllBook_entity.dart: -------------------------------------------------------------------------------- 1 | class AllbookEntity { 2 | List data; 3 | 4 | AllbookEntity({this.data}); 5 | 6 | AllbookEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | String type; 25 | String slug; 26 | String name; 27 | num userId; 28 | String description; 29 | num itemsCount; 30 | num likesCount; 31 | num watchesCount; 32 | num creatorId; 33 | num public; 34 | String createdAt; 35 | String updatedAt; 36 | String contentUpdatedAt; 37 | String pinnedAt; 38 | String archivedAt; 39 | User user; 40 | String sSerializer; 41 | 42 | Data({this.id, this.type, this.slug, this.name, this.userId, this.description, this.itemsCount, this.likesCount, this.watchesCount, this.creatorId, this.public, this.createdAt, this.updatedAt, this.contentUpdatedAt, this.pinnedAt, this.archivedAt, this.user, this.sSerializer}); 43 | 44 | Data.fromJson(Map json) { 45 | id = json['id']; 46 | type = json['type']; 47 | slug = json['slug']; 48 | name = json['name']; 49 | userId = json['user_id']; 50 | description = json['description']; 51 | itemsCount = json['items_count']; 52 | likesCount = json['likes_count']; 53 | watchesCount = json['watches_count']; 54 | creatorId = json['creator_id']; 55 | public = json['public']; 56 | createdAt = json['created_at']; 57 | updatedAt = json['updated_at']; 58 | contentUpdatedAt = json['content_updated_at']; 59 | pinnedAt = json['pinned_at']; 60 | archivedAt = json['archived_at']; 61 | user = json['user'] != null ? new User.fromJson(json['user']) : null; 62 | sSerializer = json['_serializer']; 63 | } 64 | 65 | Map toJson() { 66 | final Map data = new Map(); 67 | data['id'] = this.id; 68 | data['type'] = this.type; 69 | data['slug'] = this.slug; 70 | data['name'] = this.name; 71 | data['user_id'] = this.userId; 72 | data['description'] = this.description; 73 | data['items_count'] = this.itemsCount; 74 | data['likes_count'] = this.likesCount; 75 | data['watches_count'] = this.watchesCount; 76 | data['creator_id'] = this.creatorId; 77 | data['public'] = this.public; 78 | data['created_at'] = this.createdAt; 79 | data['updated_at'] = this.updatedAt; 80 | data['content_updated_at'] = this.contentUpdatedAt; 81 | data['pinned_at'] = this.pinnedAt; 82 | data['archived_at'] = this.archivedAt; 83 | if (this.user != null) { 84 | data['user'] = this.user.toJson(); 85 | } 86 | data['_serializer'] = this.sSerializer; 87 | return data; 88 | } 89 | } 90 | 91 | class User { 92 | num id; 93 | String type; 94 | String login; 95 | String name; 96 | String description; 97 | String avatarUrl; 98 | num public; 99 | String createdAt; 100 | String updatedAt; 101 | String sSerializer; 102 | 103 | User({this.id, this.type, this.login, this.name, this.description, this.avatarUrl, this.public, this.createdAt, this.updatedAt, this.sSerializer}); 104 | 105 | User.fromJson(Map json) { 106 | id = json['id']; 107 | type = json['type']; 108 | login = json['login']; 109 | name = json['name']; 110 | description = json['description']; 111 | avatarUrl = json['avatar_url']; 112 | public = json['public']; 113 | createdAt = json['created_at']; 114 | updatedAt = json['updated_at']; 115 | sSerializer = json['_serializer']; 116 | } 117 | 118 | Map toJson() { 119 | final Map data = new Map(); 120 | data['id'] = this.id; 121 | data['type'] = this.type; 122 | data['login'] = this.login; 123 | data['name'] = this.name; 124 | data['description'] = this.description; 125 | data['avatar_url'] = this.avatarUrl; 126 | data['public'] = this.public; 127 | data['created_at'] = this.createdAt; 128 | data['updated_at'] = this.updatedAt; 129 | data['_serializer'] = this.sSerializer; 130 | return data; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/presenter/BookPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 5 | import 'package:micro_sparrow/Utils/mvp.dart'; 6 | import 'package:micro_sparrow/View/IBookView.dart'; 7 | import 'package:http/http.dart' as http; 8 | import 'package:micro_sparrow/model/AllDoc_entity.dart'; 9 | import 'package:micro_sparrow/model/Book_entity.dart'; 10 | import 'package:micro_sparrow/model/DocDetailSerializer_entity.dart'; 11 | import 'package:micro_sparrow/model/RealBook_entity.dart'; 12 | import 'package:micro_sparrow/model/TocTree.dart'; 13 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 14 | import 'package:shared_preferences/shared_preferences.dart'; 15 | import 'package:micro_sparrow/Api.dart'; 16 | import 'package:micro_sparrow/model/Token_entity.dart'; 17 | 18 | 19 | class BookPresenter extends AbsPresenter implements IPresenter{ 20 | 21 | IBookView view; 22 | static const platform = const MethodChannel('samples.flutter.io/yami'); 23 | List _list = new List(); 24 | 25 | 26 | @override 27 | init(IView view) { 28 | this.view = view; 29 | } 30 | 31 | void getTopic(String id) async{ 32 | SharedPreferences preferences = await SharedPreferences.getInstance(); 33 | cookie = preferences.getString("cookies"); 34 | if(cookie != null && cookie != ""){ 35 | Map hearders = new Map(); 36 | hearders["cookie"] = cookie; 37 | http.Response response = await http.get(Uri.parse(Api.bookTopic(id)),headers: hearders); 38 | String content = response.body; 39 | if(content.split("decodeURIComponent").length >=1 && content.split("decodeURIComponent")[1].split('"').length >=1){ 40 | String code = response.body.split("decodeURIComponent")[1].split('"')[1]; 41 | final String result = await platform.invokeMethod('URLDecodedString',{'code': code}); 42 | var body = json.decode(result); 43 | bodyToTree(body); 44 | }else{ 45 | view.resultOfBook(false,null,SparrowException.GET_TOPIC_DATA_FAIL); 46 | } 47 | }else{ 48 | view.resultOfBook(false, null,SparrowException.GET_COOKIES_FAIL); 49 | } 50 | 51 | 52 | } 53 | 54 | void bodyToTree(body) { 55 | BookEntity _bookEntity = BookEntity.fromJson(body); 56 | if(_bookEntity.book!=null && _bookEntity.book.toc!=null){ 57 | for(int i = 0;i<_bookEntity.book.toc.length;i++){ 58 | TocTree tree = new TocTree(); 59 | tree.title = _bookEntity.book.toc[i].title; 60 | tree.url = _bookEntity.book.toc[i].url; 61 | tree.childCount = 0; 62 | tree.style = _bookEntity.book.toc[i].type; 63 | if(_bookEntity.book.toc[i].level == 0){ 64 | _list.add(tree); 65 | }else{ 66 | _list[_list.length-1].child.add(tree); 67 | _list[_list.length-1].childCount ++ ; 68 | } 69 | } 70 | view.resultOfBook(true, _list,_bookEntity.search.scope); 71 | }else{ 72 | view.resultOfBook(false, null, "错误"); 73 | } 74 | } 75 | 76 | void getAllDoc(String id) async{ 77 | String url = Api.bookAllDoc(id); 78 | var result = await getCookieHttpData(url); 79 | AlldocEntity entity = AlldocEntity.fromJson(result); 80 | if(entity.data == null || entity.data.length == 0){ 81 | view.resultOfAllDoc(false,null,SparrowException.NULL_DATA); 82 | return; 83 | } 84 | view.resultOfAllDoc(true,entity,SparrowException.OK); 85 | } 86 | 87 | void deleteDoc(int position,String repoId, String bookId) async{ 88 | String url = Api.deleteBook(repoId, bookId); 89 | Map result = await deleteHttpData(url); 90 | if(result.containsKey("status")){ 91 | view.resultOfDelDoc(false,position,SparrowException.DEL_FAIL); 92 | }else{ 93 | view.resultOfDelDoc(true,position,SparrowException.OK); 94 | } 95 | } 96 | 97 | void readExistBook(String slug, String bookId) async{ 98 | String url = Api.getBookInfo(slug, bookId); 99 | var result = await getCookieHttpData(url); 100 | RealbookEntity entity = RealbookEntity.fromJson(result); 101 | if(entity.data == null){ 102 | view.resultOfReadDoc(false,null,SparrowException.NULL_DATA); 103 | return; 104 | } 105 | DocdetailserializerEntity contentEntity= new DocdetailserializerEntity(); 106 | contentEntity.setData(entity.data.title, entity.data.content,entity.data.id); 107 | view.resultOfReadDoc(true,contentEntity,SparrowException.OK); 108 | } 109 | 110 | 111 | 112 | 113 | } -------------------------------------------------------------------------------- /lib/View/AllDocView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | import 'package:micro_sparrow/View/IAllDocView.dart'; 4 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 5 | import 'package:micro_sparrow/model/AllDoc_entity.dart'; 6 | import 'package:micro_sparrow/presenter/AllDocPresenter.dart'; 7 | 8 | class AllDocView extends StatefulWidget{ 9 | @override 10 | State createState() { 11 | return new _AllDocPage (); 12 | } 13 | 14 | } 15 | 16 | class _AllDocPage extends State implements IAllDocView { 17 | 18 | AllDocPresenter _presenter = new AllDocPresenter(); 19 | AlldocEntity _alldocEntity; 20 | bool isLoading = true; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _presenter.init(this); 26 | _presenter.getHttpData(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | _webViewLogin(); 32 | return new CustomScrollView( 33 | slivers: [ 34 | SliverAppBar( 35 | title: new Text("文档"), 36 | floating: false, 37 | pinned: true, 38 | centerTitle: true, 39 | ), 40 | Builder(builder: (BuildContext context){ 41 | return isLoading ? SliverFillRemaining(child: new Center(child: ColorLoader(radius: 15,dotRadius: 6,),),): 42 | SliverList(delegate: SliverChildBuilderDelegate((BuildContext context,int position){ 43 | return getItem(position); 44 | },childCount: getLength())); 45 | }) 46 | ], 47 | ); 48 | } 49 | 50 | @override 51 | void getDocHttpResult(body, bool param1, String s) { 52 | isLoading = false; 53 | if(param1){ 54 | setState(() { 55 | _alldocEntity = AlldocEntity.fromJson(body); 56 | }); 57 | }else{ 58 | print(s); 59 | } 60 | } 61 | 62 | Widget getItem(int position) { 63 | return ListTile( 64 | onTap:(){ 65 | String url ="https://www.yuque.com/" + 66 | _alldocEntity.data[position].book.user.login + "/" 67 | + _alldocEntity.data[position].book.slug + "/" + _alldocEntity.data[position].slug; 68 | String title = _alldocEntity.data[position].title; 69 | _readArticle(url, title); 70 | }, 71 | title: new Text(_alldocEntity.data[position].title), 72 | leading: ifPublish(position), 73 | subtitle: new Text(_alldocEntity.data[position].book.user.name + "/" + _alldocEntity.data[position].book.name), 74 | ); 75 | } 76 | 77 | getLength() { 78 | if(_alldocEntity == null || _alldocEntity.data == null){ 79 | return 0; 80 | }else{ 81 | return _alldocEntity.data.length; 82 | } 83 | } 84 | 85 | ifPublish(int position) { 86 | if(_alldocEntity.data[position].status == 1){ 87 | return Icon(Icons.class_,color: Colors.blue,); 88 | }else{ 89 | return Icon(Icons.class_,); 90 | } 91 | } 92 | 93 | _readArticle(String uri,String title) { 94 | Navigator.push( 95 | context, new MaterialPageRoute(builder: (BuildContext context) { 96 | return WebviewScaffold(url: uri, 97 | withLocalStorage: true, 98 | enableAppScheme: true, 99 | withZoom: false, 100 | withJavascript: true, 101 | allowFileURLs: true, 102 | appBar: new AppBar( 103 | backgroundColor: Colors.white, 104 | elevation: 0, 105 | brightness: Brightness.light, 106 | actions: [ 107 | IconButton(icon: Icon(Icons.more_vert, color: Colors.black45,), 108 | onPressed: () {}, 109 | ) 110 | ], 111 | leading: IconButton( 112 | icon: Icon(Icons.arrow_back, color: Colors.black45,), 113 | onPressed: () { 114 | Navigator.pop(context); 115 | }), 116 | ), 117 | appCacheEnabled: true,); 118 | })); 119 | } 120 | 121 | void _webViewLogin() { 122 | FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(); 123 | flutterWebviewPlugin.hide(); 124 | flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged type) { 125 | if (type.type == WebViewState.finishLoad) { 126 | flutterWebviewPlugin.evalJavascript( 127 | 'document.querySelector("#ReactApp > div > div > div.m-content > div.m-header-back").style.display="none";') 128 | .then((String result) { 129 | flutterWebviewPlugin.show(); 130 | }); 131 | } 132 | }); 133 | } 134 | } -------------------------------------------------------------------------------- /lib/presenter/AbsPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart' as http; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | abstract class AbsPresenter{ 7 | 8 | String cookie = ""; 9 | String token = ""; 10 | 11 | 12 | Future> _getWithTokenHeaders() async{ 13 | if(token == null || token.isEmpty){ 14 | SharedPreferences preferences = await SharedPreferences.getInstance(); 15 | token = preferences.getString("token"); 16 | } 17 | Map hearders = new Map(); 18 | hearders["Content-Type"] = "application/json"; 19 | hearders["User-Agent"] = "sparrow"; 20 | hearders["X-Auth-Token"] = token; 21 | return hearders; 22 | } 23 | 24 | Future> _getWithCookieHeaders() async{ 25 | if(cookie == null || cookie.isEmpty){ 26 | SharedPreferences preferences = await SharedPreferences.getInstance(); 27 | cookie = preferences.getString("cookies"); 28 | } 29 | Map hearders = new Map(); 30 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 31 | hearders["User-Agent"] = "sparrow"; 32 | hearders["cookie"] = cookie; 33 | return hearders; 34 | } 35 | 36 | 37 | /// 38 | /// 携带token的http请求封装 39 | /// 40 | Future> getTokenHttpData(String api) async{ 41 | Map hearders; 42 | hearders = await _getWithTokenHeaders(); 43 | http.Response response = await http.get(Uri.parse(api), 44 | headers: hearders); 45 | var result = json.decode(response.body); 46 | return result; 47 | } 48 | 49 | /// 50 | /// 携带cookie消息的http请求封装 51 | /// 52 | Future> getCookieHttpData(String api) async{ 53 | Map hearders; 54 | hearders = await _getWithCookieHeaders(); 55 | http.Response response = await http.get(Uri.parse(api), 56 | headers: hearders); 57 | var result = json.decode(response.body); 58 | return result; 59 | } 60 | 61 | /// 62 | /// http post方法 63 | /// 64 | Future> postHttpData(String api,String body) async{ 65 | Map hearders; 66 | hearders = await _getWithTokenHeaders(); 67 | http.Response response = await http.post(Uri.parse(api), 68 | body: body, 69 | headers: hearders); 70 | var result = json.decode(response.body); 71 | return result; 72 | } 73 | 74 | /// 75 | /// http put 方法 76 | /// 77 | Future> putHttpData(String api,String body) async{ 78 | Map hearders; 79 | hearders = await _getWithTokenHeaders(); 80 | http.Response response = await http.put(Uri.parse(api), 81 | body: body, 82 | headers: hearders); 83 | var result = json.decode(response.body); 84 | return result; 85 | } 86 | 87 | Future> putHttpCsrfData(String api,String body) async{ 88 | Map hearders; 89 | hearders = await _getWithCookieHeaders(); 90 | hearders['x-csrf-token'] = hearders["cookie"].split("ctoken=")[1].split(";")[0]; 91 | http.Response response = await http.put(Uri.parse(api), 92 | body: body, 93 | headers: hearders); 94 | var result = json.decode(response.body); 95 | return result; 96 | } 97 | 98 | Future> postHttpCsrfData(String api,String body) async{ 99 | Map hearders; 100 | hearders = await _getWithCookieHeaders(); 101 | hearders["Content-Type"] = "application/json"; 102 | hearders['x-csrf-token'] = hearders["cookie"].split("ctoken=")[1].split(";")[0]; 103 | http.Response response = await http.post(Uri.parse(api), 104 | body: body, 105 | headers: hearders); 106 | var result = json.decode(response.body); 107 | return result; 108 | } 109 | 110 | Future> deleteHttpCsrfData(String api,String body) async{ 111 | Map hearders; 112 | hearders = await _getWithCookieHeaders(); 113 | hearders['x-csrf-token'] = hearders["cookie"].split("ctoken=")[1].split(";")[0]; 114 | http.Response response = await http.delete(Uri.parse(api), 115 | headers: hearders); 116 | var result = json.decode(response.body); 117 | return result; 118 | } 119 | 120 | /// 121 | /// http delete 方法 122 | /// 123 | Future> deleteHttpData(String api) async{ 124 | Map hearders; 125 | hearders = await _getWithTokenHeaders(); 126 | http.Response response = await http.delete(Uri.parse(api), 127 | headers: hearders); 128 | var result = json.decode(response.body); 129 | return result; 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /lib/presenter/UserPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'package:micro_sparrow/Utils/Utils.dart'; 2 | import 'package:micro_sparrow/Utils/mvp.dart'; 3 | import 'package:micro_sparrow/View/IUserView.dart'; 4 | import 'package:micro_sparrow/model/BooksAndColumn.dart'; 5 | import 'package:micro_sparrow/model/FollowUserResponse.dart'; 6 | import 'package:micro_sparrow/model/Followed_entity.dart'; 7 | import 'package:micro_sparrow/model/User_entity.dart'; 8 | import 'package:micro_sparrow/model/Vo/FollowVo.dart'; 9 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 10 | import 'package:micro_sparrow/model/Follow_entity.dart'; 11 | import 'dart:convert'; 12 | 13 | class UserPresenter extends AbsPresenter implements IPresenter{ 14 | 15 | IUserView _view; 16 | 17 | @override 18 | init(IView view) { 19 | this._view = view; 20 | } 21 | 22 | void getUserData(String id) async{ 23 | var result = await getTokenHttpData("https://www.yuque.com/api/v2/users/$id"); 24 | UserEntity entity = new UserEntity.fromJson(result); 25 | if(entity == null){ 26 | _view.resultOfUserData(false,null,"获取用户数据错误"); 27 | return; 28 | } 29 | _view.resultOfUserData(true,entity,"OK"); 30 | } 31 | 32 | void getBooksData(String booksId,int offset,int limit) async{ 33 | //官方接口中 https://www.yuque.com/api/groups/105712/books?offset=0&limit=2&q=&type= 34 | //仅当limit = 2的时候才会显示第一条数据 35 | limit = limit + 1; 36 | var result = await getCookieHttpData("https://www.yuque.com/api/groups/$booksId/" 37 | "books?offset="+ offset.toString()+ "&limit="+limit.toString() + "&q=&type="); 38 | BooksAndColumnEntity booksAndColumnEntity = BooksAndColumnEntity.fromJson(result); 39 | if(booksAndColumnEntity == null){ 40 | _view.resultOfBoosAndColumn(false,null,"获取知识库数据失败"); 41 | return; 42 | } 43 | _view.resultOfBoosAndColumn(true,booksAndColumnEntity,"ok"); 44 | } 45 | 46 | void getFollowUser(String userId,String offset) async{ 47 | var result = await getCookieHttpData("https://www.yuque.com/api/actions/targets?action_type=follow&offset=$offset&target_type=User&user_id=$userId"); 48 | FollowEntity followEntity = FollowEntity.fromJson(result); 49 | if(followEntity == null){ 50 | _view.resultOfFollowUser(false,null,"获取关注的用户数据失败"); 51 | return; 52 | } 53 | _view.resultOfFollowUser(true,followEntity,"ok"); 54 | } 55 | 56 | void getFollowersUser(String userId,String offset) async{ 57 | var result = await getCookieHttpData("https://www.yuque.com/api/actions/users?action_type=follow&offset=$offset&target_id=$userId&target_type=User"); 58 | FollowEntity followEntity = FollowEntity.fromJson(result); 59 | if(followEntity == null){ 60 | _view.resultOfFollowersUser(false,null,"获取关注者数据失败"); 61 | return; 62 | } 63 | _view.resultOfFollowersUser(true,followEntity,"ok"); 64 | } 65 | 66 | void isFollowedUsers(FollowEntity entity) async { 67 | //用户的id数组 68 | String idArray = ""; 69 | Map isFollowed = new Map(); 70 | for(int i=0;i isFollowed = new Map(); 88 | isFollowed[int.parse(id)] = false; 89 | var result = await getCookieHttpData("https://www.yuque.com/api/actions/user-owned?action_type=follow&target_ids=$id&target_type=User"); 90 | FollowedEntity followedEntity = FollowedEntity.fromJson(result); 91 | if(followedEntity.data.length == 1){ 92 | isFollowed[int.parse(id)] = true; 93 | } 94 | _view.checkIsFollowed(true,isFollowed,"ok"); 95 | } 96 | 97 | void followUser(num userId) async{ 98 | FollowVo followVo = new FollowVo(actionType: "follow",targetType: "User",targetId: userId); 99 | var result = await postHttpCsrfData("https://www.yuque.com/api/actions",json.encode(followVo)); 100 | FollowUserResponse response = FollowUserResponse.fromJson(result); 101 | if(Utils.checkNull(response)){ 102 | _view.followUser(false,null,"网络错误"); 103 | return; 104 | } 105 | _view.followUser(true,response,"ok"); 106 | } 107 | 108 | void deleteFollowUser(num userId) async{ 109 | FollowVo followVo = new FollowVo(actionType: "follow",targetType: "User",targetId: userId); 110 | await deleteHttpCsrfData("https://www.yuque.com/api/actions?action_type=follow&target_id=$userId&target_type=User", json.encode(followVo)); 111 | _view.deleteFollowUser(true,followVo,"ok"); 112 | } 113 | 114 | 115 | 116 | } -------------------------------------------------------------------------------- /lib/model/BooksAndColumn.dart: -------------------------------------------------------------------------------- 1 | class BooksAndColumnEntity { 2 | List data; 3 | 4 | BooksAndColumnEntity({this.data}); 5 | 6 | BooksAndColumnEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | String type; 25 | String slug; 26 | String name; 27 | num userId; 28 | String description; 29 | num itemsCount; 30 | num likesCount; 31 | num watchesCount; 32 | num creatorId; 33 | num public; 34 | String createdAt; 35 | String updatedAt; 36 | String contentUpdatedAt; 37 | String pinnedAt; 38 | String archivedAt; 39 | User user; 40 | String sSerializer; 41 | 42 | Data({this.id, this.type, this.slug, this.name, this.userId, this.description, this.itemsCount, this.likesCount, this.watchesCount, this.creatorId, this.public, this.createdAt, this.updatedAt, this.contentUpdatedAt, this.pinnedAt, this.archivedAt, this.user, this.sSerializer}); 43 | 44 | Data.fromJson(Map json) { 45 | id = json['id']; 46 | type = json['type']; 47 | slug = json['slug']; 48 | name = json['name']; 49 | userId = json['user_id']; 50 | description = json['description']; 51 | itemsCount = json['items_count']; 52 | likesCount = json['likes_count']; 53 | watchesCount = json['watches_count']; 54 | creatorId = json['creator_id']; 55 | public = json['public']; 56 | createdAt = json['created_at']; 57 | updatedAt = json['updated_at']; 58 | contentUpdatedAt = json['content_updated_at']; 59 | pinnedAt = json['pinned_at']; 60 | archivedAt = json['archived_at']; 61 | user = json['user'] != null ? new User.fromJson(json['user']) : null; 62 | sSerializer = json['_serializer']; 63 | } 64 | 65 | Map toJson() { 66 | final Map data = new Map(); 67 | data['id'] = this.id; 68 | data['type'] = this.type; 69 | data['slug'] = this.slug; 70 | data['name'] = this.name; 71 | data['user_id'] = this.userId; 72 | data['description'] = this.description; 73 | data['items_count'] = this.itemsCount; 74 | data['likes_count'] = this.likesCount; 75 | data['watches_count'] = this.watchesCount; 76 | data['creator_id'] = this.creatorId; 77 | data['public'] = this.public; 78 | data['created_at'] = this.createdAt; 79 | data['updated_at'] = this.updatedAt; 80 | data['content_updated_at'] = this.contentUpdatedAt; 81 | data['pinned_at'] = this.pinnedAt; 82 | data['archived_at'] = this.archivedAt; 83 | if (this.user != null) { 84 | data['user'] = this.user.toJson(); 85 | } 86 | data['_serializer'] = this.sSerializer; 87 | return data; 88 | } 89 | } 90 | 91 | class User { 92 | num id; 93 | String type; 94 | String login; 95 | String name; 96 | String description; 97 | String avatar; 98 | String avatarUrl; 99 | String largeAvatarUrl; 100 | String mediumAvatarUrl; 101 | String smallAvatarUrl; 102 | num followersCount; 103 | num followingCount; 104 | num role; 105 | num status; 106 | String createdAt; 107 | String updatedAt; 108 | String profile; 109 | String sSerializer; 110 | 111 | User({this.id, this.type, this.login, this.name, this.description, this.avatar, this.avatarUrl, this.largeAvatarUrl, this.mediumAvatarUrl, this.smallAvatarUrl, this.followersCount, this.followingCount, this.role, this.status, this.createdAt, this.updatedAt, this.profile, this.sSerializer}); 112 | 113 | User.fromJson(Map json) { 114 | id = json['id']; 115 | type = json['type']; 116 | login = json['login']; 117 | name = json['name']; 118 | description = json['description']; 119 | avatar = json['avatar']; 120 | avatarUrl = json['avatar_url']; 121 | largeAvatarUrl = json['large_avatar_url']; 122 | mediumAvatarUrl = json['medium_avatar_url']; 123 | smallAvatarUrl = json['small_avatar_url']; 124 | followersCount = json['followers_count']; 125 | followingCount = json['following_count']; 126 | role = json['role']; 127 | status = json['status']; 128 | createdAt = json['created_at']; 129 | updatedAt = json['updated_at']; 130 | profile = json['profile']; 131 | sSerializer = json['_serializer']; 132 | } 133 | 134 | Map toJson() { 135 | final Map data = new Map(); 136 | data['id'] = this.id; 137 | data['type'] = this.type; 138 | data['login'] = this.login; 139 | data['name'] = this.name; 140 | data['description'] = this.description; 141 | data['avatar'] = this.avatar; 142 | data['avatar_url'] = this.avatarUrl; 143 | data['large_avatar_url'] = this.largeAvatarUrl; 144 | data['medium_avatar_url'] = this.mediumAvatarUrl; 145 | data['small_avatar_url'] = this.smallAvatarUrl; 146 | data['followers_count'] = this.followersCount; 147 | data['following_count'] = this.followingCount; 148 | data['role'] = this.role; 149 | data['status'] = this.status; 150 | data['created_at'] = this.createdAt; 151 | data['updated_at'] = this.updatedAt; 152 | data['profile'] = this.profile; 153 | data['_serializer'] = this.sSerializer; 154 | return data; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/model/RealBook_entity.dart: -------------------------------------------------------------------------------- 1 | class RealbookEntity { 2 | Data data; 3 | Meta meta; 4 | 5 | RealbookEntity({this.data, this.meta}); 6 | 7 | RealbookEntity.fromJson(Map json) { 8 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 9 | meta = json['meta'] != null ? new Meta.fromJson(json['meta']) : null; 10 | } 11 | 12 | Map toJson() { 13 | final Map data = new Map(); 14 | if (this.data != null) { 15 | data['data'] = this.data.toJson(); 16 | } 17 | if (this.meta != null) { 18 | data['meta'] = this.meta.toJson(); 19 | } 20 | return data; 21 | } 22 | } 23 | 24 | class Data { 25 | num id; 26 | num spaceId; 27 | String type; 28 | String title; 29 | String titleDraft; 30 | String tag; 31 | String slug; 32 | num userId; 33 | num bookId; 34 | String cover; 35 | String description; 36 | String customDescription; 37 | String bodyAsl; 38 | String format; 39 | num status; 40 | num public; 41 | num draftVersion; 42 | num commentsCount; 43 | num likesCount; 44 | String contentUpdatedAt; 45 | String createdAt; 46 | String updatedAt; 47 | String publishedAt; 48 | String firstPublishedAt; 49 | num wordCount; 50 | String content; 51 | dynamic share; 52 | String sSerializer; 53 | 54 | Data({this.id, this.spaceId, this.type, this.title, this.titleDraft, this.tag, this.slug, this.userId, this.bookId, this.cover, this.description, this.customDescription, this.bodyAsl, this.format, this.status, this.public, this.draftVersion, this.commentsCount, this.likesCount, this.contentUpdatedAt, this.createdAt, this.updatedAt, this.publishedAt, this.firstPublishedAt, this.wordCount, this.content, this.share, this.sSerializer}); 55 | 56 | Data.fromJson(Map json) { 57 | id = json['id']; 58 | spaceId = json['space_id']; 59 | type = json['type']; 60 | title = json['title']; 61 | titleDraft = json['title_draft']; 62 | tag = json['tag']; 63 | slug = json['slug']; 64 | userId = json['user_id']; 65 | bookId = json['book_id']; 66 | cover = json['cover']; 67 | description = json['description']; 68 | customDescription = json['custom_description']; 69 | bodyAsl = json['body_asl']; 70 | format = json['format']; 71 | status = json['status']; 72 | public = json['public']; 73 | draftVersion = json['draft_version']; 74 | commentsCount = json['comments_count']; 75 | likesCount = json['likes_count']; 76 | contentUpdatedAt = json['content_updated_at']; 77 | createdAt = json['created_at']; 78 | updatedAt = json['updated_at']; 79 | publishedAt = json['published_at']; 80 | firstPublishedAt = json['first_published_at']; 81 | wordCount = json['word_count']; 82 | content = json['content']; 83 | share = json['share']; 84 | sSerializer = json['_serializer']; 85 | } 86 | 87 | Map toJson() { 88 | final Map data = new Map(); 89 | data['id'] = this.id; 90 | data['space_id'] = this.spaceId; 91 | data['type'] = this.type; 92 | data['title'] = this.title; 93 | data['title_draft'] = this.titleDraft; 94 | data['tag'] = this.tag; 95 | data['slug'] = this.slug; 96 | data['user_id'] = this.userId; 97 | data['book_id'] = this.bookId; 98 | data['cover'] = this.cover; 99 | data['description'] = this.description; 100 | data['custom_description'] = this.customDescription; 101 | data['body_asl'] = this.bodyAsl; 102 | data['format'] = this.format; 103 | data['status'] = this.status; 104 | data['public'] = this.public; 105 | data['draft_version'] = this.draftVersion; 106 | data['comments_count'] = this.commentsCount; 107 | data['likes_count'] = this.likesCount; 108 | data['content_updated_at'] = this.contentUpdatedAt; 109 | data['created_at'] = this.createdAt; 110 | data['updated_at'] = this.updatedAt; 111 | data['published_at'] = this.publishedAt; 112 | data['first_published_at'] = this.firstPublishedAt; 113 | data['word_count'] = this.wordCount; 114 | data['content'] = this.content; 115 | data['share'] = this.share; 116 | data['_serializer'] = this.sSerializer; 117 | return data; 118 | } 119 | } 120 | 121 | class Meta { 122 | bool marked; 123 | Abilities abilities; 124 | 125 | Meta({this.marked, this.abilities}); 126 | 127 | Meta.fromJson(Map json) { 128 | marked = json['marked']; 129 | abilities = json['abilities'] != null ? new Abilities.fromJson(json['abilities']) : null; 130 | } 131 | 132 | Map toJson() { 133 | final Map data = new Map(); 134 | data['marked'] = this.marked; 135 | if (this.abilities != null) { 136 | data['abilities'] = this.abilities.toJson(); 137 | } 138 | return data; 139 | } 140 | } 141 | 142 | class Abilities { 143 | bool read; 144 | bool update; 145 | bool destroy; 146 | bool export; 147 | 148 | Abilities({this.read, this.update, this.destroy, this.export}); 149 | 150 | Abilities.fromJson(Map json) { 151 | read = json['read']; 152 | update = json['update']; 153 | destroy = json['destroy']; 154 | export = json['export']; 155 | } 156 | 157 | Map toJson() { 158 | final Map data = new Map(); 159 | data['read'] = this.read; 160 | data['update'] = this.update; 161 | data['destroy'] = this.destroy; 162 | data['export'] = this.export; 163 | return data; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/View/EditView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_markdown/flutter_markdown.dart'; 3 | import 'package:micro_sparrow/View/IEditView.dart'; 4 | import 'package:micro_sparrow/model/CreateDoc_entity.dart'; 5 | import 'package:micro_sparrow/model/DocDetailSerializer_entity.dart'; 6 | import 'package:micro_sparrow/presenter/EditPresenter.dart'; 7 | 8 | const String _markdownData = """# Markdown Example 9 | Markdown allows you to easily include formatted text, images, and even formatted Dart code in your app. 10 | 11 | ## Styling 12 | Style text as _italic_, __bold__, or `inline code`. 13 | 14 | - Use bulleted lists 15 | - To better clarify 16 | - Your points 17 | 18 | ## Links 19 | You can use [hyperlinks](hyperlink) in markdown 20 | 21 | ## Images 22 | 23 | You can include images: 24 | 25 | ![Flutter logo](https://flutter.io/images/flutter-mark-square-100.png#100x100) 26 | 27 | ## Markdown widget 28 | 29 | This is an example of how to create your own Markdown widget: 30 | 31 | new Markdown(data: 'Hello _world_!'); 32 | 33 | ## Code blocks 34 | Formatted Dart code looks really pretty too: 35 | 36 | ``` 37 | void main() { 38 | runApp(new MaterialApp( 39 | home: new Scaffold( 40 | body: new Markdown(data: markdownData) 41 | ) 42 | )); 43 | } 44 | ``` 45 | 46 | Enjoy! 47 | """; 48 | 49 | class EditView extends StatefulWidget{ 50 | EditView({Key key, this.id,this.entity}) : super(key: key); 51 | final String id; 52 | final DocdetailserializerEntity entity; 53 | 54 | 55 | @override 56 | State createState() { 57 | return new _EditPage(); 58 | } 59 | 60 | } 61 | 62 | class _EditPage extends State implements IEditView { 63 | 64 | int content = 0; 65 | TextEditingController _textEditingController = new TextEditingController(); 66 | TextEditingController _titleEditingController = new TextEditingController(); 67 | FocusNode _focusNode = new FocusNode(); 68 | FocusNode _titlefocusNode = new FocusNode(); 69 | EditPresenter _presenter = new EditPresenter(); 70 | bool isSave = false; 71 | var _scaffoldkey = new GlobalKey(); 72 | 73 | @override 74 | void initState() { 75 | super.initState(); 76 | _presenter.init(this); 77 | _initData(); 78 | } 79 | 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return DefaultTabController(length: 2, 84 | child:Scaffold( 85 | key: _scaffoldkey, 86 | appBar: new AppBar( 87 | backgroundColor: Colors.white, 88 | elevation: 0, 89 | brightness: Brightness.light, 90 | iconTheme: IconThemeData(color: Colors.black87), 91 | actions: [ 92 | IconButton(icon: Icon(Icons.save), onPressed: (){ 93 | CreatedocEntity doc = new CreatedocEntity(); 94 | doc.title = _titleEditingController.text; 95 | doc.body = _textEditingController.text.replaceAll("\n", "\\n"); 96 | doc.public = 0; 97 | _presenter.createDoc(widget.id,doc); 98 | }), 99 | IconButton(icon: Icon(Icons.more_vert), onPressed: (){}) 100 | ], 101 | ), 102 | body: TabBarView( 103 | children: [ 104 | SingleChildScrollView( 105 | child: new Padding(padding: EdgeInsets.all(16), 106 | child: new Column( 107 | children: [ 108 | new TextField( 109 | controller: _titleEditingController, 110 | focusNode: _titlefocusNode, 111 | cursorColor: Colors.green, 112 | decoration: InputDecoration( 113 | border: InputBorder.none, 114 | hintText: '标题', 115 | hintStyle: TextStyle(fontSize: 18) 116 | ), 117 | style: TextStyle(color: Colors.black,fontWeight: FontWeight.bold,fontSize: 18), 118 | ), 119 | new Divider(), 120 | new TextField( 121 | maxLines: null, 122 | keyboardType: TextInputType.multiline, 123 | controller: _textEditingController, 124 | focusNode: _focusNode, 125 | autofocus: true, 126 | style: new TextStyle( 127 | fontSize: 16,color: Colors.black,wordSpacing: 5,height: 1.5,), 128 | decoration: new InputDecoration.collapsed( 129 | hintText: '' 130 | ), 131 | cursorColor: Colors.green), 132 | ], 133 | )) 134 | ), 135 | Markdown(data: "# " + _titleEditingController.text + "\n" + _textEditingController.text,) 136 | ] 137 | ) 138 | ) 139 | ); 140 | } 141 | 142 | @override 143 | void reslutOfPost(bool param0, String s) { 144 | _scaffoldkey.currentState.showSnackBar(SnackBar(content: Text("保存数据成功"),backgroundColor: Colors.green,)); 145 | } 146 | 147 | void _initData() { 148 | if(widget.entity != null){ 149 | _presenter.setEntity(widget.entity); 150 | _titleEditingController.text = widget.entity.data.title; 151 | _textEditingController.text = widget.entity.data.body; 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /lib/model/Book_entity.dart: -------------------------------------------------------------------------------- 1 | class BookEntity { 2 | Book book; 3 | Search search; 4 | 5 | BookEntity({this.book,this.search}); 6 | 7 | BookEntity.fromJson(Map json) { 8 | book = json['book'] != null ? new Book.fromJson(json['book']) : null; 9 | search = json['search'] != null ? new Search.fromJson(json['search']) : null; 10 | } 11 | 12 | Map toJson() { 13 | final Map data = new Map(); 14 | if (this.book != null) { 15 | data['book'] = this.book.toJson(); 16 | } 17 | if(this.search !=null){ 18 | data['search'] = this.search.toJson(); 19 | } 20 | return data; 21 | } 22 | } 23 | 24 | class Book { 25 | String contentUpdatedAt; 26 | bool enableComment; 27 | bool enableExport; 28 | bool enableVisitorWatermark; 29 | String copyrightWatermark; 30 | bool enableSearchEngine; 31 | bool enableWebhook; 32 | bool enableTrash; 33 | num id; 34 | num spaceId; 35 | String type; 36 | String slug; 37 | String name; 38 | String description; 39 | List toc; 40 | String tocYml; 41 | String body; 42 | num userId; 43 | num creatorId; 44 | num public; 45 | num excellent; 46 | num menuType; 47 | num itemsCount; 48 | num likesCount; 49 | num watchesCount; 50 | num contentUpdatedAtMs; 51 | String deletedSlug; 52 | String createdAt; 53 | String updatedAt; 54 | String pinnedAt; 55 | String archivedAt; 56 | String deletedAt; 57 | String joinToken; 58 | num tocVersion; 59 | 60 | Book({this.contentUpdatedAt, this.enableComment, this.enableExport, this.enableVisitorWatermark, this.copyrightWatermark, this.enableSearchEngine, this.enableWebhook, this.enableTrash, this.id, this.spaceId, this.type, this.slug, this.name, this.description, this.toc, this.tocYml, this.body, this.userId, this.creatorId, this.public, this.excellent, this.menuType, this.itemsCount, this.likesCount, this.watchesCount, this.contentUpdatedAtMs, this.deletedSlug, this.createdAt, this.updatedAt, this.pinnedAt, this.archivedAt, this.deletedAt, this.joinToken, this.tocVersion}); 61 | 62 | Book.fromJson(Map json) { 63 | contentUpdatedAt = json['content_updated_at']; 64 | enableComment = json['enable_comment']; 65 | enableExport = json['enable_export']; 66 | enableVisitorWatermark = json['enable_visitor_watermark']; 67 | copyrightWatermark = json['copyright_watermark']; 68 | enableSearchEngine = json['enable_search_engine']; 69 | enableWebhook = json['enable_webhook']; 70 | enableTrash = json['enable_trash']; 71 | id = json['id']; 72 | spaceId = json['space_id']; 73 | type = json['type']; 74 | slug = json['slug']; 75 | name = json['name']; 76 | description = json['description']; 77 | if (json['toc'] != null) { 78 | toc = new List(); 79 | json['toc'].forEach((v) { toc.add(new Toc.fromJson(v)); }); 80 | } 81 | tocYml = json['toc_yml']; 82 | body = json['body']; 83 | userId = json['user_id']; 84 | creatorId = json['creator_id']; 85 | public = json['public']; 86 | excellent = json['excellent']; 87 | menuType = json['menu_type']; 88 | itemsCount = json['items_count']; 89 | likesCount = json['likes_count']; 90 | watchesCount = json['watches_count']; 91 | contentUpdatedAtMs = json['content_updated_at_ms']; 92 | deletedSlug = json['deleted_slug']; 93 | createdAt = json['created_at']; 94 | updatedAt = json['updated_at']; 95 | pinnedAt = json['pinned_at']; 96 | archivedAt = json['archived_at']; 97 | deletedAt = json['deleted_at']; 98 | joinToken = json['join_token']; 99 | tocVersion = json['toc_version']; 100 | } 101 | 102 | Map toJson() { 103 | final Map data = new Map(); 104 | data['content_updated_at'] = this.contentUpdatedAt; 105 | data['enable_comment'] = this.enableComment; 106 | data['enable_export'] = this.enableExport; 107 | data['enable_visitor_watermark'] = this.enableVisitorWatermark; 108 | data['copyright_watermark'] = this.copyrightWatermark; 109 | data['enable_search_engine'] = this.enableSearchEngine; 110 | data['enable_webhook'] = this.enableWebhook; 111 | data['enable_trash'] = this.enableTrash; 112 | data['id'] = this.id; 113 | data['space_id'] = this.spaceId; 114 | data['type'] = this.type; 115 | data['slug'] = this.slug; 116 | data['name'] = this.name; 117 | data['description'] = this.description; 118 | if (this.toc != null) { 119 | data['toc'] = this.toc.map((v) => v.toJson()).toList(); 120 | } 121 | data['toc_yml'] = this.tocYml; 122 | data['body'] = this.body; 123 | data['user_id'] = this.userId; 124 | data['creator_id'] = this.creatorId; 125 | data['public'] = this.public; 126 | data['excellent'] = this.excellent; 127 | data['menu_type'] = this.menuType; 128 | data['items_count'] = this.itemsCount; 129 | data['likes_count'] = this.likesCount; 130 | data['watches_count'] = this.watchesCount; 131 | data['content_updated_at_ms'] = this.contentUpdatedAtMs; 132 | data['deleted_slug'] = this.deletedSlug; 133 | data['created_at'] = this.createdAt; 134 | data['updated_at'] = this.updatedAt; 135 | data['pinned_at'] = this.pinnedAt; 136 | data['archived_at'] = this.archivedAt; 137 | data['deleted_at'] = this.deletedAt; 138 | data['join_token'] = this.joinToken; 139 | data['toc_version'] = this.tocVersion; 140 | return data; 141 | } 142 | } 143 | 144 | class Toc { 145 | String type; 146 | String title; 147 | num level; 148 | String url; 149 | 150 | Toc({this.type, this.title, this.level,this.url}); 151 | 152 | Toc.fromJson(Map json) { 153 | type = json['type']; 154 | title = json['title']; 155 | level = json['level']; 156 | url = json['url']; 157 | } 158 | 159 | Map toJson() { 160 | final Map data = new Map(); 161 | data['type'] = this.type; 162 | data['title'] = this.title; 163 | data['level'] = this.level; 164 | return data; 165 | } 166 | } 167 | 168 | class Search{ 169 | String scope; 170 | 171 | Search({this.scope}); 172 | 173 | 174 | Search.fromJson(Map json){ 175 | scope = json['scope']; 176 | } 177 | 178 | Map toJson() { 179 | final Map data = new Map(); 180 | data['scope'] = this.scope; 181 | return data; 182 | } 183 | 184 | 185 | } 186 | -------------------------------------------------------------------------------- /lib/Widget/ColorLoader.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/material.dart"; 2 | import 'dart:math'; 3 | 4 | class ColorLoader extends StatefulWidget { 5 | final double radius; 6 | final double dotRadius; 7 | 8 | ColorLoader({this.radius = 30.0, this.dotRadius = 3.0}); 9 | 10 | @override 11 | _ColorLoaderState createState() => _ColorLoaderState(); 12 | } 13 | 14 | class _ColorLoaderState extends State 15 | with SingleTickerProviderStateMixin { 16 | Animation animation_rotation; 17 | Animation animation_radius_in; 18 | Animation animation_radius_out; 19 | AnimationController controller; 20 | 21 | double radius; 22 | double dotRadius; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | 28 | radius = widget.radius; 29 | dotRadius = widget.dotRadius; 30 | 31 | 32 | controller = AnimationController( 33 | lowerBound: 0.0, 34 | upperBound: 1.0, 35 | duration: const Duration(milliseconds: 3000), 36 | vsync: this); 37 | 38 | animation_rotation = Tween(begin: 0.0, end: 1.0).animate( 39 | CurvedAnimation( 40 | parent: controller, 41 | curve: Interval(0.0, 1.0, curve: Curves.linear), 42 | ), 43 | ); 44 | 45 | animation_radius_in = Tween(begin: 1.0, end: 0.0).animate( 46 | CurvedAnimation( 47 | parent: controller, 48 | curve: Interval(0.75, 1.0, curve: Curves.elasticIn), 49 | ), 50 | ); 51 | 52 | animation_radius_out = Tween(begin: 0.0, end: 1.0).animate( 53 | CurvedAnimation( 54 | parent: controller, 55 | curve: Interval(0.0, 0.25, curve: Curves.elasticOut), 56 | ), 57 | ); 58 | 59 | controller.addListener(() { 60 | setState(() { 61 | if (controller.value >= 0.75 && controller.value <= 1.0) 62 | radius = widget.radius * animation_radius_in.value; 63 | else if (controller.value >= 0.0 && controller.value <= 0.25) 64 | radius = widget.radius * animation_radius_out.value; 65 | }); 66 | }); 67 | 68 | controller.addStatusListener((status) { 69 | if (status == AnimationStatus.completed) {} 70 | }); 71 | 72 | controller.repeat(); 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Container( 78 | width: 100.0, 79 | height: 100.0, 80 | //color: Colors.black12, 81 | child: new Center( 82 | child: new RotationTransition( 83 | 84 | turns: animation_rotation, 85 | child: new Container( 86 | //color: Colors.limeAccent, 87 | child: new Center( 88 | child: Stack( 89 | children: [ 90 | new Transform.translate( 91 | offset: Offset(0.0, 0.0), 92 | child: Dot( 93 | radius: radius, 94 | color: Colors.black12, 95 | ), 96 | ), 97 | new Transform.translate( 98 | child: Dot( 99 | radius: dotRadius, 100 | color: Colors.amber, 101 | ), 102 | offset: Offset( 103 | radius * cos(0.0), 104 | radius * sin(0.0), 105 | ), 106 | ), 107 | new Transform.translate( 108 | child: Dot( 109 | radius: dotRadius, 110 | color: Colors.deepOrangeAccent, 111 | ), 112 | offset: Offset( 113 | radius * cos(0.0 + 1 * pi / 4), 114 | radius * sin(0.0 + 1 * pi / 4), 115 | ), 116 | ), 117 | new Transform.translate( 118 | child: Dot( 119 | radius: dotRadius, 120 | color: Colors.pinkAccent, 121 | ), 122 | offset: Offset( 123 | radius * cos(0.0 + 2 * pi / 4), 124 | radius * sin(0.0 + 2 * pi / 4), 125 | ), 126 | ), 127 | new Transform.translate( 128 | child: Dot( 129 | radius: dotRadius, 130 | color: Colors.purple, 131 | ), 132 | offset: Offset( 133 | radius * cos(0.0 + 3 * pi / 4), 134 | radius * sin(0.0 + 3 * pi / 4), 135 | ), 136 | ), 137 | new Transform.translate( 138 | child: Dot( 139 | radius: dotRadius, 140 | color: Colors.yellow, 141 | ), 142 | offset: Offset( 143 | radius * cos(0.0 + 4 * pi / 4), 144 | radius * sin(0.0 + 4 * pi / 4), 145 | ), 146 | ), 147 | new Transform.translate( 148 | child: Dot( 149 | radius: dotRadius, 150 | color: Colors.lightGreen, 151 | ), 152 | offset: Offset( 153 | radius * cos(0.0 + 5 * pi / 4), 154 | radius * sin(0.0 + 5 * pi / 4), 155 | ), 156 | ), 157 | new Transform.translate( 158 | child: Dot( 159 | radius: dotRadius, 160 | color: Colors.orangeAccent, 161 | ), 162 | offset: Offset( 163 | radius * cos(0.0 + 6 * pi / 4), 164 | radius * sin(0.0 + 6 * pi / 4), 165 | ), 166 | ), 167 | new Transform.translate( 168 | child: Dot( 169 | radius: dotRadius, 170 | color: Colors.blueAccent, 171 | ), 172 | offset: Offset( 173 | radius * cos(0.0 + 7 * pi / 4), 174 | radius * sin(0.0 + 7 * pi / 4), 175 | ), 176 | ), 177 | ], 178 | ), 179 | ), 180 | ), 181 | ), 182 | ), 183 | ); 184 | } 185 | 186 | @override 187 | void dispose() { 188 | 189 | controller.dispose(); 190 | super.dispose(); 191 | } 192 | } 193 | 194 | class Dot extends StatelessWidget { 195 | final double radius; 196 | final Color color; 197 | 198 | Dot({this.radius, this.color}); 199 | 200 | @override 201 | Widget build(BuildContext context) { 202 | return new Center( 203 | child: Container( 204 | width: radius, 205 | height: radius, 206 | decoration: BoxDecoration(color: color, shape: BoxShape.circle), 207 | 208 | ), 209 | ); 210 | } 211 | } -------------------------------------------------------------------------------- /lib/model/ThirdData_entity.dart: -------------------------------------------------------------------------------- 1 | class ThirddataEntity { 2 | List data; 3 | 4 | ThirddataEntity({this.data}); 5 | 6 | ThirddataEntity.fromJson(Map json) { 7 | if (json['data'] != null) { 8 | data = new List(); 9 | json['data'].forEach((v) { data.add(new Data.fromJson(v)); }); 10 | } 11 | } 12 | 13 | Map toJson() { 14 | final Map data = new Map(); 15 | if (this.data != null) { 16 | data['data'] = this.data.map((v) => v.toJson()).toList(); 17 | } 18 | return data; 19 | } 20 | } 21 | 22 | class Data { 23 | num id; 24 | num spaceId; 25 | String targetType; 26 | num targetId; 27 | String title; 28 | String description; 29 | String cover; 30 | String week; 31 | String createdAt; 32 | String updatedAt; 33 | Target target; 34 | String sSerializer; 35 | 36 | Data({this.id, this.spaceId, this.targetType, this.targetId, this.title, this.description, this.cover, this.week, this.createdAt, this.updatedAt, this.target, this.sSerializer}); 37 | 38 | Data.fromJson(Map json) { 39 | id = json['id']; 40 | spaceId = json['space_id']; 41 | targetType = json['target_type']; 42 | targetId = json['target_id']; 43 | title = json['title']; 44 | description = json['description']; 45 | cover = json['cover']; 46 | week = json['week']; 47 | createdAt = json['created_at']; 48 | updatedAt = json['updated_at']; 49 | target = json['target'] != null ? new Target.fromJson(json['target']) : null; 50 | sSerializer = json['_serializer']; 51 | } 52 | 53 | Map toJson() { 54 | final Map data = new Map(); 55 | data['id'] = this.id; 56 | data['space_id'] = this.spaceId; 57 | data['target_type'] = this.targetType; 58 | data['target_id'] = this.targetId; 59 | data['title'] = this.title; 60 | data['description'] = this.description; 61 | data['cover'] = this.cover; 62 | data['week'] = this.week; 63 | data['created_at'] = this.createdAt; 64 | data['updated_at'] = this.updatedAt; 65 | if (this.target != null) { 66 | data['target'] = this.target.toJson(); 67 | } 68 | data['_serializer'] = this.sSerializer; 69 | return data; 70 | } 71 | } 72 | 73 | class Target { 74 | num id; 75 | String title; 76 | String slug; 77 | num userId; 78 | num bookId; 79 | num likesCount; 80 | String createdAt; 81 | String updatedAt; 82 | Book book; 83 | User user; 84 | String lastEditor; 85 | dynamic share; 86 | String sSerializer; 87 | 88 | Target({this.id, this.title, this.slug, this.userId, this.bookId, this.likesCount, this.createdAt, this.updatedAt, this.book, this.user, this.lastEditor, this.share, this.sSerializer}); 89 | 90 | Target.fromJson(Map json) { 91 | id = json['id']; 92 | title = json['title']; 93 | slug = json['slug']; 94 | userId = json['user_id']; 95 | bookId = json['book_id']; 96 | likesCount = json['likes_count']; 97 | createdAt = json['created_at']; 98 | updatedAt = json['updated_at']; 99 | book = json['book'] != null ? new Book.fromJson(json['book']) : null; 100 | user = json['user'] != null ? new User.fromJson(json['user']) : null; 101 | lastEditor = json['last_editor']; 102 | share = json['share']; 103 | sSerializer = json['_serializer']; 104 | } 105 | 106 | Map toJson() { 107 | final Map data = new Map(); 108 | data['id'] = this.id; 109 | data['title'] = this.title; 110 | data['slug'] = this.slug; 111 | data['user_id'] = this.userId; 112 | data['book_id'] = this.bookId; 113 | data['likes_count'] = this.likesCount; 114 | data['created_at'] = this.createdAt; 115 | data['updated_at'] = this.updatedAt; 116 | if (this.book != null) { 117 | data['book'] = this.book.toJson(); 118 | } 119 | if (this.user != null) { 120 | data['user'] = this.user.toJson(); 121 | } 122 | data['last_editor'] = this.lastEditor; 123 | data['share'] = this.share; 124 | data['_serializer'] = this.sSerializer; 125 | return data; 126 | } 127 | } 128 | 129 | class Book { 130 | num id; 131 | String type; 132 | String slug; 133 | String name; 134 | num userId; 135 | String description; 136 | num public; 137 | String createdAt; 138 | String updatedAt; 139 | String contentUpdatedAt; 140 | User user; 141 | String sSerializer; 142 | 143 | Book({this.id, this.type, this.slug, this.name, this.userId, this.description, this.public, this.createdAt, this.updatedAt, this.contentUpdatedAt, this.user, this.sSerializer}); 144 | 145 | Book.fromJson(Map json) { 146 | id = json['id']; 147 | type = json['type']; 148 | slug = json['slug']; 149 | name = json['name']; 150 | userId = json['user_id']; 151 | description = json['description']; 152 | public = json['public']; 153 | createdAt = json['created_at']; 154 | updatedAt = json['updated_at']; 155 | contentUpdatedAt = json['content_updated_at']; 156 | user = json['user'] != null ? new User.fromJson(json['user']) : null; 157 | sSerializer = json['_serializer']; 158 | } 159 | 160 | Map toJson() { 161 | final Map data = new Map(); 162 | data['id'] = this.id; 163 | data['type'] = this.type; 164 | data['slug'] = this.slug; 165 | data['name'] = this.name; 166 | data['user_id'] = this.userId; 167 | data['description'] = this.description; 168 | data['public'] = this.public; 169 | data['created_at'] = this.createdAt; 170 | data['updated_at'] = this.updatedAt; 171 | data['content_updated_at'] = this.contentUpdatedAt; 172 | if (this.user != null) { 173 | data['user'] = this.user.toJson(); 174 | } 175 | data['_serializer'] = this.sSerializer; 176 | return data; 177 | } 178 | } 179 | 180 | class User { 181 | num id; 182 | String type; 183 | String login; 184 | String name; 185 | String description; 186 | String avatarUrl; 187 | num public; 188 | String createdAt; 189 | String updatedAt; 190 | String sSerializer; 191 | 192 | User({this.id, this.type, this.login, this.name, this.description, this.avatarUrl, this.public, this.createdAt, this.updatedAt, this.sSerializer}); 193 | 194 | User.fromJson(Map json) { 195 | id = json['id']; 196 | type = json['type']; 197 | login = json['login']; 198 | name = json['name']; 199 | description = json['description']; 200 | avatarUrl = json['avatar_url']; 201 | public = json['public']; 202 | createdAt = json['created_at']; 203 | updatedAt = json['updated_at']; 204 | sSerializer = json['_serializer']; 205 | } 206 | 207 | Map toJson() { 208 | final Map data = new Map(); 209 | data['id'] = this.id; 210 | data['type'] = this.type; 211 | data['login'] = this.login; 212 | data['name'] = this.name; 213 | data['description'] = this.description; 214 | data['avatar_url'] = this.avatarUrl; 215 | data['public'] = this.public; 216 | data['created_at'] = this.createdAt; 217 | data['updated_at'] = this.updatedAt; 218 | data['_serializer'] = this.sSerializer; 219 | return data; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /lib/model/UserSearch_entity.dart: -------------------------------------------------------------------------------- 1 | class UsersearchEntity { 2 | Data data; 3 | 4 | UsersearchEntity({this.data}); 5 | 6 | UsersearchEntity.fromJson(Map json) { 7 | data = json['data'] != null ? new Data.fromJson(json['data']) : null; 8 | } 9 | 10 | Map toJson() { 11 | final Map data = new Map(); 12 | if (this.data != null) { 13 | data['data'] = this.data.toJson(); 14 | } 15 | return data; 16 | } 17 | } 18 | 19 | class Data { 20 | String type; 21 | List hits; 22 | num totalhits; 23 | num numhits; 24 | 25 | Data({this.type, this.hits, this.totalhits, this.numhits}); 26 | 27 | Data.fromJson(Map json) { 28 | type = json['type']; 29 | if (json['hits'] != null) { 30 | hits = new List(); 31 | json['hits'].forEach((v) { hits.add(new Hits.fromJson(v)); }); 32 | } 33 | totalhits = json['totalHits']; 34 | numhits = json['numHits']; 35 | } 36 | 37 | Map toJson() { 38 | final Map data = new Map(); 39 | data['type'] = this.type; 40 | if (this.hits != null) { 41 | data['hits'] = this.hits.map((v) => v.toJson()).toList(); 42 | } 43 | data['totalHits'] = this.totalhits; 44 | data['numHits'] = this.numhits; 45 | return data; 46 | } 47 | } 48 | 49 | class Hits { 50 | String id; 51 | String type; 52 | String abstract; 53 | String bookName; 54 | String groupName; 55 | String name; 56 | String login; 57 | String description; 58 | String avatarUrl; 59 | String url; 60 | Record rRecord; 61 | 62 | Hits({this.id, this.type, this.abstract, this.bookName, this.groupName, this.name, this.login, this.description, this.avatarUrl, this.url, this.rRecord}); 63 | 64 | Hits.fromJson(Map json) { 65 | id = json['id']; 66 | type = json['type']; 67 | abstract = json['abstract']; 68 | bookName = json['book_name']; 69 | groupName = json['group_name']; 70 | name = json['name']; 71 | login = json['login']; 72 | description = json['description']; 73 | avatarUrl = json['avatar_url']; 74 | url = json['url']; 75 | rRecord = json['_record'] != null ? new Record.fromJson(json['_record']) : null; 76 | } 77 | 78 | Map toJson() { 79 | final Map data = new Map(); 80 | data['id'] = this.id; 81 | data['type'] = this.type; 82 | data['abstract'] = this.abstract; 83 | data['book_name'] = this.bookName; 84 | data['group_name'] = this.groupName; 85 | data['name'] = this.name; 86 | data['login'] = this.login; 87 | data['description'] = this.description; 88 | data['avatar_url'] = this.avatarUrl; 89 | data['url'] = this.url; 90 | if (this.rRecord != null) { 91 | data['_record'] = this.rRecord.toJson(); 92 | } 93 | return data; 94 | } 95 | } 96 | 97 | class Record { 98 | String workId; 99 | String avatarUrl; 100 | String smallAvatarUrl; 101 | String mediumAvatarUrl; 102 | String largeAvatarUrl; 103 | bool isactive; 104 | bool isdeactived; 105 | bool isextcontact; 106 | num id; 107 | num spaceId; 108 | String type; 109 | String login; 110 | String name; 111 | String description; 112 | String avatar; 113 | String ownerId; 114 | num topicsCount; 115 | num publicTopicsCount; 116 | num membersCount; 117 | num booksCount; 118 | num publicBooksCount; 119 | num followersCount; 120 | num followingCount; 121 | num accountId; 122 | num role; 123 | num status; 124 | num public; 125 | bool wantsEmail; 126 | bool wantsMarketingEmail; 127 | num topicUpdatedAtMs; 128 | String deletedSlug; 129 | String language; 130 | String createdAt; 131 | String updatedAt; 132 | String deletedAt; 133 | 134 | Record({this.workId, this.avatarUrl, this.smallAvatarUrl, this.mediumAvatarUrl, this.largeAvatarUrl, this.isactive, this.isdeactived, this.isextcontact, this.id, this.spaceId, this.type, this.login, this.name, this.description, this.avatar, this.ownerId, this.topicsCount, this.publicTopicsCount, this.membersCount, this.booksCount, this.publicBooksCount, this.followersCount, this.followingCount, this.accountId, this.role, this.status, this.public, this.wantsEmail, this.wantsMarketingEmail, this.topicUpdatedAtMs, this.deletedSlug, this.language, this.createdAt, this.updatedAt, this.deletedAt}); 135 | 136 | Record.fromJson(Map json) { 137 | workId = json['work_id']; 138 | avatarUrl = json['avatar_url']; 139 | smallAvatarUrl = json['small_avatar_url']; 140 | mediumAvatarUrl = json['medium_avatar_url']; 141 | largeAvatarUrl = json['large_avatar_url']; 142 | isactive = json['isActive']; 143 | isdeactived = json['isDeactived']; 144 | isextcontact = json['isExtcontact']; 145 | id = json['id']; 146 | spaceId = json['space_id']; 147 | type = json['type']; 148 | login = json['login']; 149 | name = json['name']; 150 | description = json['description']; 151 | avatar = json['avatar']; 152 | ownerId = json['owner_id']; 153 | topicsCount = json['topics_count']; 154 | publicTopicsCount = json['public_topics_count']; 155 | membersCount = json['members_count']; 156 | booksCount = json['books_count']; 157 | publicBooksCount = json['public_books_count']; 158 | followersCount = json['followers_count']; 159 | followingCount = json['following_count']; 160 | accountId = json['account_id']; 161 | role = json['role']; 162 | status = json['status']; 163 | public = json['public']; 164 | wantsEmail = json['wants_email']; 165 | wantsMarketingEmail = json['wants_marketing_email']; 166 | topicUpdatedAtMs = json['topic_updated_at_ms']; 167 | deletedSlug = json['deleted_slug']; 168 | language = json['language']; 169 | createdAt = json['created_at']; 170 | updatedAt = json['updated_at']; 171 | deletedAt = json['deleted_at']; 172 | } 173 | 174 | Map toJson() { 175 | final Map data = new Map(); 176 | data['work_id'] = this.workId; 177 | data['avatar_url'] = this.avatarUrl; 178 | data['small_avatar_url'] = this.smallAvatarUrl; 179 | data['medium_avatar_url'] = this.mediumAvatarUrl; 180 | data['large_avatar_url'] = this.largeAvatarUrl; 181 | data['isActive'] = this.isactive; 182 | data['isDeactived'] = this.isdeactived; 183 | data['isExtcontact'] = this.isextcontact; 184 | data['id'] = this.id; 185 | data['space_id'] = this.spaceId; 186 | data['type'] = this.type; 187 | data['login'] = this.login; 188 | data['name'] = this.name; 189 | data['description'] = this.description; 190 | data['avatar'] = this.avatar; 191 | data['owner_id'] = this.ownerId; 192 | data['topics_count'] = this.topicsCount; 193 | data['public_topics_count'] = this.publicTopicsCount; 194 | data['members_count'] = this.membersCount; 195 | data['books_count'] = this.booksCount; 196 | data['public_books_count'] = this.publicBooksCount; 197 | data['followers_count'] = this.followersCount; 198 | data['following_count'] = this.followingCount; 199 | data['account_id'] = this.accountId; 200 | data['role'] = this.role; 201 | data['status'] = this.status; 202 | data['public'] = this.public; 203 | data['wants_email'] = this.wantsEmail; 204 | data['wants_marketing_email'] = this.wantsMarketingEmail; 205 | data['topic_updated_at_ms'] = this.topicUpdatedAtMs; 206 | data['deleted_slug'] = this.deletedSlug; 207 | data['language'] = this.language; 208 | data['created_at'] = this.createdAt; 209 | data['updated_at'] = this.updatedAt; 210 | data['deleted_at'] = this.deletedAt; 211 | return data; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lib/Widget/MyExpansionTitle.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | 8 | const Duration _kExpand = Duration(milliseconds: 200); 9 | 10 | /// A single-line [ListTile] with a trailing button that expands or collapses 11 | /// the tile to reveal or hide the [children]. 12 | /// 13 | /// This widget is typically used with [ListView] to create an 14 | /// "expand / collapse" list entry. When used with scrolling widgets like 15 | /// [ListView], a unique [PageStorageKey] must be specified to enable the 16 | /// [ExpansionTile] to save and restore its expanded state when it is scrolled 17 | /// in and out of view. 18 | /// 19 | /// See also: 20 | /// 21 | /// * [ListTile], useful for creating expansion tile [children] when the 22 | /// expansion tile represents a sublist. 23 | /// * The "Expand/collapse" section of 24 | /// . 25 | class ExpansionTile extends StatefulWidget { 26 | /// Creates a single-line [ListTile] with a trailing button that expands or collapses 27 | /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must 28 | /// be non-null. 29 | const ExpansionTile({ 30 | Key key, 31 | this.leading, 32 | @required this.title, 33 | this.titleOnTap, 34 | this.backgroundColor, 35 | this.onExpansionChanged, 36 | this.children = const [], 37 | this.trailing, 38 | this.initiallyExpanded = false, 39 | }) : assert(initiallyExpanded != null), 40 | super(key: key); 41 | 42 | /// A widget to display before the title. 43 | /// 44 | /// Typically a [CircleAvatar] widget. 45 | final Widget leading; 46 | 47 | /// The primary content of the list item. 48 | /// 49 | /// Typically a [Text] widget. 50 | final Widget title; 51 | 52 | final GestureTapCallback titleOnTap; 53 | 54 | /// Called when the tile expands or collapses. 55 | /// 56 | /// When the tile starts expanding, this function is called with the value 57 | /// true. When the tile starts collapsing, this function is called with 58 | /// the value false. 59 | final ValueChanged onExpansionChanged; 60 | 61 | /// The widgets that are displayed when the tile expands. 62 | /// 63 | /// Typically [ListTile] widgets. 64 | final List children; 65 | 66 | /// The color to display behind the sublist when expanded. 67 | final Color backgroundColor; 68 | 69 | /// A widget to display instead of a rotating arrow icon. 70 | final Widget trailing; 71 | 72 | /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). 73 | final bool initiallyExpanded; 74 | 75 | @override 76 | _ExpansionTileState createState() => _ExpansionTileState(); 77 | } 78 | 79 | class _ExpansionTileState extends State with SingleTickerProviderStateMixin { 80 | static final Animatable _easeOutTween = CurveTween(curve: Curves.easeOut); 81 | static final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); 82 | static final Animatable _halfTween = Tween(begin: 0.0, end: 0.5); 83 | 84 | final ColorTween _borderColorTween = ColorTween(); 85 | final ColorTween _headerColorTween = ColorTween(); 86 | final ColorTween _iconColorTween = ColorTween(); 87 | final ColorTween _backgroundColorTween = ColorTween(); 88 | 89 | AnimationController _controller; 90 | Animation _iconTurns; 91 | Animation _heightFactor; 92 | Animation _borderColor; 93 | Animation _headerColor; 94 | Animation _iconColor; 95 | Animation _backgroundColor; 96 | 97 | bool _isExpanded = false; 98 | 99 | @override 100 | void initState() { 101 | super.initState(); 102 | _controller = AnimationController(duration: _kExpand, vsync: this); 103 | _heightFactor = _controller.drive(_easeInTween); 104 | _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); 105 | _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); 106 | _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); 107 | _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); 108 | _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); 109 | 110 | _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded; 111 | if (_isExpanded) 112 | _controller.value = 1.0; 113 | } 114 | 115 | @override 116 | void dispose() { 117 | _controller.dispose(); 118 | super.dispose(); 119 | } 120 | 121 | void _handleTap() { 122 | setState(() { 123 | _isExpanded = !_isExpanded; 124 | if (_isExpanded) { 125 | _controller.forward(); 126 | } else { 127 | _controller.reverse().then((void value) { 128 | if (!mounted) 129 | return; 130 | setState(() { 131 | // Rebuild without widget.children. 132 | }); 133 | }); 134 | } 135 | PageStorage.of(context)?.writeState(context, _isExpanded); 136 | }); 137 | if (widget.onExpansionChanged != null) 138 | widget.onExpansionChanged(_isExpanded); 139 | } 140 | 141 | Widget _buildChildren(BuildContext context, Widget child) { 142 | final Color borderSideColor = _borderColor.value ?? Colors.transparent; 143 | final Color titleColor = _headerColor.value; 144 | 145 | return Container( 146 | decoration: BoxDecoration( 147 | color: _backgroundColor.value ?? Colors.transparent, 148 | border: Border( 149 | top: BorderSide(color: borderSideColor), 150 | bottom: BorderSide(color: borderSideColor), 151 | ) 152 | ), 153 | child: Column( 154 | mainAxisSize: MainAxisSize.min, 155 | children: [ 156 | IconTheme.merge( 157 | data: IconThemeData(color: _iconColor.value), 158 | child: ListTile( 159 | leading: widget.leading, 160 | title: DefaultTextStyle( 161 | style: Theme.of(context).textTheme.subhead.copyWith(color: titleColor), 162 | child: widget.title, 163 | ), 164 | trailing: new InkWell( 165 | borderRadius: BorderRadius.circular(10), 166 | child: widget.trailing ?? RotationTransition( 167 | turns: _iconTurns, 168 | child: const Icon(Icons.expand_more), 169 | ), 170 | onTap: _handleTap, 171 | ), 172 | onTap: widget.titleOnTap, 173 | ), 174 | ), 175 | ClipRect( 176 | child: Align( 177 | heightFactor: _heightFactor.value, 178 | child: child, 179 | ), 180 | ), 181 | ], 182 | ), 183 | ); 184 | } 185 | 186 | @override 187 | void didChangeDependencies() { 188 | final ThemeData theme = Theme.of(context); 189 | _borderColorTween 190 | ..end = theme.dividerColor; 191 | _headerColorTween 192 | ..begin = theme.textTheme.subhead.color 193 | ..end = theme.accentColor; 194 | _iconColorTween 195 | ..begin = theme.unselectedWidgetColor 196 | ..end = theme.accentColor; 197 | _backgroundColorTween 198 | ..end = widget.backgroundColor; 199 | super.didChangeDependencies(); 200 | } 201 | 202 | @override 203 | Widget build(BuildContext context) { 204 | final bool closed = !_isExpanded && _controller.isDismissed; 205 | return AnimatedBuilder( 206 | animation: _controller.view, 207 | builder: _buildChildren, 208 | child: closed ? null : Column(children: widget.children), 209 | ); 210 | 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/View/GroupView.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:micro_sparrow/View/BookView.dart'; 5 | import 'package:micro_sparrow/View/IGroupView.dart'; 6 | import 'package:micro_sparrow/View/SearchView.dart'; 7 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 8 | import 'package:micro_sparrow/Widget/ReadArticle.dart'; 9 | import 'package:micro_sparrow/presenter/GroupPresenter.dart'; 10 | import 'package:transparent_image/transparent_image.dart'; 11 | import 'package:micro_sparrow/model/Group_entity.dart'; 12 | 13 | 14 | class GroupView extends StatefulWidget{ 15 | 16 | GroupView({Key key, this.id,this.imageUrl,this.title,this.subscripe,this.peopleNum}) : super(key: key); 17 | 18 | final String id; 19 | final String imageUrl; 20 | final String title; 21 | final String subscripe; 22 | final String peopleNum; 23 | 24 | @override 25 | State createState() { 26 | return new _groupPage(); 27 | } 28 | 29 | } 30 | 31 | class _groupPage extends State implements IGroupView{ 32 | 33 | 34 | GroupEntity _groupEntity = new GroupEntity(); 35 | 36 | GroupPresenter _groupPrenster = new GroupPresenter(); 37 | 38 | bool isLoading = true; 39 | 40 | 41 | 42 | @override 43 | void initState() { 44 | super.initState(); 45 | _groupPrenster.init(this); 46 | _groupPrenster.getGroupHttpData(widget.id); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return new Scaffold( 52 | body: CustomScrollView( 53 | slivers: [ 54 | SliverAppBar( 55 | floating: false, 56 | pinned: true, 57 | brightness: Brightness.light, 58 | centerTitle: true, 59 | expandedHeight: 200, 60 | flexibleSpace: _getBar(), 61 | actions: [ 62 | IconButton( 63 | icon: Icon(Icons.add,color: Colors.white,), 64 | onPressed: (){ 65 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 66 | return SearchView(id: widget.id,); 67 | })); 68 | }, 69 | ) 70 | ], 71 | ), 72 | Builder(builder: (BuildContext context){ 73 | return isLoading ? SliverFillRemaining(child: new Center(child: ColorLoader(radius: 15,dotRadius: 6,),),): 74 | SliverList(delegate: SliverChildBuilderDelegate((BuildContext context,int position){ 75 | return _getContentItem(position); 76 | },childCount: _getLength())); 77 | }) 78 | ], 79 | ), 80 | ); 81 | } 82 | 83 | _getBar() { 84 | return Container( 85 | margin: EdgeInsets.all(0), 86 | child: new FlexibleSpaceBar( 87 | background: Stack( 88 | children: [ 89 | new Stack( 90 | fit: StackFit.expand, 91 | children: [ 92 | new Container( 93 | margin: EdgeInsets.all(0), 94 | child: FadeInImage.memoryNetwork(placeholder: kTransparentImage, image: widget.imageUrl,fit: BoxFit.cover,) 95 | ), 96 | BackdropFilter(filter: new ImageFilter.blur(sigmaX: 30,sigmaY: 30), 97 | child:new Container( 98 | margin: EdgeInsets.all(0), 99 | color: Colors.green.withOpacity(0), 100 | ),), 101 | ], 102 | ), 103 | new Center( 104 | child: new Padding(padding: EdgeInsets.only(left: 16,top: 82,right: 16,bottom: 16), 105 | child:new Row( 106 | crossAxisAlignment: CrossAxisAlignment.center, 107 | children: [ 108 | new Container( 109 | width: 80, 110 | height: 80, 111 | child: Hero(tag: widget.id, child: CircleAvatar( 112 | backgroundImage: FadeInImage.memoryNetwork(placeholder: kTransparentImage, image: widget.imageUrl,fit: BoxFit.cover,).image, 113 | ),) 114 | ), 115 | new Container( 116 | margin: EdgeInsets.only(top: 0,left: 16), 117 | child: new Column( 118 | crossAxisAlignment: CrossAxisAlignment.start, 119 | mainAxisAlignment: MainAxisAlignment.center, 120 | children: [ 121 | Text(widget.title,style: new TextStyle(fontSize: 20,fontWeight: FontWeight.bold),), 122 | Padding(padding: EdgeInsets.only(top: 16), 123 | child: Text(widget.subscripe), 124 | ) 125 | ], 126 | ), 127 | ), 128 | new Container( 129 | margin: EdgeInsets.only(left: 16,right: 12), 130 | child: new Icon(Icons.people), 131 | ), 132 | new Text(widget.peopleNum +"人") 133 | ], 134 | ), 135 | ) 136 | ) 137 | ], 138 | ) 139 | ) 140 | ); 141 | 142 | } 143 | 144 | 145 | @override 146 | void getGroupHttpResult(param0, bool param1) { 147 | isLoading = false; 148 | if(param1){ 149 | setState(() { 150 | _groupEntity = GroupEntity.fromJson(param0); 151 | if(_groupEntity.data.length == 0){ 152 | print("group is null "); 153 | }else{ 154 | print("get group data successfully"); 155 | } 156 | }); 157 | } 158 | } 159 | 160 | _getLength() { 161 | if(_groupEntity.data == null){ 162 | return 0; 163 | }else{ 164 | return _groupEntity.data.length; 165 | } 166 | } 167 | 168 | Widget _getContentItem(int position) { 169 | return new Card( 170 | elevation: 0, 171 | margin: EdgeInsets.only(left: 0,right: 0,top: 8,bottom: 8), 172 | child: new Column( 173 | children: [ 174 | new InkWell( 175 | child: new ListTile( 176 | leading: Icon(Icons.book,color: Colors.blue,size: 20,), 177 | title: new Text(_groupEntity.data[position].name,style: new TextStyle(fontWeight: FontWeight.bold),), 178 | trailing: Icon(Icons.chevron_right,color:Colors.grey,), 179 | ), 180 | onTap: (){ 181 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 182 | return new BookView(id: _groupEntity.data[position].id.toString(),title: _groupEntity.data[position].name,); 183 | })); 184 | }, 185 | ), 186 | _getIfDiver(position), 187 | Builder(builder: (BuildContext context){ 188 | return MediaQuery.removePadding(context: context, 189 | removeTop: true, 190 | removeBottom: true, 191 | child: ListView.builder(itemBuilder: (BuildContext context,int mposition){ 192 | return _getSummaryItem(position,mposition); 193 | },itemCount: _getSummaryCount(position),shrinkWrap: true,physics: ClampingScrollPhysics(),) 194 | ); 195 | }) 196 | ], 197 | ), 198 | ); 199 | } 200 | 201 | Widget _getSummaryItem(int position,int mPosition) { 202 | return new InkWell( 203 | child: new Container( 204 | margin: EdgeInsets.only(left: 20,top: 8,right: 8,bottom: 8), 205 | child: new Text(_groupEntity.data[position].summary[mPosition].title,overflow: TextOverflow.ellipsis,), 206 | ), 207 | onTap: (){ 208 | String url = "https://www.yuque.com/" + _groupEntity.data[position].user.login + "/" + _groupEntity.data[position].slug + "/" + _groupEntity.data[position].summary[mPosition].slug; 209 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 210 | return new ReadArticle(url: url,); 211 | })); 212 | }, 213 | ); 214 | } 215 | 216 | _getSummaryCount(int position) { 217 | if(_groupEntity.data[position].summary == null){ 218 | return 0; 219 | }else{ 220 | return _groupEntity.data[position].summary.length; 221 | } 222 | } 223 | 224 | _getIfDiver(int position) { 225 | if(_groupEntity.data[position].summary == null || _groupEntity.data[position].summary.length == 0){ 226 | //todo 优化 227 | return Center(); 228 | }else{ 229 | return new Divider(height: 1); 230 | } 231 | } 232 | 233 | } 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /lib/presenter/MainViewPresenter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 4 | import 'package:micro_sparrow/Api.dart'; 5 | import 'package:micro_sparrow/Utils/SparrowException.dart'; 6 | import 'package:micro_sparrow/Utils/mvp.dart'; 7 | import 'package:micro_sparrow/View/IMainView.dart'; 8 | import 'package:micro_sparrow/model/Doc_entity.dart'; 9 | import 'package:micro_sparrow/model/EventEntity.dart'; 10 | import 'package:micro_sparrow/model/ExistToken_entity.dart'; 11 | import 'package:micro_sparrow/model/Notification_entity.dart'; 12 | import 'package:micro_sparrow/model/ThirdData_entity.dart'; 13 | import 'package:micro_sparrow/model/Token_entity.dart'; 14 | import 'package:micro_sparrow/model/UserInfo_entity.dart'; 15 | import 'package:micro_sparrow/presenter/AbsPresenter.dart'; 16 | import 'package:shared_preferences/shared_preferences.dart'; 17 | import 'package:http/http.dart' as http; 18 | 19 | class MainViewPresenter extends AbsPresenter implements IPresenter{ 20 | 21 | IMainView _view; 22 | String _cookie = ""; 23 | String _token = ""; 24 | bool _isExpire = false; 25 | 26 | @override 27 | init(IView view) { 28 | this._view = view; 29 | } 30 | 31 | Future readCookie() async{ 32 | SharedPreferences preferences =await SharedPreferences.getInstance(); 33 | _cookie = preferences.getString("cookies"); 34 | return; 35 | } 36 | 37 | Future getHistoryHttpData() async{ 38 | if(_cookie == null){ 39 | _view.resultOfEvent(false,null,SparrowException.GET_COOKIES_FAIL); 40 | return; 41 | } 42 | var result = await _getHttpData(Api.historyDoc(0, 15)); 43 | DocEntity docEntity = new DocEntity.fromJson(result); 44 | if(docEntity==null){ 45 | _view.resultOfHistoryDoc(false,null,SparrowException.NETWORK_ERROR); 46 | }else if(docEntity.data == null){ 47 | _view.resultOfHistoryDoc(false,null,SparrowException.NULL_DATA); 48 | }else{ 49 | _view.resultOfHistoryDoc(true,docEntity,SparrowException.OK); 50 | } 51 | return; 52 | } 53 | 54 | Future getEventHttpData() async{ 55 | await readCookie(); 56 | if(_cookie == ""){ 57 | _view.resultOfEvent(false,null,SparrowException.GET_COOKIES_FAIL); 58 | return; 59 | } 60 | if(_cookie == null){ 61 | _view.resultOfEvent(false,null,SparrowException.LOGIN); 62 | return; 63 | } 64 | var result = await _getHttpData(Api.event); 65 | print(result); 66 | EventEntity _event = new EventEntity.fromJson(result); 67 | if(_event==null){ 68 | _view.resultOfEvent(false,null,SparrowException.NETWORK_ERROR); 69 | }else if(_event.data == null){ 70 | _view.resultOfEvent(false,null,SparrowException.NULL_DATA); 71 | }else{ 72 | _view.resultOfEvent(true,_event,SparrowException.OK); 73 | } 74 | return; 75 | } 76 | 77 | 78 | Future> _getHttpData(String api) async{ 79 | Map hearders; 80 | hearders = _getHeaders(); 81 | http.Response response = await http.get(Uri.parse(api), 82 | headers: hearders); 83 | var result = json.decode(response.body); 84 | return result; 85 | } 86 | 87 | 88 | /// 89 | /// 添加请求头 90 | /// 91 | Map _getHeaders() { 92 | Map hearders = new Map(); 93 | hearders["Content-Type"] = "application/x-www-form-urlencoded"; 94 | hearders["User-Agent"] = "sparrow"; 95 | hearders["cookie"] = _cookie; 96 | return hearders; 97 | } 98 | 99 | Future getNesHttpData() async{ 100 | if(_cookie == null){ 101 | _view.resultOfEvent(false,null,SparrowException.GET_COOKIES_FAIL); 102 | } 103 | var result = await _getHttpData(Api.news(0)); 104 | ThirddataEntity thirddataEntity = new ThirddataEntity.fromJson(result); 105 | if(thirddataEntity==null){ 106 | _view.resultOfNews(false,null,SparrowException.NETWORK_ERROR); 107 | }else if(thirddataEntity.data == null){ 108 | _view.resultOfNews(false,null,SparrowException.NULL_DATA); 109 | }else{ 110 | _view.resultOfNews(true,thirddataEntity,SparrowException.OK); 111 | } 112 | return; 113 | } 114 | 115 | void saveCookie() async { 116 | SharedPreferences preferences = await SharedPreferences.getInstance(); 117 | preferences.setString("cookies", _cookie); 118 | } 119 | 120 | Future getAllCookies(FlutterWebviewPlugin flutterWebviewPlugin) async { 121 | final String result = await flutterWebviewPlugin.getAllCookies("https://www.yuque.com/dashboard"); 122 | _cookie = result; 123 | saveCookie(); 124 | await _getToken(); 125 | getEventHttpData(); 126 | getMyInfo(); 127 | getHistoryHttpData(); 128 | getNesHttpData(); 129 | getNotification(); 130 | } 131 | 132 | /// 133 | /// 获取语雀的token 134 | /// 135 | Future _getToken() async{ 136 | SharedPreferences preferences = await SharedPreferences.getInstance(); 137 | _token = preferences.getString("token"); 138 | if(_token == null || _isExpire){ 139 | Map result =await _getHttpData(Api.getToken); 140 | ExisttokenEntity existTokenEntity = ExisttokenEntity.fromJson(result); 141 | if(existTokenEntity == null || existTokenEntity.data.length == 0){ 142 | _createToken(); 143 | return; 144 | } 145 | //获取语雀中已经存在的token值防止重复创建 146 | for(int i = 0;i result = await _getHttpData(Api.getTokenInfo(existTokenEntity.data[i].id.toString())); 149 | TokenEntity exist = TokenEntity.fromJson(result); 150 | _token = exist.data.token; 151 | preferences.setString("token", exist.data.token); 152 | return; 153 | } 154 | } 155 | await _createToken(); 156 | return; 157 | } 158 | 159 | } 160 | 161 | /// 162 | /// 设置token是否有效,当用户自行删除token时,可以通过修改isExpire参数重新获取token 163 | /// 164 | void setTokenIsExpire(bool param){ 165 | this._isExpire = param; 166 | } 167 | 168 | 169 | /// 170 | /// 创建token 171 | /// 172 | Future _createToken() async{ 173 | SharedPreferences preferences = await SharedPreferences.getInstance(); 174 | String postBody = '{"description":"微语雀","scope":"group:read,group,' 175 | 'topic:read,topic,repo:read,repo,doc:read,doc,artboard","type":"oauth"}'; 176 | Map headers = _getHeaders(); 177 | headers['x-csrf-token'] = _cookie.split("ctoken=")[1].split(";")[0]; 178 | headers['content-type'] = "application/json"; 179 | http.Response response = await http.post(Api.token,body: postBody,headers: headers); 180 | Map result = json.decode(response.body); 181 | TokenEntity tokenEntity = TokenEntity.fromJson(result); 182 | preferences.setString("token", tokenEntity.data.token); 183 | } 184 | 185 | void getMoreDocData(int length) async{ 186 | if(_cookie == null){ 187 | _view.resultOfEvent(false,null,SparrowException.GET_COOKIES_FAIL); 188 | return; 189 | } 190 | var result = await _getHttpData(Api.historyDoc(length, 10)); 191 | DocEntity entity = new DocEntity.fromJson(result); 192 | if(entity==null){ 193 | _view.resultOfMoreDoc(false,null,SparrowException.NETWORK_ERROR); 194 | }else if(entity.data == null){ 195 | _view.resultOfMoreDoc(false,null,SparrowException.NULL_DATA); 196 | }else{ 197 | _view.resultOfMoreDoc(true,entity,SparrowException.OK); 198 | } 199 | } 200 | 201 | void getMoreNewsData(int length) async{ 202 | if(_cookie == null){ 203 | _view.resultOfEvent(false,null,SparrowException.GET_COOKIES_FAIL); 204 | return; 205 | } 206 | var result = await _getHttpData(Api.news(length)); 207 | ThirddataEntity entity = new ThirddataEntity.fromJson(result); 208 | if(entity==null){ 209 | _view.resultOfMoreNews(false,null,SparrowException.NETWORK_ERROR); 210 | }else if(entity.data == null){ 211 | _view.resultOfMoreNews(false,null,SparrowException.NULL_DATA); 212 | }else{ 213 | _view.resultOfMoreNews(true,entity,SparrowException.OK); 214 | } 215 | } 216 | 217 | void getMyInfo() async{ 218 | var result = await getTokenHttpData(Api.getMyInfo()); 219 | UserinfoEntity entity = UserinfoEntity.fromJson(result); 220 | if(entity == null || entity.data == null){ 221 | _view.resultOfMyInfo(false,null,SparrowException.NETWORK_ERROR); 222 | return; 223 | } 224 | _view.resultOfMyInfo(true,entity,SparrowException.OK); 225 | } 226 | 227 | void clearAll() async{ 228 | SharedPreferences preferences = await SharedPreferences.getInstance(); 229 | _token = null; 230 | _cookie = null; 231 | preferences.setString("token", null); 232 | preferences.setString("cookies", null); 233 | _view.resultOfLogout(); 234 | } 235 | 236 | void getUnReadNotifications(){ 237 | 238 | } 239 | 240 | void getNotification() async{ 241 | var result = await getCookieHttpData("https://www.yuque.com/api/notifications?offset=0&type=unread"); 242 | NotificationEntity entity = NotificationEntity.fromJson(result); 243 | if(entity == null || entity.data == null || entity.data.normalcount == 0){ 244 | _view.resultOfNotification(false,null,SparrowException.NETWORK_ERROR); 245 | return; 246 | } 247 | _view.resultOfNotification(true,entity,SparrowException.OK); 248 | } 249 | 250 | 251 | 252 | } -------------------------------------------------------------------------------- /lib/View/BookView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | import 'package:micro_sparrow/View/EditView.dart'; 4 | import 'package:micro_sparrow/Widget/ColorLoader.dart'; 5 | import 'package:micro_sparrow/Widget/MyExpansionTitle.dart' as mExpansion; 6 | import 'package:micro_sparrow/View/IBookView.dart'; 7 | import 'package:micro_sparrow/Widget/ReadArticle.dart'; 8 | import 'package:micro_sparrow/model/AllDoc_entity.dart'; 9 | import 'package:micro_sparrow/model/DocDetailSerializer_entity.dart'; 10 | import 'package:micro_sparrow/model/TocTree.dart'; 11 | import 'package:micro_sparrow/presenter/BookPresenter.dart'; 12 | import 'package:micro_sparrow/model/Book_entity.dart'; 13 | 14 | class BookView extends StatefulWidget{ 15 | BookView({Key key, this.id,this.title,}) : super(key: key); 16 | 17 | final String id; 18 | final String title; 19 | 20 | @override 21 | State createState() { 22 | return new _BookPage(); 23 | } 24 | 25 | } 26 | 27 | class _BookPage extends State implements IBookView { 28 | 29 | BookPresenter _bookPresenter = new BookPresenter(); 30 | List _bookList; 31 | String _url = ""; 32 | bool loading = true; 33 | AlldocEntity _allDocEntity; 34 | var _scaffoldkey = new GlobalKey(); 35 | 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | _bookPresenter.init(this); 41 | _bookPresenter.getTopic(widget.id); 42 | _bookPresenter.getAllDoc(widget.id); 43 | } 44 | 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return new DefaultTabController(length: 2, initialIndex: 0,child: Scaffold( 49 | key: _scaffoldkey, 50 | appBar: new AppBar( 51 | backgroundColor: Colors.white, 52 | brightness: Brightness.light, 53 | elevation: 1, 54 | iconTheme: IconThemeData(color: Colors.black87), 55 | flexibleSpace: SafeArea( 56 | child: getTabBar(), 57 | ), 58 | actions: [ 59 | IconButton(icon:Icon(Icons.add), onPressed: (){ 60 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 61 | return EditView(id: widget.id,); 62 | })); 63 | }) 64 | ], 65 | ), 66 | body: TabBarView(children: [ 67 | new Center( 68 | child: loading ? ColorLoader( 69 | radius: 15, 70 | dotRadius: 6, 71 | ):ListView.builder(itemBuilder: (BuildContext context,int position){ 72 | return _getItem(position); 73 | },itemCount: _getLength(),), 74 | ), 75 | new Center( 76 | child: ListView.builder(itemBuilder: (BuildContext context,int position){ 77 | return _getAllItem(position); 78 | },itemCount: _getAllLength(),), 79 | ), 80 | ]) 81 | )); 82 | } 83 | 84 | @override 85 | void resultOfBook(bool param0,content,String message) { 86 | _url = message; 87 | if(param0){ 88 | setState(() { 89 | _bookList = content; 90 | loading = false; 91 | }); 92 | }else{ 93 | setState(() { 94 | loading = false; 95 | }); 96 | } 97 | } 98 | 99 | 100 | Widget _getItem(int position) { 101 | if(_bookList[position].childCount == 0){ 102 | return mExpansion.ExpansionTile( 103 | title:new Text(_bookList[position].title), 104 | children: _getWidgets(position), 105 | trailing: new Container(width: 0,height: 0,), 106 | leading: _ifCanLook(position), 107 | titleOnTap: (){ 108 | if(_bookList[position].style == "DOC"){ 109 | String url = "https://www.yuque.com/"+ _url + "/" +_bookList[position].url; 110 | String title = _bookList[position].title; 111 | _readArticle(url, title); 112 | } 113 | }, 114 | ); 115 | }else{ 116 | return mExpansion.ExpansionTile( 117 | title:new Text(_bookList[position].title), 118 | children: _getWidgets(position), 119 | initiallyExpanded: true, 120 | leading: _ifCanLook(position), 121 | titleOnTap: (){ 122 | if(_bookList[position].style == "DOC"){ 123 | String url = "https://www.yuque.com/"+ _url + "/" +_bookList[position].url; 124 | String title = _bookList[position].title; 125 | _readArticle(url, title); 126 | } 127 | }, 128 | ); 129 | } 130 | } 131 | 132 | _getLength() { 133 | if(_bookList == null){ 134 | return 0; 135 | }else{ 136 | return _bookList.length; 137 | } 138 | } 139 | 140 | _getWidgets(int position) { 141 | List w = new List(); 142 | for(int i = 0;i<_bookList[position].childCount;i++){ 143 | w.add( 144 | new ListTile( 145 | onTap: (){ 146 | if(_bookList[position].child[i].style == "DOC"){ 147 | String url = "https://www.yuque.com/"+ _url + "/" +_bookList[position].child[i].url; 148 | String title = _bookList[position].title; 149 | _readArticle(url, title); 150 | } 151 | }, 152 | leading: new Container( 153 | margin: EdgeInsets.only(left: 8), 154 | child: Icon(Icons.brightness_1,color: Colors.green,size: 15,), 155 | ), 156 | title: new Text(_bookList[position].child[i].title,style: TextStyle(fontSize: 14),), 157 | ), 158 | ); 159 | } 160 | return w; 161 | } 162 | 163 | _readArticle(String uri,String title) { 164 | Navigator.push( 165 | context, new MaterialPageRoute(builder: (BuildContext context) { 166 | return ReadArticle(url: uri,); 167 | })); 168 | } 169 | 170 | _ifCanLook(int position) { 171 | if(_bookList[position].style == "DOC"){ 172 | return Icon(Icons.insert_drive_file,color: Colors.blue,size: 16,); 173 | }else{ 174 | return Icon(Icons.insert_drive_file,size: 16,); 175 | } 176 | } 177 | 178 | Widget _getAllItem(int position) { 179 | return Column( 180 | children: [ 181 | ListTile(title: Text(_allDocEntity.data[position].title), 182 | onTap: (){ 183 | String url = "https://www.yuque.com/"+ "go/doc/" + _allDocEntity.data[position].id.toString(); 184 | Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){ 185 | return new ReadArticle(url: url,); 186 | })); 187 | }, 188 | leading: Icon(Icons.brightness_1,color: Colors.green,size: 15,), 189 | trailing: PopupMenuButton(itemBuilder: (BuildContext context)=>[ 190 | new PopupMenuItem( 191 | value:0, 192 | child: Text("编辑",style: TextStyle(color: _allDocEntity.data[position].format == "markdown" ? Colors.black:Colors.grey),) 193 | ), 194 | new PopupMenuItem( 195 | value:1, 196 | child: Text("删除",style: TextStyle(color: Colors.red),) 197 | ), 198 | new PopupMenuItem( 199 | value:2, 200 | child: Text("移到目录") 201 | ), 202 | ], 203 | icon:Icon(Icons.more_vert,color: Colors.green,size: 20,), 204 | onSelected: (int value){ 205 | if(value == 0 && _allDocEntity.data[position].format == "markdown"){ 206 | _bookPresenter.readExistBook(_allDocEntity.data[position].slug,widget.id); 207 | } else if(value == 1){ 208 | _bookPresenter.deleteDoc(position,widget.id,_allDocEntity.data[position].id.toString()); 209 | }else{ 210 | _scaffoldkey.currentState.showSnackBar(SnackBar(content: Text("暂未实现"))); 211 | } 212 | }, 213 | ), 214 | ), 215 | Divider(height: 1,) 216 | ], 217 | ); 218 | } 219 | 220 | _getAllLength() { 221 | if(_allDocEntity==null || _allDocEntity.data == null){ 222 | return 0; 223 | }else{ 224 | return _allDocEntity.data.length; 225 | } 226 | } 227 | 228 | @override 229 | void resultOfAllDoc(bool param0, AlldocEntity entity, String ok) { 230 | if(param0){ 231 | setState(() { 232 | _allDocEntity = entity; 233 | }); 234 | } 235 | } 236 | 237 | getTabBar() { 238 | return new Center( 239 | child: TabBar( 240 | isScrollable: true, 241 | labelColor: Colors.green, 242 | indicatorColor: Colors.green, 243 | unselectedLabelColor: Colors.black87, 244 | indicatorSize: TabBarIndicatorSize.label, 245 | tabs: [ 246 | Tab(text: "目录",), 247 | Tab(text: "文章",) 248 | ]), 249 | ); 250 | } 251 | 252 | @override 253 | void resultOfDelDoc(bool param0, int position, String ok) { 254 | if(param0){ 255 | _scaffoldkey.currentState.showSnackBar(SnackBar(content: Text("删除成功"),backgroundColor: Colors.green,)); 256 | setState(() { 257 | _allDocEntity.data.removeAt(position); 258 | }); 259 | } 260 | } 261 | 262 | @override 263 | void resultOfReadDoc(bool param0, DocdetailserializerEntity entity, String ok) { 264 | if(param0){ 265 | Navigator.of(_scaffoldkey.currentState.context).push(MaterialPageRoute(builder: (BuildContext context){ 266 | return new EditView(id: widget.id,entity: entity,); 267 | })); 268 | } 269 | } 270 | } --------------------------------------------------------------------------------