├── images ├── bg.jpg ├── bg_2.jpg ├── logo.png ├── smile.png ├── icon_msg.png ├── icon_wx.png ├── loading.png ├── profile.png ├── empty_data.png ├── home_green.png ├── icon_circle.png ├── icon_home.png ├── icon_sina.png └── profile_2.jpg ├── lib ├── model │ ├── BannerEntity.dart │ ├── MenuModel.dart │ ├── EventList.dart │ └── CategoryResponse.dart ├── api │ ├── Api.dart │ └── http.dart ├── pages │ ├── home │ │ ├── HomeBuildRows.dart │ │ ├── HomeListView.dart │ │ └── HomePage.dart │ ├── mine │ │ ├── MinePage.dart │ │ ├── videoCard.dart │ │ └── settingCard.dart │ ├── history │ │ ├── WidgetHistoryTitle.dart │ │ ├── HistoryDetailPage.dart │ │ ├── HistoryPage.dart │ │ └── WidgetHistoryList.dart │ ├── classify │ │ ├── ClassifyPage.dart │ │ └── ClassifyTabPage.dart │ ├── search │ │ ├── SearchPage.dart │ │ └── SearchListPage.dart │ ├── detail │ │ ├── ArticleDetailPage.dart │ │ ├── DetailListView.dart │ │ └── DetailPage.dart │ ├── index │ │ └── IndexDrawPage.dart │ ├── push │ │ └── PushPage.dart │ ├── Application.dart │ ├── update │ │ └── UpdatePage.dart │ └── login │ │ └── LoginPage.dart ├── main.dart ├── utils │ ├── DialogUtils.dart │ ├── SharedPrfUtils.dart │ ├── ScreenUtils.dart │ ├── IndicatorUtils.dart │ ├── TimeUtils.dart │ ├── LoadingDialogUtils.dart │ ├── PageRouteUtils.dart │ └── MessageDialogUtils.dart ├── common │ ├── CommonShare.dart │ ├── Constant.dart │ ├── WelfareBuildRows.dart │ ├── GlobalConfig.dart │ ├── WebViewPage.dart │ └── DetailList.dart ├── widget │ └── NewsBannerView.dart └── welfare │ └── PhotoView.dart ├── .gitignore ├── .idea ├── dictionaries │ └── zhouqiong.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── libraries │ ├── gradle_wrapper.xml │ ├── Flutter_Plugins.xml │ ├── Dart_SDK.xml │ └── Dart_Packages.xml ├── modules.xml └── misc.xml ├── android ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradle.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_logo_center.png │ │ │ │ └── ic_logo_font.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── gen │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── fluttergank │ │ │ │ ├── R.java │ │ │ │ └── Manifest.java │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── fluttergank │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── .idea │ ├── encodings.xml │ ├── vcs.xml │ ├── misc.xml │ ├── runConfigurations.xml │ ├── gradle.xml │ └── modules.xml ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── logo.imageset │ │ │ ├── logo@2x.png │ │ │ ├── logo@3x.png │ │ │ └── Contents.json │ │ ├── GankFont.imageset │ │ │ ├── GankFont.png │ │ │ ├── GankFont@2x.png │ │ │ ├── GankFont@3x.png │ │ │ └── Contents.json │ │ ├── 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 │ ├── AppDelegate.m │ ├── Info.plist │ └── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── .gitignore ├── Podfile.lock └── Podfile ├── .metadata ├── test └── widget_test.dart ├── flutter_gank_android.iml ├── flutter_gank.iml ├── pubspec.yaml ├── GankFlutter.iml └── README.md /images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/bg.jpg -------------------------------------------------------------------------------- /lib/model/BannerEntity.dart: -------------------------------------------------------------------------------- 1 | class BannerEntity { 2 | List banner; 3 | } 4 | -------------------------------------------------------------------------------- /images/bg_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/bg_2.jpg -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/smile.png -------------------------------------------------------------------------------- /images/icon_msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/icon_msg.png -------------------------------------------------------------------------------- /images/icon_wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/icon_wx.png -------------------------------------------------------------------------------- /images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/loading.png -------------------------------------------------------------------------------- /images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/profile.png -------------------------------------------------------------------------------- /images/empty_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/empty_data.png -------------------------------------------------------------------------------- /images/home_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/home_green.png -------------------------------------------------------------------------------- /images/icon_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/icon_circle.png -------------------------------------------------------------------------------- /images/icon_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/icon_home.png -------------------------------------------------------------------------------- /images/icon_sina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/icon_sina.png -------------------------------------------------------------------------------- /images/profile_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/images/profile_2.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /.idea/dictionaries/zhouqiong.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/logo.imageset/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/logo.imageset/logo@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/logo.imageset/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/logo.imageset/logo@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_logo_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_logo_center.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_logo_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_logo_font.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/GankFont.imageset/GankFont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/GankFont.imageset/GankFont.png -------------------------------------------------------------------------------- /android/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #5b88ff 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/GankFont.imageset/GankFont@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/GankFont.imageset/GankFont@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/GankFont.imageset/GankFont@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/GankFont.imageset/GankFont@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQ330093887/GankFlutter/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/ZQ330093887/GankFlutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/gen/com/example/fluttergank/R.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.example.fluttergank; 4 | 5 | /* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */ 6 | public final class R { 7 | } -------------------------------------------------------------------------------- /android/app/src/main/gen/com/example/fluttergank/Manifest.java: -------------------------------------------------------------------------------- 1 | /*___Generated_by_IDEA___*/ 2 | 3 | package com.example.fluttergank; 4 | 5 | /* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */ 6 | public final class Manifest { 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 | -------------------------------------------------------------------------------- /.idea/libraries/gradle_wrapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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.4-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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: 66091f969653fd3535b265ddcd87436901858a1d 8 | channel: dev 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "logo@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "logo@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/fluttergank/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.fluttergank; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/GankFont.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "GankFont.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "GankFont@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "GankFont@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/api/Api.dart: -------------------------------------------------------------------------------- 1 | class Api { 2 | static final String HOST = "http://gank.io/api"; 3 | static final String BANNER = "https://gank.io/api/v2/banners"; 4 | static final String FEED_URL = "https://gank.io/api/v2/data/category/"; 5 | static final String SEARCH_URL = "https://gank.io/api/v2/search/"; 6 | static final String TODAY_URL = "https://gank.io/api/v2/hot/views/category/Article/count/10"; 7 | static final String HISTORY_URL = "https://gank.io/api/day/history"; 8 | static final String DATA_DAY_URL = "http://gank.io/api/day/"; 9 | } 10 | -------------------------------------------------------------------------------- /lib/pages/home/HomeBuildRows.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/model/CategoryResponse.dart'; 2 | import 'package:GankFlutter/widget/NewsBannerView.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | // ignore: non_constant_identifier_names 7 | Widget HomeBuildRows(postData) { 8 | List banner = new List(); 9 | if (postData != null) { 10 | for (var value in postData) { 11 | banner.add(BannerData.fromJson(value)); 12 | } 13 | } 14 | return new NewsBannerView(banner); 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/pages/Application.dart'; 3 | import 'package:GankFlutter/common/GlobalConfig.dart'; 4 | 5 | void main() => runApp(new MyApp()); 6 | 7 | class MyApp extends StatelessWidget { 8 | // This widget is the root of your application. 9 | @override 10 | Widget build(BuildContext context) { 11 | return new MaterialApp( 12 | title: 'Flutter 干货集中营', 13 | theme: new ThemeData( 14 | primarySwatch: GlobalConfig.colorPrimary, 15 | ), 16 | home: new ApplicationPage(), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | maven { url "http://jcenter.bintray.com" } 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | maven { url "http://jcenter.bintray.com" } 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 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /lib/utils/DialogUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/utils/LoadingDialogUtils.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class DialogUtils { 6 | 7 | static LoadingDialogUtils dialogUtils; 8 | 9 | static void show(BuildContext context, String msg) { 10 | dialogUtils = new LoadingDialogUtils( 11 | //调用对话框 12 | text: msg, 13 | ); 14 | showDialog( 15 | context: context, //BuildContext对象 16 | barrierDismissible: true, 17 | builder: (BuildContext context) { 18 | return dialogUtils; 19 | }); 20 | } 21 | 22 | static void hidden() { 23 | if (dialogUtils != null) { 24 | dialogUtils.dismiss(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/SharedPrfUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class SharedPrfUtils { 4 | static saveString(String key, value) async { 5 | SharedPreferences spf = await SharedPreferences.getInstance(); 6 | spf.setString(key, value); 7 | } 8 | 9 | static saveList(String key, List value) async { 10 | SharedPreferences spf = await SharedPreferences.getInstance(); 11 | spf.setStringList(key, value); 12 | } 13 | 14 | static get(String key) async { 15 | SharedPreferences spf = await SharedPreferences.getInstance(); 16 | return spf.get(key); 17 | } 18 | 19 | static remove(String key) async { 20 | SharedPreferences spf = await SharedPreferences.getInstance(); 21 | spf.remove(key); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 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/model/MenuModel.dart: -------------------------------------------------------------------------------- 1 | const String wx = '微信好友'; 2 | const String wx_circle = '微信朋友圈'; 3 | const String sina = '新浪微博'; 4 | const String msg = '短信'; 5 | //const String qq = 'QQ好友'; 6 | //const String q_zone = 'QQ空间'; 7 | //const String copy_url = '复制链接'; 8 | //const String browser = '浏览器打开'; 9 | 10 | class Menu { 11 | final int index; 12 | final String title; 13 | final String icon; 14 | 15 | const Menu({this.index, this.title, this.icon}); 16 | } 17 | 18 | const List menus_share = const [ 19 | const Menu(index: 0, title: wx, icon: "images/icon_wx.png"), 20 | const Menu(index: 1, title: wx_circle, icon: "images/icon_circle.png"), 21 | const Menu(index: 2, title: sina, icon: "images/icon_sina.png"), 22 | const Menu(index: 3, title: msg, icon: "images/icon_msg.png"), 23 | ]; 24 | 25 | const List menus_login = const [ 26 | const Menu(index: 0, title: wx, icon: "images/icon_wx.png"), 27 | const Menu(index: 1, title: sina, icon: "images/icon_sina.png"), 28 | ]; 29 | -------------------------------------------------------------------------------- /lib/pages/mine/MinePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/GlobalConfig.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'myInfoCard.dart'; 6 | import 'myServiceCard.dart'; 7 | import 'videoCard.dart'; 8 | 9 | class MinePage extends StatefulWidget { 10 | @override 11 | State createState() => _MinePageState(); 12 | } 13 | 14 | class _MinePageState extends State { 15 | @override 16 | void initState() { 17 | super.initState(); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new Scaffold( 23 | body: new SingleChildScrollView( 24 | child: new Container( 25 | color: GlobalConfig.backgroundColor, 26 | child: new Column( 27 | children: [ 28 | myInfoCard(context), 29 | myServiceCard(context), 30 | // settingCard(context), 31 | videoCard(context), //图片卡片 32 | // ideaCard(context) 33 | ], 34 | ), 35 | ), 36 | )); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:GankFlutter/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/pages/home/HomeListView.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/Constant.dart'; 2 | import 'package:GankFlutter/model/BannerEntity.dart'; 3 | import 'package:GankFlutter/pages/home/HomeBuildRows.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | Widget buildDailyListView( 8 | BuildContext context, List homeData, bool requestError) { 9 | ///如果首页item的数据为空则显示加载进度条 10 | if (homeData == null) { 11 | return buildLoadingIndicator(); 12 | } 13 | 14 | if (requestError) { 15 | return buildExceptionIndicator("网络请求错误"); 16 | } 17 | 18 | if (homeData.length == 0) { 19 | return buildExceptionIndicator("这里空空的什么都没有呢..."); 20 | } else { 21 | return buildListViewBuilder(context, homeData); 22 | } 23 | } 24 | 25 | Widget buildListViewBuilder(context, List items) { 26 | return new ListView.builder( 27 | physics: const AlwaysScrollableScrollPhysics(), 28 | padding: const EdgeInsets.all(2.0), 29 | itemCount: items == null ? 0 : items.length, 30 | itemBuilder: (context, i) { 31 | final item = items[i]; 32 | if (item is BannerEntity) { 33 | return HomeBuildRows(item.banner); 34 | } else { 35 | return buildRow(context, item); 36 | } 37 | }, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/pages/history/WidgetHistoryTitle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/common/GlobalConfig.dart'; 3 | 4 | class WidgetHistoryTitle extends StatefulWidget { 5 | final String category; 6 | 7 | WidgetHistoryTitle(this.category); 8 | 9 | @override 10 | _WidgetHistoryTitleState createState() => _WidgetHistoryTitleState(); 11 | } 12 | 13 | class _WidgetHistoryTitleState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | decoration: BoxDecoration( 18 | color: GlobalConfig.cardBackgroundColor, 19 | border: Border( 20 | bottom: BorderSide(width: 0.0, color: Theme.of(context).dividerColor), 21 | ), 22 | ), 23 | child: Row( 24 | children: [ 25 | Container( 26 | margin: EdgeInsets.symmetric(vertical: 15), 27 | width: 4, 28 | height: 20, 29 | color: Theme.of(context).primaryColor, 30 | ), 31 | Padding( 32 | padding: const EdgeInsets.only(left: 12.0), 33 | child: Text( 34 | widget.category, 35 | style: Theme.of(context).textTheme.title, 36 | ), 37 | ) 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/model/EventList.dart: -------------------------------------------------------------------------------- 1 | class EventList { 2 | Map> events; 3 | 4 | EventList({ 5 | this.events, 6 | }); 7 | 8 | void add(DateTime date, DateTime event) { 9 | if (events == null) 10 | events = { 11 | date: [event] 12 | }; 13 | else if (!events.containsKey(date)) 14 | events[date] = [event]; 15 | else 16 | events[date].add(event); 17 | } 18 | 19 | void addAll(DateTime date, List events) { 20 | if (this.events == null) 21 | this.events = {date: events}; 22 | else if (!this.events.containsKey(date)) 23 | this.events[date] = events; 24 | else 25 | this.events[date].addAll(events); 26 | } 27 | 28 | bool remove(DateTime date, DateTime event) { 29 | return events != null && events.containsKey(date) 30 | ? events[date].remove(event) 31 | : true; 32 | } 33 | 34 | List removeAll(DateTime date) { 35 | return events != null && events.containsKey(date) 36 | ? events.remove(date) 37 | : []; 38 | } 39 | 40 | void clear() { 41 | if (events != null) { 42 | events.clear(); 43 | } else { 44 | events = {}; 45 | } 46 | } 47 | 48 | List getEvents(DateTime date) { 49 | return events != null && events.containsKey(date) ? events[date] : []; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /lib/utils/ScreenUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'dart:ui' as ui show window; 4 | 5 | 6 | class Screen { 7 | static double get width { 8 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 9 | return mediaQuery.size.width; 10 | } 11 | 12 | static double get height { 13 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 14 | return mediaQuery.size.height; 15 | } 16 | 17 | static double get scale { 18 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 19 | return mediaQuery.devicePixelRatio; 20 | } 21 | 22 | static double get textScaleFactor { 23 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 24 | return mediaQuery.textScaleFactor; 25 | } 26 | 27 | static double get navigationBarHeight { 28 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 29 | return mediaQuery.padding.top + kToolbarHeight; 30 | } 31 | 32 | static double get topSafeHeight { 33 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 34 | return mediaQuery.padding.top; 35 | } 36 | 37 | static double get bottomSafeHeight { 38 | MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window); 39 | return mediaQuery.padding.bottom; 40 | } 41 | 42 | static updateStatusBarStyle(SystemUiOverlayStyle style) { 43 | SystemChrome.setSystemUIOverlayStyle(style); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/utils/IndicatorUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | import 'package:GankFlutter/common/GlobalConfig.dart'; 5 | 6 | class IndicatorFactory { 7 | GlobalKey _easyRefreshKey = 8 | new GlobalKey(); 9 | GlobalKey _headerKey = 10 | new GlobalKey(); 11 | GlobalKey _footerKey = 12 | new GlobalKey(); 13 | 14 | GlobalKey get easyRefreshKey => 15 | _easyRefreshKey; // Widget buildDefaultHeader(BuildContext context, int mode) { 16 | 17 | Widget buildDefaultHeader() { 18 | return ClassicsHeader( 19 | key: _headerKey, 20 | refreshText: "pullToRefresh", 21 | textColor: GlobalConfig.colorPrimary, 22 | refreshReadyText: "释放可以刷新", 23 | refreshingText: "正在刷新...", 24 | refreshedText: "刷新完成", 25 | moreInfo: "更新于 %T", 26 | bgColor: Colors.white); 27 | } 28 | 29 | Widget buildDefaultFooter() { 30 | return ClassicsFooter( 31 | key: _footerKey, 32 | loadHeight: 50.0, 33 | textColor: GlobalConfig.colorPrimary, 34 | loadText: "上拉加载", 35 | loadReadyText: "释放加载", 36 | loadingText: "火热加载中...", 37 | loadedText: "加载结束", 38 | noMoreText: "加载完成", 39 | moreInfo: "更新于 %T", 40 | bgColor: Colors.white); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/common/CommonShare.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/model/MenuModel.dart'; 3 | 4 | class CommonShare { 5 | static buildShareBottomPop(BuildContext context) { 6 | showModalBottomSheet( 7 | context: context, 8 | builder: (context) { 9 | return new Container( 10 | height: 120.0, 11 | color: Colors.white, 12 | child: new GridView.count( 13 | crossAxisCount: 4, 14 | mainAxisSpacing: 4.0, 15 | crossAxisSpacing: 4.0, 16 | padding: const EdgeInsets.all(4.0), 17 | children: menus_share.map((Menu m) { 18 | return new GestureDetector( 19 | onTap: () {}, 20 | child: new Column( 21 | mainAxisSize: MainAxisSize.min, 22 | children: [ 23 | new Padding( 24 | padding: 25 | const EdgeInsets.only(top: 12.0, bottom: 12.0), 26 | child: new Image.asset( 27 | m.icon, 28 | width: 40.0, 29 | height: 40.0, 30 | )), 31 | new Text(m.title), 32 | ], 33 | ), 34 | ); 35 | }).toList(), 36 | )); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/common/Constant.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/DetailList.dart'; 2 | import 'package:GankFlutter/common/WelfareBuildRows.dart'; 3 | import 'package:GankFlutter/model/CategoryResponse.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | Widget buildRow(context, one) { 8 | PostData postData = PostData.fromJson(one); 9 | if (postData.type == 'Girl') { 10 | return BuildWelfareRows(context, postData); 11 | } else { 12 | return buildDetailListRow(context, postData); 13 | } 14 | } 15 | 16 | ///异常处理 17 | Widget buildExceptionIndicator(String message) { 18 | return new Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | new Align( 22 | alignment: Alignment.center, 23 | child: new Column( 24 | children: [ 25 | new Image.asset( 26 | 'images/empty_data.png', 27 | width: 50.0, 28 | height: 50.0, 29 | color: Colors.grey, 30 | ), 31 | new Container( 32 | padding: EdgeInsets.only(top: 20.0), 33 | child: new Text( 34 | message, 35 | style: const TextStyle(color: Colors.grey), 36 | ), 37 | ) 38 | ], 39 | ), 40 | ), 41 | ], 42 | ); 43 | } 44 | 45 | ///正在加载 46 | Widget buildLoadingIndicator() { 47 | return new Center( 48 | child: new CupertinoActivityIndicator(), 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /lib/pages/classify/ClassifyPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'ClassifyTabPage.dart'; 4 | 5 | class ClassifyPage extends StatefulWidget { 6 | @override 7 | State createState() => _ClassifyPageState(); 8 | } 9 | 10 | class _ClassifyPageState extends State 11 | with SingleTickerProviderStateMixin { 12 | TabController _controller; 13 | ClassifyTabPage classifyTabPage; 14 | List classifyTab; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | classifyTabPage = new ClassifyTabPage(); 20 | classifyTab = classifyTabPage.initClassify(); 21 | _controller = new TabController(vsync: this, length: classifyTab.length); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | super.dispose(); 27 | _controller.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return new Scaffold( 33 | appBar: new AppBar( 34 | title: new TabBar( 35 | controller: _controller, 36 | indicatorColor: Theme.of(context).primaryColor, 37 | isScrollable: true, 38 | tabs: classifyTab.map((ClassifyTabPage page) { 39 | return new Tab(text: page.text); 40 | }).toList(), 41 | ), 42 | ), 43 | body: new TabBarView( 44 | controller: _controller, 45 | children: classifyTab.map((ClassifyTabPage page) { 46 | return page.detailPage; 47 | }).toList()), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/TimeUtils.dart: -------------------------------------------------------------------------------- 1 | String getTimestampString(DateTime date) { 2 | DateTime curDate = new DateTime.now(); 3 | Duration diff = curDate.difference(date); 4 | if (diff.inDays > 0) { 5 | return diff.inDays.toString() + "天前"; 6 | } else if (diff.inHours > 0) { 7 | return diff.inHours.toString() + "小时前"; 8 | } else if (diff.inMinutes > 0) { 9 | return diff.inMinutes.toString() + "分钟前"; 10 | } else if (diff.inSeconds > 0) { 11 | return "刚刚"; 12 | } 13 | return date.toString(); 14 | } 15 | 16 | String formatDateStr(String date) { 17 | DateTime moonLanding = DateTime.parse(date); 18 | return '${moonLanding.year}-${moonLanding.month}-${moonLanding.day}'; 19 | } 20 | 21 | //String getWeekDay(String date) { 22 | // DateTime dateTime = DateTime.parse(date); 23 | // var weekDay = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; 24 | // return '${weekDay[dateTime.weekday - 1]}'; 25 | //} 26 | 27 | String getWeekDay(int i) { 28 | var weekDay = ["日", "一", "二", "三", "四", "五", "六"]; 29 | return '${weekDay[i]}'; 30 | } 31 | 32 | String getDay(date) { 33 | DateTime dateTime = DateTime.parse(date); 34 | String day = dateTime.day.toString(); 35 | return day.length < 2 ? '0$day' : day; 36 | } 37 | 38 | String getMonth(date) { 39 | DateTime dateTime = DateTime.parse(date); 40 | var months = [ 41 | "一月", 42 | "二月", 43 | "三月", 44 | "四月", 45 | "五月", 46 | "六月", 47 | "七月", 48 | "八月", 49 | "九月", 50 | "十月", 51 | "十一月", 52 | "十二月" 53 | ]; 54 | return '${months[dateTime.month - 1]}'; 55 | } -------------------------------------------------------------------------------- /flutter_gank_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /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 | Gank集中营 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 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 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/utils/LoadingDialogUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///进度条dialog 4 | // ignore: must_be_immutable 5 | class LoadingDialogUtils extends Dialog { 6 | String text; 7 | BuildContext context; 8 | 9 | LoadingDialogUtils({Key key, @required this.text}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | this.context = context; 14 | return new Material( 15 | //创建透明层 16 | type: MaterialType.transparency, //透明类型 17 | child: new Center( 18 | //保证控件居中效果 19 | child: new SizedBox( 20 | width: 120.0, 21 | height: 120.0, 22 | child: new Container( 23 | decoration: ShapeDecoration( 24 | color: Color(0xffffffff), 25 | shape: RoundedRectangleBorder( 26 | borderRadius: BorderRadius.all( 27 | Radius.circular(8.0), 28 | ), 29 | ), 30 | ), 31 | child: new Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | new CircularProgressIndicator(), 36 | new Padding( 37 | padding: const EdgeInsets.only( 38 | top: 20.0, 39 | ), 40 | child: new Text( 41 | text, 42 | style: new TextStyle(fontSize: 12.0), 43 | ), 44 | ), 45 | ], 46 | ), 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | 53 | dismiss() { 54 | Navigator.of(context).pop(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/api/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'dart:async'; 3 | import 'package:GankFlutter/model/CategoryResponse.dart'; 4 | 5 | import 'dart:convert'; 6 | 7 | class HttpExt { 8 | static void get(String url, Function callback, Function errorCallback) async { 9 | try { 10 | http.Response res = await http.get(url); 11 | if (callback != null) { 12 | callback(res.body); 13 | } 14 | } catch (exception) { 15 | if (errorCallback != null) { 16 | errorCallback(exception); 17 | } 18 | } 19 | } 20 | 21 | Future getRequest(String url, [Map params]) async { 22 | http.Response response = await http.get(url, headers: params); 23 | return response.body.toString(); 24 | } 25 | 26 | Future getGankfromNet(String url) async { 27 | final responseStr = await getRequest(url); 28 | return CategoryResponse.fromJson(jsonDecode(responseStr)); 29 | } 30 | 31 | 32 | Future getHistoryRequest(String url) async { 33 | final responseStr = await getRequest(url); 34 | return HistoryResponse.fromJson(jsonDecode(responseStr)); 35 | } 36 | 37 | Future getGankDayData(String url) async { 38 | final responseStr = await getRequest(url); 39 | return GankPost.fromJson(jsonDecode(responseStr)); 40 | } 41 | 42 | static void post(String url, Function callback, 43 | {Map params, Function errorCallback}) async { 44 | try { 45 | http.Response res = await http.post(url, body: params); 46 | if (callback != null) { 47 | callback(res.body); 48 | } 49 | } catch (e) { 50 | if (errorCallback != null) { 51 | errorCallback(e); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/classify/ClassifyTabPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/pages/detail/DetailPage.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ClassifyTabPage { 5 | ClassifyTabPage({this.icon, this.text, this.detailPage}); 6 | 7 | final IconData icon; 8 | final String text; 9 | final DetailPage detailPage; 10 | 11 | List initClassify() { 12 | // 存储所有页面的列表 13 | List _allPages = [ 14 | new ClassifyTabPage( 15 | text: "all", 16 | detailPage: new DetailPage( 17 | feedType: ('All'), 18 | showTitle: false, 19 | )), 20 | new ClassifyTabPage( 21 | text: "福利", 22 | detailPage: new DetailPage( 23 | feedType: ('Girl'), 24 | showTitle: false, 25 | )), 26 | new ClassifyTabPage( 27 | text: "Android", 28 | detailPage: new DetailPage( 29 | feedType: ('Android'), 30 | showTitle: false, 31 | )), 32 | new ClassifyTabPage( 33 | text: "iOS", 34 | detailPage: new DetailPage( 35 | feedType: ('iOS'), 36 | showTitle: false, 37 | )), 38 | new ClassifyTabPage( 39 | text: "前端", 40 | detailPage: new DetailPage( 41 | feedType: ('frontend'), 42 | showTitle: false, 43 | )), 44 | new ClassifyTabPage( 45 | text: "Flutter", 46 | detailPage: new DetailPage( 47 | feedType: ('Flutter'), 48 | showTitle: false, 49 | )), 50 | new ClassifyTabPage( 51 | text: "App", 52 | detailPage: new DetailPage( 53 | feedType: ('app'), 54 | showTitle: false, 55 | )), 56 | ]; 57 | return _allPages; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/pages/search/SearchPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/pages/search/SearchListPage.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class SearchPage extends StatefulWidget { 6 | @override 7 | _SearchPageState createState() => new _SearchPageState(); 8 | } 9 | 10 | class _SearchPageState extends State { 11 | final controller = TextEditingController(); 12 | SearchListPage _searchListPage; 13 | 14 | void changeContent() { 15 | setState(() { 16 | _searchListPage = new SearchListPage(new ValueKey(controller.text)); 17 | }); 18 | } 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | changeContent(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | TextField searchField = new TextField( 29 | autofocus: true, 30 | textInputAction: TextInputAction.search, 31 | onSubmitted: (content) { 32 | changeContent(); 33 | }, 34 | decoration: new InputDecoration( 35 | border: InputBorder.none, 36 | hintText: '搜索真的好了,不骗你', 37 | hintStyle: TextStyle(color: Colors.white24), 38 | ), 39 | controller: controller, 40 | style: TextStyle(color: Colors.white), 41 | ); 42 | 43 | return new Scaffold( 44 | appBar: new AppBar( 45 | title: searchField, 46 | actions: [ 47 | new IconButton( 48 | icon: new Icon(Icons.search), 49 | onPressed: () { 50 | changeContent(); 51 | }), 52 | new IconButton( 53 | icon: new Icon(Icons.close), 54 | onPressed: () { 55 | setState(() { 56 | controller.clear(); 57 | }); 58 | }), 59 | ], 60 | ), 61 | body: _searchListPage, 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_webview_plugin (0.0.1): 4 | - Flutter 5 | - FMDB (2.7.2): 6 | - FMDB/standard (= 2.7.2) 7 | - FMDB/standard (2.7.2) 8 | - path_provider (0.0.1): 9 | - Flutter 10 | - shared_preferences (0.0.1): 11 | - Flutter 12 | - sqflite (0.0.1): 13 | - Flutter 14 | - FMDB (~> 2.7.2) 15 | - url_launcher (0.0.1): 16 | - Flutter 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `.symlinks/flutter/ios`) 20 | - flutter_webview_plugin (from `.symlinks/plugins/flutter_webview_plugin/ios`) 21 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 22 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 23 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 24 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 25 | 26 | SPEC REPOS: 27 | https://github.com/cocoapods/specs.git: 28 | - FMDB 29 | 30 | EXTERNAL SOURCES: 31 | Flutter: 32 | :path: ".symlinks/flutter/ios" 33 | flutter_webview_plugin: 34 | :path: ".symlinks/plugins/flutter_webview_plugin/ios" 35 | path_provider: 36 | :path: ".symlinks/plugins/path_provider/ios" 37 | shared_preferences: 38 | :path: ".symlinks/plugins/shared_preferences/ios" 39 | sqflite: 40 | :path: ".symlinks/plugins/sqflite/ios" 41 | url_launcher: 42 | :path: ".symlinks/plugins/url_launcher/ios" 43 | 44 | SPEC CHECKSUMS: 45 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 46 | flutter_webview_plugin: 0b491d31c34ab5c86a71c9f1a57ac000dd1b75e9 47 | FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa 48 | path_provider: 09407919825bfe3c2deae39453b7a5b44f467873 49 | shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 50 | sqflite: d1612813fa7db7c667bed9f1d1b508deffc56999 51 | url_launcher: 92b89c1029a0373879933c21642958c874539095 52 | 53 | PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2 54 | 55 | COCOAPODS: 1.6.0.beta.1 56 | -------------------------------------------------------------------------------- /lib/pages/detail/ArticleDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 3 | 4 | //文章详情界面 5 | // ignore: must_be_immutable 6 | class ArticleDetailPage extends StatefulWidget { 7 | String title; 8 | String url; 9 | 10 | ArticleDetailPage({ 11 | Key key, 12 | @required this.title, 13 | @required this.url, 14 | }) : super(key: key); 15 | 16 | @override 17 | State createState() { 18 | return new ArticleDetailPageState(); 19 | } 20 | } 21 | 22 | class ArticleDetailPageState extends State { 23 | bool isLoadind = true; 24 | 25 | final flutterWebViewPlugin = new FlutterWebviewPlugin(); 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | flutterWebViewPlugin.onStateChanged.listen((state) { 31 | debugPrint('state:_' + state.type.toString()); 32 | if (state.type == WebViewState.finishLoad) { 33 | // 加载完成 34 | setState(() { 35 | isLoadind = false; 36 | }); 37 | } else if (state.type == WebViewState.startLoad) { 38 | setState(() { 39 | isLoadind = true; 40 | }); 41 | } 42 | }); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return new WebviewScaffold( 48 | url: widget.url, 49 | appBar: new AppBar( 50 | title: new Text(widget.title), 51 | bottom: new PreferredSize( 52 | preferredSize: const Size.fromHeight(1.0), 53 | child: isLoadind 54 | ? new LinearProgressIndicator( 55 | backgroundColor: Color(0xFFE57373), 56 | valueColor: AlwaysStoppedAnimation(Color(0xFFD32F2F))) 57 | : new Divider( 58 | height: 1.0, 59 | color: Theme.of(context).primaryColor, 60 | )), 61 | ), 62 | withZoom: false, 63 | withLocalStorage: true, 64 | withJavascript: true, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /flutter_gank.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /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 | throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.") 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.") 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.fluttergank" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'androidx.test:runner:1.1.0' 60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 61 | } 62 | -------------------------------------------------------------------------------- /lib/common/WelfareBuildRows.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/model/CategoryResponse.dart'; 2 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 3 | import 'package:GankFlutter/welfare/PhotoView.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | // ignore: non_constant_identifier_names 9 | Widget BuildWelfareRows(context, PostData postData) { 10 | final ThemeData theme = Theme.of(context); 11 | final TextStyle titleStyle = 12 | theme.textTheme.headline5.copyWith(color: Colors.white); 13 | 14 | return new InkWell( 15 | onTap: () => { 16 | routePagerNavigator(context, 17 | new PhotoView(item: new BannerData(null, "美图", postData.url))) 18 | }, 19 | child: new Card( 20 | margin: new EdgeInsets.all(2.0), 21 | child: new Padding( 22 | padding: new EdgeInsets.all(8.0), 23 | child: new SizedBox( 24 | height: 300.0, 25 | child: new Stack( 26 | children: [ 27 | Positioned.fill( 28 | // child: FadeInImage.assetNetwork( 29 | // placeholder: 'images/bg.jpg', 30 | // image: postData.url, 31 | // fit: BoxFit.cover), 32 | child: new CachedNetworkImage( 33 | placeholder: (context, url) => 34 | CupertinoActivityIndicator(), 35 | imageUrl: postData.url, 36 | fit: BoxFit.fitWidth, 37 | ), 38 | ), 39 | Positioned( 40 | bottom: 6.0, 41 | left: 6.0, 42 | right: 6.0, 43 | child: new FittedBox( 44 | fit: BoxFit.scaleDown, 45 | alignment: Alignment.bottomLeft, 46 | child: new Text( 47 | postData.desc, 48 | style: titleStyle, 49 | ), 50 | ), 51 | ), 52 | ], 53 | ), 54 | )), 55 | )); 56 | } 57 | -------------------------------------------------------------------------------- /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 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 31 | # referring to absolute paths on developers' machines. 32 | system('rm -rf .symlinks') 33 | system('mkdir -p .symlinks/plugins') 34 | 35 | # Flutter Pods 36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 37 | if generated_xcode_build_settings.empty? 38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 39 | end 40 | generated_xcode_build_settings.map { |p| 41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 42 | symlink = File.join('.symlinks', 'flutter') 43 | File.symlink(File.dirname(p[:path]), symlink) 44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 45 | end 46 | } 47 | 48 | # Plugin Pods 49 | plugin_pods = parse_KV_file('../.flutter-plugins') 50 | plugin_pods.map { |p| 51 | symlink = File.join('.symlinks', 'plugins', p[:name]) 52 | File.symlink(p[:path], symlink) 53 | pod p[:name], :path => File.join(symlink, 'ios') 54 | } 55 | end 56 | 57 | post_install do |installer| 58 | installer.pods_project.targets.each do |target| 59 | target.build_configurations.each do |config| 60 | config.build_settings['ENABLE_BITCODE'] = 'NO' 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/pages/detail/DetailListView.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/Constant.dart'; 2 | import 'package:GankFlutter/model/CategoryResponse.dart'; 3 | import 'package:GankFlutter/pages/history/WidgetHistoryList.dart'; 4 | import 'package:GankFlutter/pages/history/WidgetHistoryTitle.dart'; 5 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 6 | import 'package:GankFlutter/welfare/PhotoView.dart'; 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | Widget buildListViewBuilder(context, List results) { 12 | print(results); 13 | switch (results.length) { 14 | case 1: 15 | return new Center( 16 | child: new Card( 17 | elevation: 16.0, 18 | child: new Text("暂无数据"), 19 | ), 20 | ); 21 | default: 22 | return new ListView.builder( 23 | physics: const AlwaysScrollableScrollPhysics(), 24 | padding: const EdgeInsets.all(2.0), 25 | // controller: _controller, 26 | itemCount: results == null ? 0 : results.length, 27 | itemBuilder: (context, i) { 28 | return buildRow(context, results[i]); 29 | }, 30 | ); 31 | } 32 | } 33 | 34 | Widget buildHistoryListView(BuildContext context, GankPost postData) { 35 | return new ListView.builder( 36 | physics: const AlwaysScrollableScrollPhysics(), 37 | padding: const EdgeInsets.all(2.0), 38 | // controller: _controller, 39 | itemCount: postData.gankItems == null ? 0 : postData.gankItems.length + 1, 40 | itemBuilder: (context, i) { 41 | if (i == 0) { 42 | return _buildImageBanner(context, postData); 43 | } else { 44 | PostData pd = postData.gankItems[i - 1]; 45 | return pd.isTitle 46 | ? WidgetHistoryTitle(pd.category) 47 | : WidgetHistoryList(pd); 48 | } 49 | }, 50 | ); 51 | } 52 | 53 | GestureDetector _buildImageBanner(BuildContext context, GankPost postData) { 54 | return GestureDetector( 55 | onTap: () { 56 | BannerData p = new BannerData(null, "美图", postData.girlImage); 57 | routePagerNavigator(context, new PhotoView(item: p)); 58 | }, 59 | child: CachedNetworkImage( 60 | height: 200, 61 | imageUrl: postData.girlImage, 62 | fit: BoxFit.cover, 63 | ), 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /lib/utils/PageRouteUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/pages/detail/ArticleDetailPage.dart'; 2 | import 'package:GankFlutter/pages/detail/DetailPage.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | routePageBuilder(BuildContext context, String title) { 6 | Navigator.of(context).push(new PageRouteBuilder( 7 | opaque: false, 8 | pageBuilder: (BuildContext context, _, __) { 9 | return new DetailPage( 10 | feedType: title, 11 | showTitle: true, 12 | ); 13 | }, 14 | transitionsBuilder: (_, Animation animation, __, Widget child) { 15 | return new FadeTransition( 16 | opacity: animation, 17 | child: new FadeTransition( 18 | opacity: new Tween(begin: 0.5, end: 1.0).animate(animation), 19 | child: child, 20 | ), 21 | ); 22 | })); 23 | } 24 | 25 | routeWebView(BuildContext context, String title, String url) { 26 | // Navigator.of(context).push(new MaterialPageRoute(builder: (context) { 27 | // return new ArticleDetailPage(title: title, url: url); 28 | // },)); 29 | 30 | Navigator.of(context).push(new PageRouteBuilder( 31 | opaque: false, 32 | pageBuilder: (BuildContext context, _, __) { 33 | return new ArticleDetailPage(title: title, url: url); 34 | }, 35 | transitionsBuilder: (_, Animation animation, __, Widget child) { 36 | return new FadeTransition( 37 | opacity: animation, 38 | child: new FadeTransition( 39 | opacity: new Tween(begin: 0.5, end: 1.0).animate(animation), 40 | child: child, 41 | ), 42 | ); 43 | })); 44 | } 45 | 46 | routePagerNavigator(BuildContext context, Widget v) { 47 | // Navigator.push(context, new MaterialPageRoute(builder: (context) => v)); 48 | 49 | Navigator.of(context).push(new PageRouteBuilder( 50 | opaque: false, 51 | pageBuilder: (BuildContext context, _, __) { 52 | return v; 53 | }, 54 | transitionsBuilder: (_, Animation animation, __, Widget child) { 55 | return new FadeTransition( 56 | opacity: animation, 57 | child: new FadeTransition( 58 | opacity: new Tween(begin: 0.5, end: 1.0).animate(animation), 59 | child: child, 60 | ), 61 | ); 62 | })); 63 | } 64 | -------------------------------------------------------------------------------- /lib/pages/index/IndexDrawPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/common/GlobalConfig.dart'; 3 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 4 | import 'package:GankFlutter/pages/search/SearchPage.dart'; 5 | import 'package:GankFlutter/pages/history/HistoryPage.dart'; 6 | 7 | ///侧滑栏 8 | // ignore: must_be_immutable 9 | class IndexDrawPage extends StatelessWidget { 10 | String email, name, profileimg, background; 11 | 12 | IndexDrawPage({this.background, this.profileimg, this.name, this.email}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return new ListView( 17 | children: [ 18 | new UserAccountsDrawerHeader( 19 | accountName: new Text(name), 20 | accountEmail: new Text(email), 21 | currentAccountPicture: 22 | new CircleAvatar(backgroundImage: new AssetImage(profileimg)), 23 | decoration: new BoxDecoration( 24 | image: new DecorationImage( 25 | image: new AssetImage(background), fit: BoxFit.cover)), 26 | ), 27 | new ListTile( 28 | title: new Text("福利"), 29 | trailing: new Icon( 30 | Icons.whatshot, 31 | color: Colors.red, 32 | ), 33 | onTap: () { 34 | print("Home"); 35 | Navigator.of(context).pop(); 36 | routePageBuilder(context, GlobalConfig.welfare); 37 | }, 38 | ), 39 | new ListTile( 40 | title: new Text("搜索"), 41 | trailing: new Icon( 42 | Icons.search, 43 | color: Colors.red, 44 | ), 45 | onTap: () { 46 | print("Notification"); 47 | Navigator.of(context).pop(); 48 | routePagerNavigator(context, new SearchPage()); 49 | }, 50 | ), 51 | new ListTile( 52 | title: new Text("历史"), 53 | trailing: new Icon( 54 | Icons.history, 55 | color: Colors.red, 56 | ), 57 | onTap: () { 58 | print("Notification"); 59 | Navigator.of(context).pop(); 60 | routePagerNavigator(context, new HistoryPage()); 61 | }, 62 | ), 63 | new ListTile( 64 | title: new Text("Exit"), 65 | trailing: new Icon(Icons.exit_to_app), 66 | onTap: () { 67 | print("Exit"); 68 | Navigator.of(context).pop(); 69 | }, 70 | ) 71 | ], 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/common/GlobalConfig.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GlobalConfig { 4 | ///颜色值 5 | static Color colorPrimary = Colors.red; 6 | static Color searchBackgroundColor = new Color(0xFFEBEBEB); 7 | static Color cardBackgroundColor = Colors.white; 8 | static Color fontColor = Colors.black54; 9 | static Color backgroundColor = new Color(0xFFEBEBEB); 10 | 11 | ///URL链接 12 | static String wxGithubUrl = "https://github.com/ZQ330093887/GankWX"; 13 | static String flutterGithubUrl = "https://github.com/ZQ330093887/GankFlutter"; 14 | static String iosGithubUrl = "https://github.com/ZQ330093887/GankIOSProgect"; 15 | static String androidGithubUrl = 16 | "https://github.com/ZQ330093887/ConurbationsAndroid"; 17 | static String aboutAuthorURl = "https://www.jianshu.com/u/9681f3bbb8c2"; 18 | static String thanksEditURl = "https://gank.io/backbone"; 19 | 20 | ///导航 21 | static String homeTab = "首页"; 22 | static String classyTab = "分类"; 23 | static String mineTab = "我的"; 24 | 25 | ///侧边栏 26 | static String email = "330093887@qq.com"; 27 | static String authorNice = "Array 魔君"; 28 | 29 | ///登录 30 | static String loginTitle = "用GitHub登录"; 31 | static String loginSubTitle = "登录后可提交干货"; 32 | static String flutterVersion = "Flutter版"; 33 | static String wxVersion = "小程序版"; 34 | static String androidVersion = "Android版"; 35 | static String iosVersion = "iOS版"; 36 | static String android = "Android"; 37 | static String all = "all"; 38 | static String iOS = "iOS"; 39 | static String h5 = "前端"; 40 | static String push = "瞎推荐"; 41 | static String resource = "拓展资源"; 42 | static String welfare = "Girl"; 43 | static String app = "App"; 44 | static String video = "休息视频"; 45 | 46 | ///推送 47 | static String pushTitle = "干货推荐"; 48 | static String thinkTitle = "感谢编辑"; 49 | static String updateTitle = "版本更新"; 50 | static String aboutTitle = "关于作者"; 51 | static String pushSubTitle = "有新的干货会第一时间推送给你"; 52 | static String pushInfo = 53 | "干货推送是利用系统特性解决第三方干货集中营客户端无法拥有原生推送的方案,你可以在这里选择开启或者关闭。你的 iOS 设备可以根据你使用干货集中营的频率和时间智能的安排干货集中营来获取每日更新的干货,并显示在应用图标上。你需要确保以下设置均为正确状态:"; 54 | static String pushNext1 = "1.【设置】-【干货集中营】-【通知】权限为允许状态;"; 55 | static String pushNext2 = "2. 【设置】-【干货集中营】-【后台应用刷新】权限为允许状态;"; 56 | static String pushNext3 = "3. 如果上述权限都为允许状态,开启干货推送,enjoy it !"; 57 | 58 | ///登录 59 | static String githubLogin = "Github 账号登录"; 60 | static String pwdLogin = "密码登录"; 61 | static String loginSubView = "登 录"; 62 | static String pwd = "密码"; 63 | static String nice = "账号"; 64 | static String inputCode = "请输入短信验证码"; 65 | static String inputNice = "请输入用户名/邮箱地址"; 66 | } 67 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/pages/history/HistoryDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:GankFlutter/api/Api.dart'; 4 | import 'package:GankFlutter/api/http.dart'; 5 | import 'package:GankFlutter/common/Constant.dart'; 6 | import 'package:GankFlutter/model/CategoryResponse.dart'; 7 | import 'package:GankFlutter/pages/detail/DetailListView.dart'; 8 | import 'package:GankFlutter/utils/IndicatorUtils.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 12 | 13 | // ignore: must_be_immutable 14 | class HistoryDetailPage extends StatefulWidget { 15 | String data; 16 | 17 | HistoryDetailPage({Key key, this.data}) : super(key: key); 18 | 19 | @override 20 | State createState() => _HistoryDetailState(); 21 | } 22 | 23 | class _HistoryDetailState extends State 24 | with HttpExt, IndicatorFactory { 25 | GankPost listData; 26 | var requestError = false; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | _loadingData(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | Widget detailBuild(BuildContext context) { 37 | return requestError 38 | ? buildExceptionIndicator("网络请求出错了!") 39 | : listData == null 40 | ? new Center( 41 | child: new CupertinoActivityIndicator(), 42 | ) 43 | : new EasyRefresh( 44 | autoLoad: true, 45 | key: easyRefreshKey, 46 | refreshHeader: buildDefaultHeader(), 47 | // ignore: missing_return 48 | onRefresh: () { 49 | _pullToRefresh(); 50 | }, 51 | child: buildHistoryListView(context, listData)); 52 | } 53 | 54 | return new Scaffold( 55 | appBar: new AppBar( 56 | title: new Text(widget.data.replaceAll("/", "-")), 57 | ), 58 | body: detailBuild(context), 59 | ); 60 | } 61 | 62 | void _loadingData() async { 63 | String url = getUrl(); 64 | await getGankDayData(url).then((GankPost dailyResponse) { 65 | print(dailyResponse); 66 | setState(() { 67 | requestError = false; 68 | listData = dailyResponse; 69 | }); 70 | }).catchError((error) { 71 | setState(() { 72 | requestError = true; 73 | }); 74 | }).whenComplete(() { 75 | if (requestError) return; 76 | easyRefreshKey.currentState.callRefreshFinish(); 77 | }); 78 | } 79 | 80 | //刷新 81 | Future _pullToRefresh() async { 82 | _loadingData(); 83 | return null; 84 | } 85 | 86 | String getUrl() { 87 | var url = Api.DATA_DAY_URL + widget.data; 88 | print("Url==>: $url"); 89 | return url; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /lib/pages/mine/videoCard.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/GlobalConfig.dart'; 2 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:GankFlutter/pages/push/PushPage.dart'; 5 | import 'package:GankFlutter/pages/update/UpdatePage.dart'; 6 | 7 | Widget videoCard(BuildContext context) { 8 | return new Card( 9 | child: new Container( 10 | color: GlobalConfig.cardBackgroundColor, 11 | margin: const EdgeInsets.only(top: 6.0, bottom: 6.0), 12 | padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), 13 | child: new Column( 14 | children: [ 15 | new ListTile( 16 | leading: const Icon(Icons.score, color: Colors.red), 17 | title: const Text('干货推荐'), 18 | trailing: Icon( 19 | Icons.arrow_forward, 20 | color: Colors.red, 21 | size: 16, 22 | ), 23 | onTap: () { 24 | routePagerNavigator(context, new PushPage()); 25 | }), 26 | // new Container( 27 | // height: 1, 28 | // margin: const EdgeInsets.only(left: 20), 29 | // width: MediaQuery.of(context).size.width, 30 | // decoration: new BoxDecoration( 31 | // border: new BorderDirectional( 32 | // start: new BorderSide( 33 | // color: Colors.red, 34 | // width: MediaQuery.of(context).size.width - 50))), 35 | // ), 36 | new ListTile( 37 | leading: const Icon(Icons.thumbs_up_down, color: Colors.amber), 38 | title: const Text('感谢编辑'), 39 | trailing: Icon( 40 | Icons.arrow_forward, 41 | color: Colors.amber, 42 | size: 16, 43 | ), 44 | onTap: () { 45 | routeWebView(context, GlobalConfig.thinkTitle, 46 | GlobalConfig.thanksEditURl); 47 | }), 48 | new ListTile( 49 | leading: const Icon(Icons.update, color: Colors.deepPurpleAccent), 50 | title: const Text('版本更新'), 51 | trailing: Icon( 52 | Icons.arrow_forward, 53 | color: Colors.deepPurpleAccent, 54 | size: 16, 55 | ), 56 | onTap: () { 57 | routePagerNavigator(context, new UpdatePage()); 58 | }), 59 | new ListTile( 60 | leading: const Icon( 61 | Icons.people, 62 | color: Colors.teal, 63 | ), 64 | title: const Text('关于作者'), 65 | trailing: Icon( 66 | Icons.arrow_forward, 67 | color: Colors.teal, 68 | size: 16, 69 | ), 70 | onTap: () { 71 | routeWebView(context, GlobalConfig.aboutTitle, 72 | GlobalConfig.aboutAuthorURl); 73 | }) 74 | ], 75 | ), 76 | ), 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: GankFlutter 2 | description: 干货集中营 客户端 Flutter版. 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 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | # The following adds the Cupertino Icons font to your application. 17 | # Use with the CupertinoIcons class for iOS style icons. 18 | cupertino_icons: ^0.1.2 19 | english_words: ^3.1.0 20 | flutter_webview_plugin: ^0.3.11 21 | url_launcher: ^3.0.2 22 | transparent_image: ^0.1.0 23 | flutter_easyrefresh: ^1.2.7 24 | cached_network_image: ^2.2.0+1 25 | shared_preferences: ^0.4.2 26 | date_utils: ^0.1.0 27 | intl: ^0.15.7 28 | carousel_slider: ^1.1.0 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://www.dartlang.org/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | 41 | # The following line ensures that the Material Icons font is 42 | # included with your application, so that you can use the icons in 43 | # the material Icons class. 44 | uses-material-design: true 45 | 46 | # To add assets to your application, add an assets section, like this: 47 | # assets: 48 | # - images/a_dot_burr.jpeg 49 | # - images/a_dot_ham.jpeg 50 | assets: 51 | - images/home_green.png 52 | - images/icon_home.png 53 | - images/profile.png 54 | - images/bg.jpg 55 | - images/bg_2.jpg 56 | - images/profile_2.jpg 57 | - images/icon_circle.png 58 | - images/icon_msg.png 59 | - images/icon_sina.png 60 | - images/icon_wx.png 61 | - images/logo.png 62 | - images/smile.png 63 | - images/empty_data.png 64 | - images/loading.png 65 | # An image asset can refer to one or more resolution-specific "variants", see 66 | # https://flutter.io/assets-and-images/#resolution-aware. 67 | 68 | # For details regarding adding assets from package dependencies, see 69 | # https://flutter.io/assets-and-images/#from-packages 70 | 71 | # To add custom fonts to your application, add a fonts section here, 72 | # in this "flutter" section. Each entry in this list should have a 73 | # "family" key with the font family name, and a "fonts" key with a 74 | # list giving the asset and other descriptors for the font. For 75 | # example: 76 | # fonts: 77 | # - family: Schyler 78 | # fonts: 79 | # - asset: fonts/Schyler-Regular.ttf 80 | # - asset: fonts/Schyler-Italic.ttf 81 | # style: italic 82 | # - family: Trajan Pro 83 | # fonts: 84 | # - asset: fonts/TrajanPro.ttf 85 | # - asset: fonts/TrajanPro_Bold.ttf 86 | # weight: 700 87 | # 88 | # For details regarding fonts from package dependencies, 89 | # see https://flutter.io/custom-fonts/#from-packages 90 | -------------------------------------------------------------------------------- /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/pages/push/PushPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:GankFlutter/common/GlobalConfig.dart'; 4 | 5 | class PushPage extends StatefulWidget { 6 | @override 7 | State createState() { 8 | return new _PushPageState(); 9 | } 10 | } 11 | 12 | class _PushPageState extends State { 13 | bool _lights = true; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | // TODO: implement build 18 | return new Scaffold( 19 | appBar: new AppBar( 20 | title: new Text(GlobalConfig.pushTitle), 21 | ), 22 | body: new Column( 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | new Row(children: [ 26 | new Expanded( 27 | child: new Column( 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | new Container( 31 | padding: EdgeInsets.fromLTRB(22.0, 24.0, 0.0, 0.0), 32 | child: new Text(GlobalConfig.pushTitle, 33 | style: new TextStyle( 34 | fontWeight: FontWeight.bold, 35 | ))), 36 | new Container( 37 | padding: EdgeInsets.fromLTRB(22.0, 6.0, 0.0, 10.0), 38 | child: new Text(GlobalConfig.pushSubTitle, 39 | style: new TextStyle( 40 | color: Colors.grey[500], fontSize: 12.0)), 41 | ), 42 | ])), 43 | new Container( 44 | padding: EdgeInsets.only(right: 22.0), 45 | child: new CupertinoSwitch( 46 | value: _lights, 47 | activeColor: GlobalConfig.colorPrimary, 48 | onChanged: (bool value) { 49 | setState(() { 50 | _lights = value; 51 | // print(value); 52 | }); 53 | }, 54 | ), 55 | ) 56 | ]), 57 | new Container( 58 | padding: EdgeInsets.fromLTRB(22.0, 40.0, 22.0, 10.0), 59 | child: new Text( 60 | GlobalConfig.pushInfo, 61 | style: new TextStyle(fontSize: 14.0), 62 | )), 63 | new Container( 64 | padding: EdgeInsets.fromLTRB(22.0, 10.0, 22.0, 5.0), 65 | child: new Text( 66 | GlobalConfig.pushNext1, 67 | style: new TextStyle(fontSize: 12.0), 68 | )), 69 | new Container( 70 | padding: EdgeInsets.fromLTRB(22.0, 10.0, 22.0, 5.0), 71 | child: new Text( 72 | GlobalConfig.pushNext2, 73 | style: new TextStyle(fontSize: 12.0), 74 | )), 75 | new Container( 76 | padding: EdgeInsets.fromLTRB(22.0, 10.0, 22.0, 5.0), 77 | child: new Text( 78 | GlobalConfig.pushNext3, 79 | style: new TextStyle(fontSize: 12.0), 80 | )), 81 | ], 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/common/WebViewPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 6 | 7 | /// 网页展示界面 8 | class WebViewPage extends StatefulWidget { 9 | final Map post; 10 | 11 | WebViewPage(this.post); 12 | 13 | @override 14 | State createState() => new _WebViewState(); 15 | } 16 | 17 | class _WebViewState extends State { 18 | GlobalKey _scaffoldKey = new GlobalKey(); 19 | 20 | bool isLoaded = false; 21 | 22 | // Instance of WebView plugin 23 | final flutterWebviewPlugin = new FlutterWebviewPlugin(); 24 | 25 | StreamSubscription _onStateChanged; 26 | 27 | void showSnack(String msg) { 28 | _scaffoldKey.currentState 29 | .showSnackBar(new SnackBar(content: new Text(msg))); 30 | } 31 | 32 | bool loading = false; 33 | 34 | @override 35 | initState() { 36 | super.initState(); 37 | 38 | flutterWebviewPlugin.close(); 39 | 40 | _onStateChanged = 41 | flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) { 42 | print("state: ${state.type}"); 43 | if (state.type == WebViewState.finishLoad) { 44 | // 加载完成 45 | setState(() { 46 | isLoaded = true; 47 | }); 48 | } 49 | }); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | // Every listener should be canceled, the same should be done with this stream. 55 | 56 | _onStateChanged.cancel(); 57 | flutterWebviewPlugin.dispose(); 58 | 59 | super.dispose(); 60 | } 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | print(widget.post['url']); 65 | String title = 66 | widget.post['desc'] == null ? '一个陌生的地方' : widget.post['desc']; 67 | 68 | List titleContent = []; 69 | if (!isLoaded) { 70 | titleContent.add(new CupertinoActivityIndicator()); 71 | } 72 | titleContent.add(new Expanded( 73 | child: new Text(title, 74 | overflow: TextOverflow.ellipsis, 75 | style: new TextStyle(fontSize: 16.0)))); 76 | 77 | return new WebviewScaffold( 78 | key: _scaffoldKey, 79 | url: widget.post['url'], 80 | withZoom: true, 81 | withLocalStorage: true, 82 | withJavascript: true, 83 | appBar: new AppBar( 84 | title: new Row( 85 | mainAxisAlignment: MainAxisAlignment.center, 86 | children: titleContent, 87 | ), 88 | //title: new Text(title), 89 | actions: [ 90 | new Padding( 91 | padding: const EdgeInsets.all(4.0), 92 | child: new IconButton( 93 | icon: new Icon(Icons.refresh), 94 | onPressed: () {}, 95 | ), 96 | ), 97 | new Padding( 98 | padding: const EdgeInsets.all(4.0), 99 | child: new IconButton( 100 | icon: new Icon(Icons.share), 101 | onPressed: () {}, 102 | ), 103 | ), 104 | ], 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/pages/home/HomePage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:GankFlutter/api/Api.dart'; 5 | import 'package:GankFlutter/api/http.dart'; 6 | import 'package:GankFlutter/model/BannerEntity.dart'; 7 | import 'package:GankFlutter/model/CategoryResponse.dart'; 8 | import 'package:GankFlutter/utils/SharedPrfUtils.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | import 'HomeListView.dart'; 13 | 14 | // ignore: must_be_immutable 15 | class HomePage extends StatefulWidget { 16 | String dataDay; 17 | 18 | HomePage({Key key, this.dataDay}) : super(key: key); 19 | 20 | _NewsListState createState() => new _NewsListState(); 21 | } 22 | 23 | class _NewsListState extends State with HttpExt { 24 | List listData; 25 | var requestError = false; 26 | 27 | Future _pullToRefresh(var list) async { 28 | await getGankfromNet(Api.TODAY_URL) 29 | .then((CategoryResponse categoryResponse) { 30 | if (categoryResponse.status == 100) { 31 | List _listData = categoryResponse.data; 32 | setState(() { 33 | ///缓存网络请求的数据 34 | //目前只缓存第一页数据 35 | SharedPrfUtils.saveString( 36 | Api.TODAY_URL, json.encode(categoryResponse.toJson())); 37 | 38 | BannerEntity bannerEntity = new BannerEntity(); 39 | bannerEntity.banner = new List(); 40 | bannerEntity.banner.addAll(list); 41 | _listData.insert(0, bannerEntity); 42 | listData = _listData; 43 | //目前只缓存第一页数据 44 | }); 45 | } 46 | }).catchError((error) { 47 | setState(() { 48 | requestError = true; 49 | listData = new List(); 50 | }); 51 | }).whenComplete(() {}); 52 | } 53 | 54 | @override 55 | void initState() { 56 | super.initState(); 57 | getBannerData(); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return new RefreshIndicator( 63 | child: buildDailyListView(context, listData, requestError), 64 | onRefresh: getBannerData, 65 | ); 66 | } 67 | 68 | ///请求首页item数据 69 | void loadingData(var list) async { 70 | var cacheData = await SharedPrfUtils.get(Api.TODAY_URL); 71 | if (cacheData != null) { 72 | var userMap = json.decode(cacheData); 73 | CategoryResponse categoryResponse = CategoryResponse.fromJson(userMap); 74 | print("----首页走缓存1---"); 75 | List _listData = categoryResponse.data; 76 | setState(() { 77 | BannerEntity bannerEntity = new BannerEntity(); 78 | bannerEntity.banner = new List(); 79 | bannerEntity.banner.addAll(list); 80 | _listData.insert(0, bannerEntity); 81 | listData = _listData; 82 | }); 83 | } else { 84 | print("开始网络请求"); 85 | _pullToRefresh(list); 86 | } 87 | } 88 | 89 | Future getBannerData() async { 90 | await getGankfromNet(Api.BANNER).then((CategoryResponse categoryResponse) { 91 | if (categoryResponse.status == 100) { 92 | loadingData(categoryResponse.data); 93 | } 94 | }).catchError((error) { 95 | setState(() { 96 | requestError = true; 97 | }); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/pages/Application.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/common/GlobalConfig.dart'; 3 | import 'index/IndexDrawPage.dart'; 4 | 5 | import 'home/HomePage.dart'; 6 | import 'classify/ClassifyPage.dart'; 7 | import 'mine/MinePage.dart'; 8 | 9 | class ApplicationPage extends StatefulWidget { 10 | @override 11 | State createState() => _ApplicationPageState(); 12 | } 13 | 14 | class _ApplicationPageState extends State 15 | with SingleTickerProviderStateMixin { 16 | int page = 0; 17 | String title = GlobalConfig.homeTab; 18 | PageController pageController; 19 | 20 | //定义底部导航项目 21 | final List _bottomTabs = [ 22 | new BottomNavigationBarItem( 23 | icon: Icon(Icons.home), 24 | title: Text(GlobalConfig.homeTab), 25 | backgroundColor: GlobalConfig.colorPrimary), 26 | new BottomNavigationBarItem( 27 | icon: Icon(Icons.tune), 28 | title: Text(GlobalConfig.classyTab), 29 | backgroundColor: GlobalConfig.colorPrimary), 30 | new BottomNavigationBarItem( 31 | icon: Icon(Icons.person), 32 | title: Text(GlobalConfig.mineTab), 33 | backgroundColor: GlobalConfig.colorPrimary), 34 | ]; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | pageController = new PageController(initialPage: this.page); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | pageController.dispose(); 45 | super.dispose(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | // TODO: implement build 51 | return MaterialApp( 52 | theme: new ThemeData(primaryColor: GlobalConfig.colorPrimary), 53 | home: Scaffold( 54 | appBar: new AppBar( 55 | title: new Text(title), 56 | ), 57 | drawer: new Drawer( 58 | child: new IndexDrawPage( 59 | email: GlobalConfig.email, 60 | name: GlobalConfig.authorNice, 61 | profileimg: "images/profile_2.jpg", 62 | background: "images/bg_2.jpg", 63 | ), 64 | ), 65 | body: new PageView( 66 | physics: NeverScrollableScrollPhysics(), 67 | children: [HomePage(), ClassifyPage(), MinePage()], 68 | controller: pageController, 69 | onPageChanged: onPageChanged, 70 | ), 71 | bottomNavigationBar: new BottomNavigationBar( 72 | items: _bottomTabs, 73 | currentIndex: page, 74 | fixedColor: GlobalConfig.colorPrimary, 75 | type: BottomNavigationBarType.fixed, 76 | onTap: onTap), 77 | ), 78 | ); 79 | } 80 | 81 | void onTap(int index) { 82 | pageController.animateToPage(index, 83 | duration: const Duration(milliseconds: 300), curve: Curves.ease); 84 | } 85 | 86 | void onPageChanged(int page) { 87 | setState(() { 88 | this.page = page; 89 | switch (page) { 90 | case 0: 91 | title = GlobalConfig.homeTab; 92 | break; 93 | case 1: 94 | title = GlobalConfig.classyTab; 95 | break; 96 | case 2: 97 | title = GlobalConfig.mineTab; 98 | break; 99 | } 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /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/widget/NewsBannerView.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/GlobalConfig.dart'; 2 | import 'package:GankFlutter/model/CategoryResponse.dart'; 3 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 4 | import 'package:GankFlutter/utils/ScreenUtils.dart'; 5 | import 'package:cached_network_image/cached_network_image.dart'; 6 | import 'package:carousel_slider/carousel_slider.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | class NewsBannerView extends StatelessWidget { 10 | final List banners; 11 | 12 | NewsBannerView(this.banners); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | color: Colors.white, 18 | child: CarouselSlider( 19 | items: banners.map((banner) { 20 | return Builder( 21 | builder: (BuildContext context) { 22 | return GestureDetector( 23 | onTap: () { 24 | routeWebView(context, banner.title, banner.url); 25 | }, 26 | child: Container( 27 | width: Screen.width, 28 | margin: 29 | EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0), 30 | child: Stack( 31 | children: [ 32 | Container( 33 | width: Screen.width, 34 | decoration: BoxDecoration( 35 | image: DecorationImage( 36 | image: CachedNetworkImageProvider(banner.image), 37 | fit: BoxFit.cover, 38 | ), 39 | borderRadius: 40 | BorderRadius.all(Radius.circular(3))), 41 | ), 42 | Opacity( 43 | opacity: 0.5, 44 | child: Container( 45 | width: Screen.width, 46 | decoration: BoxDecoration( 47 | color: Colors.black12, 48 | borderRadius: 49 | BorderRadius.all(Radius.circular(3))), 50 | ), 51 | ), 52 | Container( 53 | padding: EdgeInsets.all(20), 54 | width: Screen.width, 55 | child: Column( 56 | mainAxisAlignment: MainAxisAlignment.end, 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Text( 60 | banner.title, 61 | style: TextStyle( 62 | color: GlobalConfig.cardBackgroundColor, 63 | fontSize: 16, 64 | fontWeight: FontWeight.bold, 65 | ), 66 | ), 67 | Text(banner.title, 68 | overflow: TextOverflow.ellipsis, 69 | maxLines: 2, 70 | style: TextStyle( 71 | color: GlobalConfig.cardBackgroundColor, 72 | )), 73 | ], 74 | ), 75 | ), 76 | ], 77 | )), 78 | ); 79 | }, 80 | ); 81 | }).toList(), 82 | aspectRatio: 2, 83 | autoPlay: true, 84 | autoPlayInterval: const Duration(seconds: 5), 85 | enlargeCenterPage: true, 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /GankFlutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/welfare/PhotoView.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/model/CategoryResponse.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | const double _kMinFlingVelocity = 800.0; 6 | 7 | class PhotoView extends StatefulWidget { 8 | const PhotoView({Key key, this.item}) : super(key: key); 9 | 10 | final BannerData item; 11 | 12 | @override 13 | _PhotoViewState createState() => _PhotoViewState(); 14 | } 15 | 16 | class _PhotoViewState extends State 17 | with SingleTickerProviderStateMixin { 18 | AnimationController _controller; 19 | Animation _flingAnimation; 20 | Offset _offset = Offset.zero; 21 | double _scale = 1.0; 22 | Offset _normalizedOffset; 23 | double _previousScale; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _controller = new AnimationController(vsync: this) 29 | ..addListener(_handleFlingAnimation); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | _controller.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | // The maximum offset value is 0,0. If the size of this renderer's box is w,h 39 | // then the minimum offset value is w - _scale * w, h - _scale * h. 40 | Offset _clampOffset(Offset offset) { 41 | final Size size = context.size; 42 | final Offset minOffset = 43 | new Offset(size.width, size.height) * (1.0 - _scale); 44 | return new Offset( 45 | offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0)); 46 | } 47 | 48 | void _handleFlingAnimation() { 49 | setState(() { 50 | _offset = _flingAnimation.value; 51 | }); 52 | } 53 | 54 | void _handleOnScaleStart(ScaleStartDetails details) { 55 | setState(() { 56 | _previousScale = _scale; 57 | _normalizedOffset = (details.focalPoint - _offset) / _scale; 58 | // The fling animation stops if an input gesture starts. 59 | _controller.stop(); 60 | }); 61 | } 62 | 63 | void _handleOnScaleUpdate(ScaleUpdateDetails details) { 64 | setState(() { 65 | _scale = (_previousScale * details.scale).clamp(1.0, 4.0); 66 | // Ensure that image location under the focal point stays in the same place despite scaling. 67 | _offset = _clampOffset(details.focalPoint - _normalizedOffset * _scale); 68 | }); 69 | } 70 | 71 | void _handleOnScaleEnd(ScaleEndDetails details) { 72 | final double magnitude = details.velocity.pixelsPerSecond.distance; 73 | if (magnitude < _kMinFlingVelocity) return; 74 | final Offset direction = details.velocity.pixelsPerSecond / magnitude; 75 | final double distance = (Offset.zero & context.size).shortestSide; 76 | _flingAnimation = new Tween( 77 | begin: _offset, end: _clampOffset(_offset + direction * distance)) 78 | .animate(_controller); 79 | _controller 80 | ..value = 0.0 81 | ..fling(velocity: magnitude / 1000.0); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return Scaffold( 87 | appBar: AppBar( 88 | title: Text(widget.item.title), 89 | ), 90 | body: GestureDetector( 91 | onTap: () { 92 | ///添加单击关闭 93 | Navigator.of(context).pop(); 94 | }, 95 | onScaleStart: _handleOnScaleStart, 96 | onScaleUpdate: _handleOnScaleUpdate, 97 | onScaleEnd: _handleOnScaleEnd, 98 | child: ClipRect( 99 | child: Transform( 100 | transform: Matrix4.identity() 101 | ..translate(_offset.dx, _offset.dy) 102 | ..scale(_scale), 103 | child: new Stack( 104 | children: [ 105 | Center(child: CupertinoActivityIndicator()), 106 | Positioned.fill( 107 | child: Image.network( 108 | widget.item.url, 109 | fit: BoxFit.cover, 110 | ), 111 | ), 112 | ], 113 | ), 114 | ), 115 | ), 116 | )); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/model/CategoryResponse.dart: -------------------------------------------------------------------------------- 1 | class CategoryResponse { 2 | int status; 3 | List data; 4 | 5 | CategoryResponse(this.status, this.data); 6 | 7 | CategoryResponse.fromJson(Map json) { 8 | this.status = json['status']; 9 | this.data = json['data']; 10 | } 11 | 12 | Map toJson() => { 13 | 'status': status, 14 | 'data': data, 15 | }; 16 | } 17 | 18 | class HistoryResponse { 19 | bool error; 20 | List results; 21 | 22 | HistoryResponse(this.error, this.results); 23 | 24 | HistoryResponse.fromJson(Map json) { 25 | this.error = json['error']; 26 | this.results = json['results']; 27 | } 28 | 29 | Map toJson() => { 30 | 'error': error, 31 | 'results': results, 32 | }; 33 | } 34 | 35 | class PostData { 36 | String _id; 37 | String createdAt; 38 | String desc; 39 | List images; 40 | String publishedAt; 41 | String source; 42 | String type; 43 | String url; 44 | bool used; 45 | String author; 46 | bool isTitle = false; 47 | String category; 48 | int stars; 49 | 50 | PostData(this._id, this.createdAt, this.desc, this.images, this.publishedAt, 51 | this.source, this.type, this.url, this.used, this.author); 52 | 53 | PostData.title(this.isTitle, this.category); 54 | 55 | PostData.addField(Map json, {String category}) { 56 | this.category = category; 57 | setField(json); 58 | } 59 | 60 | void setField(Map json) { 61 | this.createdAt = json['createdAt']; 62 | this.desc = json['desc'] ?? ''; 63 | this.images = 64 | json['images']?.map((image) => image as String)?.toList() ?? []; 65 | this.publishedAt = json['publishedAt']; 66 | this.source = json['source']; 67 | this.type = json['type']; 68 | this.url = json['url']; 69 | this.stars = json['stars']; 70 | this.author = json['author'] ?? "github"; 71 | } 72 | 73 | PostData.fromJson(Map json) { 74 | this._id = json['_id']; 75 | this.createdAt = json['createdAt']; 76 | this.desc = json['desc']; 77 | this.images = json['images']; 78 | this.publishedAt = json['publishedAt']; 79 | this.source = json['source']; 80 | this.type = json['type']; 81 | this.url = json['url']; 82 | this.stars = json['stars']; 83 | this.used = json['used']; 84 | this.author = json['author']; 85 | } 86 | 87 | Map toJson() => { 88 | '_id': _id, 89 | 'createdAt': createdAt, 90 | 'desc': desc, 91 | 'images': images, 92 | 'publishedAt': publishedAt, 93 | 'source': source, 94 | 'type': type, 95 | 'url': url, 96 | 'used': used, 97 | 'author': author, 98 | 'category': category, 99 | }; 100 | } 101 | 102 | class BannerData { 103 | String image; 104 | String title; 105 | String url; 106 | 107 | BannerData(this.image, this.title, this.url); 108 | 109 | BannerData.fromJson(Map json) { 110 | this.image = json['image']; 111 | this.title = json['title']; 112 | this.url = json['url']; 113 | } 114 | } 115 | 116 | class GankPost { 117 | List category; 118 | Map itemDataMap = Map(); 119 | String girlImage; 120 | List gankItems = []; 121 | 122 | GankPost.fromJson(Map json) 123 | : category = 124 | json['category']?.map((c) => c.toString())?.toList() { 125 | var results = json['results']; 126 | results.forEach((name, value) { 127 | if (name != '福利') { 128 | itemDataMap[name] = _createGankItemListFromJson(name, value); 129 | } 130 | }); 131 | girlImage = json['results']['福利'][0]['url']; 132 | } 133 | 134 | List _createGankItemListFromJson(String name, List value) { 135 | var gankItemList = value 136 | .map((item) => PostData.addField(item, category: name)) 137 | .toList(); 138 | gankItems.add(PostData.title(true, name)); 139 | gankItems.addAll(gankItemList); 140 | return gankItemList; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GankFlutter 2 | 3 | 一款追求全新用户体验的干货集中营 flutter 版 客户端 4 | - [GitHub IOS版本](https://github.com/ZQ330093887/GankIOSProgect) 5 | - [GitHub 小程序版本](https://github.com/ZQ330093887/GankWX) 6 | - [GitHub Android版本](https://github.com/ZQ330093887/ConurbationsAndroid) 7 | 8 | ## 应用截图 9 | 10 | | ![1](https://upload-images.jianshu.io/upload_images/3278692-6c1fca7d9b6b58c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![2](https://upload-images.jianshu.io/upload_images/3278692-1499e8ff06ce088c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![3](https://upload-images.jianshu.io/upload_images/3278692-75ccb43d0a1eec4e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![4](https://upload-images.jianshu.io/upload_images/3278692-371765e22a5694db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![5](https://upload-images.jianshu.io/upload_images/3278692-4959af09e381bb7e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | 11 | | :--: | :--: | :--: | :--: | :--: | 12 | | 每日干货 | 分类干货 | 个人中心 | 福利社区 | 历史车轮| 13 | ### 编译运行流程 14 | 15 | 1、配置好Flutter开发环境(目前Flutter SDK 版本 v0.5.8 的 Tag ),可参阅 [【搭建环境】](https://flutterchina.club)。 16 | 17 | 2、clone代码,执行`Packages get`安装第三方包。(因为某些不可抗力原因,国内可能需要设置代理: 18 | [代理环境变量](https://flutterchina.club/setup-windows/)) 19 | 3、android studio、XCode、VScode、IDEA都可以 20 | 21 | 22 | ## 特别感谢 23 | 24 | - API提供:[@代码家](https://github.com/daimajia) 25 | - [干货集中营](http://gank.io/) 26 | 27 | ## 期待 28 | 29 | - 如果您在使用过程中发现BUG或者觉得有何不合适,欢迎 issues me! 30 | - 如果老铁觉得还可以,麻烦点个star支持一下,谢谢了! 31 | - [简书](https://www.jianshu.com/u/9681f3bbb8c2) 32 | 33 | ## 学习资料 34 | - 官方的 [Flutter官方地址](https://flutter.io/get-started/install/) 35 | - 国内翻译版本 [Flutter中文网](https://flutterchina.club/) 36 | - [Flutter 完整开发实战详解(一、Dart 语言和 Flutter 基础)](https://juejin.im/entry/5b631e3e51882519861c2ef1 ) 37 | - [Flutter 完整开发实战详解(二、快速实战篇)](https://juejin.im/entry/5b685bd4e51d451994602cae ) 38 | - [Flutter 完整开发实战详解(三、打包填坑篇)](https://juejin.im/entry/5b6fd5ee6fb9a009d36a4104 ) 39 | - [Flutter 完整开发实战详解(四、 Redux、主题、国际化)](https://juejin.im/post/5b79767ff265da435450a873 ) 40 | 41 | ### 第三方框架 42 | 43 | 当前 Flutter SDK 版本 v0.5.8(这些第三方库我在项目中用到了部分,这里收集起来,共大家找起来方便) 44 | 45 | 库 | 功能 46 | -------- | --- 47 | **dio**|**网络框架** 48 | **shared_preferences**|**本地数据缓存** 49 | **fluttertoast**|**toast** 50 | **flutter_redux**|**redux** 51 | **device_info**|**设备信息** 52 | **connectivity**|**网络链接** 53 | **flutter_markdown**|**markdown解析** 54 | **json_annotation**|**json模板** 55 | **json_serializable**|**json模板** 56 | **url_launcher**|**启动外部浏览器** 57 | **iconfont**|**字库图标** 58 | **share**|**系统分享** 59 | **flutter_spinkit**|**加载框样式** 60 | **get_version**|**版本信息** 61 | **flutter_webview_plugin**|**全屏的webview** 62 | **sqflite**|**数据库** 63 | **flutter_statusbar**|**状态栏** 64 | **flutter_svg**|**svg** 65 | **photo_view**|**图片预览** 66 | **flutter_slidable**|**侧滑** 67 | 68 | 官方的Demo及各个Widget的效果在安装了Flutter SDK之后在 Flutter SDK安装目录/flutter/examples下,可以自己一一尝试。 69 | 70 | ## flutter (MAC)环境搭建 71 | 输入命令: 72 | 73 | export PUB_HOSTED_URL=https://pub.flutter-io.cn 74 | export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn 75 | git clone -b dev https://github.com/flutter/flutter.git 76 | export PATH="$PWD/flutter/bin:$PATH" 77 | cd ./flutter 78 | flutter doctor 79 | 80 | ## 已优化问题 81 | 82 | - 首页banner优化:提升banner加载图片的速度 83 | - 图片优化:福利图片列表加载图片和图片详情展示图片性能提升70% 84 | - UI以及交互优化:页面跳转,展示交互相对提升 85 | - 优化下拉刷新,上拉加载样式和加载中交互 86 | - 首页、分类页 添加缓存功能 87 | - iOS机器上不卡顿,在Android机器上存在卡顿(解决中……) 88 | - 注册、登录、分享功能暂未实现(进行中……) 89 | 90 | ### 更新日志 91 | - 3.2.0 92 | - 更新Flutter SDK 93 | - 列表样式修改,提升列表颜值 94 | - 我的页面修改,简介美观 95 | - 3.1.5 96 | - 更新库文件,优化首页banner 97 | - 优化代码,调整重构首页 98 | - 3.1.4 99 | - 服务端接口发生变化,首页业务逻辑改变 100 | - 解决一个因为异步刷新导致的崩溃问题(该问题困扰我很长时间,终于解决了) 101 | - 3.1.3 102 | - 自定义日历控件 103 | - 新增历史车轮页面,通过日历展示哪天有干货 104 | - 新增展示某日干货页面,点击日历查询日期对应的干货信息 105 | - 3.1.2 106 | - 分类列表页添加缓存,优化用户体验 107 | - 首页banner,item各自添加缓存功能 108 | - 3.1.1 109 | - 首页banner优化 110 | - 福利图片加载图片优化新能提示70% 111 | - 查看图片交互效果优化 112 | - 优化搜索 113 | - 图片带缓存 114 | - 3.1.0 UI风格大调整,全新的 UI 风格(里程碑) 115 | - 3.0.0 116 | - 1.优化刷新样式,提升交互效果 117 | - 2.新增搜索功能 118 | - 3.解决应用启动白屏的问题,改变主界面UI,增强用户体验 119 | - 2.0.0 完善业务逻辑,避免造轮子 120 | - 1.0.0 发布成功 121 | 122 | -------------------------------------------------------------------------------- /lib/utils/MessageDialogUtils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | /// 消息dialogue 5 | // ignore: must_be_immutable 6 | class MessageDialog extends Dialog { 7 | String title; 8 | String message; 9 | String negativeText; 10 | String positiveText; 11 | Function onCloseEvent; 12 | Function onPositivePressEvent; 13 | 14 | MessageDialog({ 15 | Key key, 16 | @required this.title, 17 | @required this.message, 18 | this.negativeText, 19 | this.positiveText, 20 | this.onPositivePressEvent, 21 | @required this.onCloseEvent, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return new Padding( 27 | padding: const EdgeInsets.all(15.0), 28 | child: new Material( 29 | type: MaterialType.transparency, 30 | child: new Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | new Container( 34 | decoration: ShapeDecoration( 35 | color: Color(0xffffffff), 36 | shape: RoundedRectangleBorder( 37 | borderRadius: BorderRadius.all( 38 | Radius.circular(8.0), 39 | ), 40 | ), 41 | ), 42 | margin: const EdgeInsets.all(12.0), 43 | child: new Column( 44 | children: [ 45 | new Padding( 46 | padding: const EdgeInsets.all(10.0), 47 | child: new Stack( 48 | alignment: AlignmentDirectional.centerEnd, 49 | children: [ 50 | new Center( 51 | child: new Text( 52 | title, 53 | style: new TextStyle( 54 | fontSize: 19.0, 55 | ), 56 | ), 57 | ), 58 | new GestureDetector( 59 | onTap: this.onCloseEvent, 60 | child: new Padding( 61 | padding: const EdgeInsets.all(5.0), 62 | child: new Icon( 63 | Icons.close, 64 | color: Color(0xffe0e0e0), 65 | ), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ), 71 | new Container( 72 | color: Color(0xffe0e0e0), 73 | height: 1.0, 74 | ), 75 | new Container( 76 | constraints: BoxConstraints(minHeight: 180.0), 77 | child: new Padding( 78 | padding: const EdgeInsets.all(12.0), 79 | child: new IntrinsicHeight( 80 | child: new Text( 81 | message, 82 | style: TextStyle(fontSize: 16.0), 83 | ), 84 | ), 85 | ), 86 | ), 87 | this._buildBottomButtonGroup(), 88 | ], 89 | ), 90 | ), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | 97 | Widget _buildBottomButtonGroup() { 98 | var widgets = []; 99 | if (negativeText != null && negativeText.isNotEmpty) 100 | widgets.add(_buildBottomCancelButton()); 101 | if (positiveText != null && positiveText.isNotEmpty) 102 | widgets.add(_buildBottomPositiveButton()); 103 | return new Flex( 104 | direction: Axis.horizontal, 105 | children: widgets, 106 | ); 107 | } 108 | 109 | Widget _buildBottomCancelButton() { 110 | return new Flexible( 111 | fit: FlexFit.tight, 112 | child: new FlatButton( 113 | onPressed: onCloseEvent, 114 | child: new Text( 115 | negativeText, 116 | style: TextStyle( 117 | fontSize: 16.0, 118 | ), 119 | ), 120 | ), 121 | ); 122 | } 123 | 124 | Widget _buildBottomPositiveButton() { 125 | return new Flexible( 126 | fit: FlexFit.tight, 127 | child: new FlatButton( 128 | onPressed: onPositivePressEvent, 129 | child: new Text( 130 | positiveText, 131 | style: TextStyle( 132 | color: Color(Colors.teal.value), 133 | fontSize: 16.0, 134 | ), 135 | ), 136 | ), 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/pages/history/HistoryPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; import 'package:GankFlutter/api/Api.dart'; import 'package:GankFlutter/api/http.dart'; import 'package:GankFlutter/common/Constant.dart'; import 'package:GankFlutter/common/GlobalConfig.dart'; import 'package:GankFlutter/model/CategoryResponse.dart'; import 'package:GankFlutter/model/EventList.dart'; import 'package:GankFlutter/utils/DialogUtils.dart'; import 'package:GankFlutter/utils/SharedPrfUtils.dart'; import 'package:GankFlutter/widget/CalendarCarousel.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class HistoryPage extends StatefulWidget { @override State createState() => new _HistoryPageState(); } class _HistoryPageState extends State with HttpExt, TickerProviderStateMixin { EventList _markedDateMap = new EventList(); CurvedAnimation curved; //曲线动画,动画插值, AnimationController controller; //动画控制器 /// 标志当前在请求中。 var _isRequesting = true; var _requestError = false; @override void initState() { super.initState(); controller = new AnimationController( vsync: this, duration: const Duration(seconds: 1)); curved = new CurvedAnimation( parent: controller, curve: Curves.bounceOut); //模仿小球自由落体运动轨迹 loadingData(); } @override Widget build(BuildContext context) { CalendarCarousel _calendarCarousel = CalendarCarousel( weekendTextStyle: TextStyle( color: GlobalConfig.colorPrimary, ), thisMonthDayBorderColor: Colors.grey, weekFormat: false, markedDatesMap: _markedDateMap, todayBorderColor: GlobalConfig.colorPrimary, todayButtonColor: GlobalConfig.colorPrimary, iconColor: GlobalConfig.colorPrimary, height: 430.0, customGridViewPhysics: NeverScrollableScrollPhysics(), markedDateShowIcon: true, markedDateIconMaxShown: 2, markedDateMoreShowTotal: true, // null for not showing hidden events indicator ); return new Scaffold( appBar: new AppBar( title: new Text('历史车轮'), actions: [ new RotationTransition( turns: curved, child: new IconButton( icon: new Icon(Icons.autorenew), onPressed: () { controller.forward(); //向前播放动画 ///这里添加刷新功能 DialogUtils.show(context, "更新中…"); requestHistoryData(); }), ) ], ), body: _isRequesting ? _requestError ? buildExceptionIndicator("服务器异常,修复中…") : Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(GlobalConfig.colorPrimary), ), ) : SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ //custom icon Container( margin: EdgeInsets.symmetric(horizontal: 16.0), child: _calendarCarousel, ), ], ), )); } ///请求历史日期 void loadingData() async { ///先从缓存中获取日历数据 var cacheData = await SharedPrfUtils.get(Api.HISTORY_URL); if (cacheData != null) { var userMap = json.decode(cacheData); HistoryResponse historyResponse = HistoryResponse.fromJson(userMap); print("日历获取缓存数据成功"); setState(() { _isRequesting = false; _requestError = false; timeDataFormat(historyResponse.results); }); } else { requestHistoryData(); } } void requestHistoryData() { getHistoryRequest(Api.HISTORY_URL).then((HistoryResponse historyResponse) { if (!historyResponse.error) { var _listData = historyResponse.results; print("================================="); print(_listData); if (_listData.length > 0) { setState(() { _isRequesting = false; _requestError = false; timeDataFormat(_listData); SharedPrfUtils.saveString( Api.HISTORY_URL, json.encode(historyResponse.toJson())); }); } } DialogUtils.hidden(); controller.reverse(); //向后播放动画 }).catchError((error) { setState(() { _requestError = true; }); DialogUtils.hidden(); }); } void timeDataFormat(var listData) { _markedDateMap.clear(); for (var item in listData) { List sp = item.split("-"); _markedDateMap.add( new DateTime(int.parse(sp[0]), int.parse(sp[1]), int.parse(sp[2])), new DateTime(int.parse(sp[0]), int.parse(sp[1]), int.parse(sp[2]))); } } } -------------------------------------------------------------------------------- /lib/pages/history/WidgetHistoryList.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/model/CategoryResponse.dart'; 2 | import 'package:GankFlutter/utils/TimeUtils.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:GankFlutter/common/WebViewPage.dart'; 6 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 7 | import 'package:GankFlutter/welfare/PhotoView.dart'; 8 | 9 | class WidgetHistoryList extends StatefulWidget { 10 | final PostData gankItem; 11 | 12 | WidgetHistoryList(this.gankItem); 13 | 14 | @override 15 | _WidgetHistoryState createState() => _WidgetHistoryState(); 16 | } 17 | 18 | class _WidgetHistoryState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return GestureDetector( 22 | onTap: () { 23 | routePagerNavigator(context, WebViewPage(widget.gankItem.toJson())); 24 | }, 25 | child: Container( 26 | decoration: BoxDecoration( 27 | color: Theme.of(context).canvasColor, 28 | border: Border( 29 | bottom: 30 | BorderSide(width: 0.0, color: Theme.of(context).dividerColor), 31 | )), 32 | child: IntrinsicHeight( 33 | child: Row( 34 | crossAxisAlignment: CrossAxisAlignment.stretch, 35 | children: _buildGankListItem(context), 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | 42 | ///build gank list item. 43 | List _buildGankListItem(BuildContext context) { 44 | var contentWidgets = [ 45 | Expanded( 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Container( 50 | margin: EdgeInsets.only(top: 16, bottom: 16, left: 16, right: 10), 51 | child: Text( 52 | widget.gankItem.desc, 53 | maxLines: 3, 54 | overflow: TextOverflow.ellipsis, 55 | style: Theme.of(context).textTheme.body1, 56 | ), 57 | ), 58 | Container( 59 | margin: EdgeInsets.only(left: 8, bottom: 16), 60 | child: Row( 61 | mainAxisAlignment: MainAxisAlignment.start, 62 | children: [ 63 | Row( 64 | children: [ 65 | Icon( 66 | Icons.person_outline, 67 | color: Theme.of(context).primaryColor, 68 | ), 69 | Padding( 70 | padding: const EdgeInsets.only(left: 5.0), 71 | child: SizedBox( 72 | width: 75, 73 | child: Text( 74 | widget.gankItem.author, 75 | maxLines: 1, 76 | style: Theme.of(context).textTheme.body2, 77 | overflow: TextOverflow.ellipsis, 78 | )), 79 | ) 80 | ], 81 | ), 82 | Container( 83 | child: Row( 84 | children: [ 85 | Icon(Icons.access_time, 86 | color: Theme.of(context).primaryColor), 87 | Padding( 88 | padding: const EdgeInsets.only(left: 1.0), 89 | child: Text( 90 | formatDateStr(widget.gankItem.publishedAt), 91 | style: Theme.of(context).textTheme.body2, 92 | ), 93 | ) 94 | ], 95 | ), 96 | ), 97 | ], 98 | ), 99 | ), 100 | ], 101 | ), 102 | ), 103 | ]; 104 | 105 | ///添加右侧缩略图显示 106 | if (widget.gankItem.images != null && widget.gankItem.images.isNotEmpty) { 107 | var imageUrl = widget.gankItem.images[0]; 108 | contentWidgets.add(GestureDetector( 109 | onTap: () { 110 | routePagerNavigator(context, 111 | new PhotoView(item: new BannerData(null, "缩略图", imageUrl))); 112 | }, 113 | child: Container( 114 | margin: EdgeInsets.only(right: 16, top: 20, bottom: 20), 115 | width: 80, 116 | decoration: BoxDecoration( 117 | image: DecorationImage( 118 | fit: BoxFit.cover, 119 | image: CachedNetworkImageProvider( 120 | imageUrl.replaceAll("large", "thumbnail"))), 121 | ), 122 | ), 123 | )); 124 | } 125 | return contentWidgets; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_Packages.xml: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/pages/detail/DetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:GankFlutter/api/Api.dart'; 5 | import 'package:GankFlutter/api/http.dart'; 6 | import 'package:GankFlutter/common/Constant.dart'; 7 | import 'package:GankFlutter/model/CategoryResponse.dart'; 8 | import 'package:GankFlutter/utils/IndicatorUtils.dart'; 9 | import 'package:GankFlutter/utils/SharedPrfUtils.dart'; 10 | import 'package:flutter/cupertino.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 13 | 14 | import 'DetailListView.dart'; 15 | 16 | // ignore: must_be_immutable 17 | class DetailPage extends StatefulWidget { 18 | String feedType; 19 | bool showTitle = false; 20 | 21 | DetailPage({Key key, this.feedType, this.showTitle}) : super(key: key); 22 | 23 | @override 24 | State createState() => _DetailPageState(); 25 | } 26 | 27 | class _DetailPageState extends State 28 | with HttpExt, IndicatorFactory { 29 | List listData; 30 | var curPage = 1; 31 | var listTotalSize = 0; 32 | var requestError = false; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _loadingData(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | Widget detailBuild(BuildContext context) { 43 | return requestError 44 | ? buildExceptionIndicator("网络请求出错了!") 45 | : listData == null 46 | ? new Center( 47 | child: new CupertinoActivityIndicator(), 48 | ) 49 | : new EasyRefresh( 50 | autoLoad: true, 51 | key: easyRefreshKey, 52 | refreshHeader: buildDefaultHeader(), 53 | refreshFooter: buildDefaultFooter(), 54 | onRefresh: () { 55 | _pullToRefresh(); 56 | }, 57 | loadMore: () { 58 | _loadingMore(); 59 | }, 60 | child: buildListViewBuilder(context, listData)); 61 | } 62 | 63 | if (widget.showTitle) { 64 | return new Scaffold( 65 | appBar: new AppBar( 66 | title: new Text(widget.feedType), 67 | ), 68 | body: detailBuild(context), 69 | ); 70 | } else { 71 | return new Scaffold( 72 | body: detailBuild(context), 73 | ); 74 | } 75 | } 76 | 77 | //网络请求 78 | getNewsList(bool isLoadMore) async { 79 | String url = getUrl(); 80 | await getGankfromNet(url).then((CategoryResponse categoryResponse) { 81 | if (categoryResponse.status == 100) { 82 | var _listData = categoryResponse.data; 83 | print(_listData); 84 | print("===================="); 85 | if (_listData.length > 0) { 86 | setState(() { 87 | if (!isLoadMore) { 88 | listData = _listData; 89 | //目前只缓存第一页数据 90 | SharedPrfUtils.saveString( 91 | url, json.encode(categoryResponse.toJson())); 92 | } else { 93 | List list1 = new List(); 94 | list1.addAll(listData); 95 | list1.addAll(_listData); 96 | listData = list1; 97 | } 98 | }); 99 | } 100 | } 101 | }).catchError((error) { 102 | setState(() { 103 | print("====================" + error.toString()); 104 | requestError = true; 105 | }); 106 | }).whenComplete(() { 107 | if (requestError) return; 108 | if (easyRefreshKey.currentState == null) return; 109 | if (isLoadMore) { 110 | easyRefreshKey.currentState.callLoadMoreFinish(); 111 | } else { 112 | easyRefreshKey.currentState.callRefreshFinish(); 113 | } 114 | }); 115 | } 116 | 117 | //刷新 118 | Future _pullToRefresh() async { 119 | curPage = 1; 120 | getNewsList(false); 121 | return null; 122 | } 123 | 124 | //加载更多 125 | void _loadingMore() { 126 | print("load more ... "); 127 | curPage++; 128 | getNewsList(true); 129 | } 130 | 131 | void _loadingData() async { 132 | //先获取缓存数据 133 | String url = getUrl(); 134 | var cacheData = await SharedPrfUtils.get(url); 135 | if (cacheData != null) { 136 | var userMap = json.decode(cacheData); 137 | CategoryResponse categoryResponse = CategoryResponse.fromJson(userMap); 138 | print("获取缓存数据成功"); 139 | setState(() { 140 | listData = categoryResponse.data; 141 | }); 142 | } 143 | 144 | if (null == listData) { 145 | print("开始网络请求"); 146 | getNewsList(false); 147 | } 148 | } 149 | 150 | String getUrl() { 151 | var url = Api.FEED_URL + 152 | (widget.feedType == "Girl" ? "Girl/type/" : "GanHuo/type/"); 153 | 154 | url += widget.feedType + '/page/' + this.curPage.toString() + "/count/10"; 155 | print("feedListUrl: $url"); 156 | return url; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/pages/search/SearchListPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:GankFlutter/common/GlobalConfig.dart'; 5 | import 'package:GankFlutter/api/Api.dart'; 6 | import 'package:GankFlutter/api/http.dart'; 7 | import 'package:GankFlutter/common/Constant.dart'; 8 | import 'package:GankFlutter/model/CategoryResponse.dart'; 9 | import 'package:GankFlutter/pages/detail/DetailListView.dart'; 10 | import 'package:GankFlutter/utils/IndicatorUtils.dart'; 11 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 12 | import 'package:flutter/cupertino.dart'; 13 | import 'package:flutter/material.dart'; 14 | 15 | // ignore: must_be_immutable 16 | class SearchListPage extends StatefulWidget { 17 | String id; 18 | 19 | SearchListPage(ValueKey key) : super(key: key) { 20 | this.id = key.value.toString(); 21 | } 22 | 23 | @override 24 | _SearchListPageState createState() => new _SearchListPageState(); 25 | } 26 | 27 | class _SearchListPageState extends State 28 | with HttpExt, IndicatorFactory { 29 | ///异常描述 30 | var _errorString = "傻了吧唧的!不输入东西搜啥"; 31 | 32 | /// 标志当前在请求中。 33 | var _isRequesting = false; 34 | 35 | ///请求是否异常 36 | var requestError = true; 37 | var listData; 38 | var curPage = 1; 39 | 40 | final controller = TextEditingController(); 41 | 42 | @override 43 | void initState() { 44 | super.initState(); 45 | setState(() { 46 | _isRequesting = true; 47 | requestError = true; 48 | }); 49 | searchArticle(false); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return requestError 55 | ? _isRequesting 56 | ? Center( 57 | child: CircularProgressIndicator( 58 | valueColor: AlwaysStoppedAnimation(GlobalConfig.colorPrimary), 59 | ), 60 | ) 61 | : buildExceptionIndicator(_errorString) 62 | : listData == null 63 | ? new Center( 64 | child: buildExceptionIndicator("抱歉!这会我傻了,啥也没搜到"), 65 | ) 66 | : new EasyRefresh( 67 | autoLoad: true, 68 | key: easyRefreshKey, 69 | refreshHeader: buildDefaultHeader(), 70 | refreshFooter: buildDefaultFooter(), 71 | // ignore: missing_return 72 | onRefresh: () { 73 | _pullToRefresh(); 74 | }, 75 | // ignore: missing_return 76 | loadMore: () { 77 | _loadingMore(); 78 | }, 79 | child: buildListViewBuilder(context, listData)); 80 | } 81 | 82 | //刷新 83 | Future _pullToRefresh() async { 84 | curPage = 1; 85 | searchArticle(false); 86 | return null; 87 | } 88 | 89 | //加载更多 90 | void _loadingMore() async { 91 | print("load more ... "); 92 | curPage++; 93 | searchArticle(true); 94 | } 95 | 96 | //网络请求 97 | searchArticle(bool isLoadMore) { 98 | var url = Api.SEARCH_URL; 99 | url += widget.id + 100 | '/category/All/type/All/page/' + 101 | this.curPage.toString() + 102 | '/count/10'; 103 | print("feedListUrl: $url"); 104 | 105 | HttpExt.get(url, (data) { 106 | setState(() { 107 | _isRequesting = false; 108 | requestError = false; 109 | new Future.delayed(new Duration(milliseconds: 200)).then((val) { 110 | requestBack(isLoadMore); 111 | }); 112 | }); 113 | if (data != null) { 114 | CategoryResponse categoryResponse = 115 | CategoryResponse.fromJson(jsonDecode(data)); 116 | if (categoryResponse.status == 100) { 117 | var _listData = categoryResponse.data; 118 | print(_listData); 119 | if (_listData.length > 0) { 120 | setState(() { 121 | if (!isLoadMore) { 122 | listData = _listData; 123 | } else { 124 | List list1 = new List(); 125 | list1.addAll(listData); 126 | list1.addAll(_listData); 127 | listData = list1; 128 | } 129 | }); 130 | } else { 131 | setState(() { 132 | listData = null; 133 | }); 134 | } 135 | } 136 | } 137 | }, (e) { 138 | print(" get news list error: $e"); 139 | setState(() { 140 | requestError = true; 141 | if (e.toString().split("

") != null && 142 | e.toString().split("

").length > 1) { 143 | _errorString = "服务器异常,修复中…"; 144 | } 145 | }); 146 | }); 147 | } 148 | 149 | void requestBack(bool isLoadMore) { 150 | if (requestError) return; 151 | if (isLoadMore) { 152 | easyRefreshKey.currentState.callLoadMoreFinish(); 153 | } else { 154 | easyRefreshKey.currentState.callRefreshFinish(); 155 | } 156 | } 157 | 158 | @override 159 | void dispose() { 160 | controller.dispose(); 161 | super.dispose(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/pages/update/UpdatePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class UpdatePage extends StatefulWidget { 5 | @override 6 | State createState() { 7 | return new _UpdatePageState(); 8 | } 9 | } 10 | 11 | class _UpdatePageState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | final itemInfo = new List.generate( 15 | _allPages.length, 16 | (i) => i < _allPages.length - 1 17 | ? new MessageItem(_allPages[i]) 18 | : new HeadingItem(), 19 | ); 20 | 21 | return new Scaffold( 22 | appBar: new AppBar( 23 | title: new Text("版本更新"), 24 | ), 25 | body: new ListView.builder( 26 | itemCount: itemInfo.length, 27 | // ignore: missing_return 28 | itemBuilder: (context, index) { 29 | final item = itemInfo[index]; 30 | 31 | if (item is MessageItem) { 32 | return buildListTile(context, item.page); 33 | } else if (item is HeadingItem) { 34 | return buildFooter(context); 35 | } 36 | }, 37 | )); 38 | } 39 | 40 | Widget buildListTile(BuildContext context, Page item) { 41 | return new Container( 42 | padding: EdgeInsets.fromLTRB(0.0, 20.0, 7.0, 0.0), 43 | child: new Row( 44 | ///主轴居中,即是横向居中 45 | children: [ 46 | ///一个图标,大小16.0,灰色 47 | new Expanded( 48 | child: new CircleAvatar( 49 | child: new Text( 50 | item.versionCode, 51 | style: const TextStyle(color: Colors.white, fontSize: 12.0), 52 | ), 53 | maxRadius: 15.0, 54 | backgroundColor: Colors.brown, 55 | ), 56 | ), 57 | 58 | new Expanded( 59 | flex: 4, 60 | 61 | ///间隔 62 | child: new Column( 63 | crossAxisAlignment: CrossAxisAlignment.start, 64 | mainAxisSize: MainAxisSize.min, 65 | children: [ 66 | new Text(item.versionTime), 67 | new Text(item.versionName), 68 | ], 69 | ), 70 | ), 71 | ], 72 | ), 73 | ); 74 | } 75 | 76 | Widget buildFooter(BuildContext context) { 77 | return 78 | 79 | ///居中显示 80 | new Center( 81 | child: Container( 82 | padding: EdgeInsets.only(top: 50.0), 83 | child: new Row( 84 | ///主轴居中,即是横向居中 85 | mainAxisAlignment: MainAxisAlignment.center, 86 | 87 | ///大小按照最大充满 88 | mainAxisSize: MainAxisSize.max, 89 | children: [ 90 | ///一个图标,大小16.0,灰色 91 | new Image.asset( 92 | 'images/smile.png', 93 | width: 25.0, 94 | height: 25.0, 95 | color: Colors.red, 96 | ), 97 | 98 | ///间隔 99 | new Padding(padding: new EdgeInsets.only(left: 15.0)), 100 | new Column( 101 | crossAxisAlignment: CrossAxisAlignment.start, 102 | mainAxisSize: MainAxisSize.min, 103 | children: [ 104 | new Text( 105 | '感谢你对我的关注', 106 | style: const TextStyle(color: Colors.red, fontSize: 12.0), 107 | ), 108 | new Text( 109 | '希望这些更新能给你带来帮助', 110 | style: const TextStyle(color: Colors.red, fontSize: 12.0), 111 | ), 112 | ], 113 | ) 114 | ], 115 | ), 116 | ) //显示右侧的箭头,不显示则传null 117 | ); 118 | } 119 | } 120 | 121 | class Page { 122 | Page({this.versionCode, this.versionName, this.versionTime}); 123 | 124 | final String versionCode; 125 | final String versionName; 126 | final String versionTime; 127 | } 128 | 129 | // 存储所有页面的列表 130 | final List _allPages = [ 131 | new Page( 132 | versionCode: "1.0", versionName: "1.0正式发布成功", versionTime: "2018-8-20"), 133 | new Page( 134 | versionCode: "2.0", versionName: "完善业务逻辑,避免造轮子", versionTime: "2018-9-7"), 135 | new Page( 136 | versionCode: "3.0", 137 | versionName: "自定义刷新加载样式,添加搜索功能,解决应用启动白屏的问题,改变主界面UI,增强用户体验", 138 | versionTime: "2018-9-19"), 139 | new Page( 140 | versionCode: "3.1", 141 | versionName: "UI风格大调整,全新的 UI 风格(里程碑)", 142 | versionTime: "2018-9-23"), 143 | new Page( 144 | versionCode: "3.1.1", 145 | versionName: "首页banner优化,图片加载,展示提升70%,图片带缓存", 146 | versionTime: "2018-9-30"), 147 | new Page( 148 | versionCode: "3.1.2", 149 | versionName: "我的页面改版,更换刷新库", 150 | versionTime: "2019-1-1"), 151 | new Page(versionCode: "n.n", versionName: "待定", versionTime: "待定"), 152 | ]; 153 | 154 | // The base class for the different types of items the List can contain 155 | abstract class ListItem {} 156 | 157 | // A ListItem that contains data to display a heading 158 | class HeadingItem implements ListItem {} 159 | 160 | // A ListItem that contains data to display a message 161 | class MessageItem implements ListItem { 162 | final Page page; 163 | 164 | MessageItem(this.page); 165 | } 166 | -------------------------------------------------------------------------------- /lib/pages/login/LoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:GankFlutter/common/GlobalConfig.dart'; 4 | import 'package:GankFlutter/common/CommonShare.dart'; 5 | 6 | class LoginPage extends StatefulWidget { 7 | @override 8 | State createState() { 9 | return new _LoginPageState(); 10 | } 11 | } 12 | 13 | class _LoginPageState extends State { 14 | GlobalKey registKey = new GlobalKey(); 15 | 16 | String _phoneNum = ''; 17 | 18 | String _verifyCode = ''; 19 | 20 | Widget _buildPhoneEdit() { 21 | var node = new FocusNode(); 22 | return new Padding( 23 | padding: const EdgeInsets.only(left: 40.0, right: 40.0), 24 | child: new TextField( 25 | onChanged: (str) { 26 | _phoneNum = str; 27 | setState(() {}); 28 | }, 29 | decoration: new InputDecoration( 30 | hintText: GlobalConfig.inputNice, 31 | labelText: GlobalConfig.nice, 32 | hintStyle: new TextStyle(fontSize: 12.0, color: Colors.grey)), 33 | maxLines: 1, 34 | maxLength: 11, 35 | //键盘展示为号码 36 | keyboardType: TextInputType.phone, 37 | //只能输入数字 38 | inputFormatters: [ 39 | WhitelistingTextInputFormatter.digitsOnly, 40 | ], 41 | onSubmitted: (text) { 42 | // FocusScope.of(context).reparentIfNeeded(node); 43 | FocusScope.of(context).requestFocus(node); 44 | 45 | }, 46 | ), 47 | ); 48 | } 49 | 50 | Widget _buildVerifyCodeEdit() { 51 | var node = new FocusNode(); 52 | Widget verifyCodeEdit = new TextField( 53 | onChanged: (str) { 54 | _verifyCode = str; 55 | setState(() {}); 56 | }, 57 | decoration: new InputDecoration( 58 | hintText: GlobalConfig.inputCode, 59 | labelText: GlobalConfig.pwd, 60 | hintStyle: new TextStyle(fontSize: 12.0, color: Colors.grey)), 61 | maxLines: 1, 62 | maxLength: 6, 63 | //键盘展示为数字 64 | keyboardType: TextInputType.number, 65 | //只能输入数字 66 | inputFormatters: [ 67 | WhitelistingTextInputFormatter.digitsOnly, 68 | ], 69 | onSubmitted: (text) { 70 | // FocusScope.of(context).reparentIfNeeded(node); 71 | FocusScope.of(context).requestFocus(node); 72 | }, 73 | ); 74 | 75 | return new Padding( 76 | padding: const EdgeInsets.only(left: 40.0, right: 40.0, top: 0.0), 77 | child: new Stack( 78 | children: [ 79 | verifyCodeEdit, 80 | ], 81 | ), 82 | ); 83 | } 84 | 85 | Widget _buildRegister() { 86 | return new Padding( 87 | padding: EdgeInsets.only(top: 30.0, bottom: 30.0), 88 | child: new RaisedButton( 89 | padding: new EdgeInsets.fromLTRB(130.0, 10.0, 130.0, 10.0), 90 | color: GlobalConfig.colorPrimary, 91 | textColor: Colors.white, 92 | disabledColor: Colors.blue[100], 93 | onPressed: (_phoneNum.isEmpty || _verifyCode.isEmpty) 94 | ? null 95 | : () { 96 | showTips(); 97 | }, 98 | child: new Text( 99 | GlobalConfig.loginSubView, 100 | style: new TextStyle(fontSize: 16.0, color: Colors.white), 101 | ), 102 | ), 103 | ); 104 | } 105 | 106 | Widget _buildTips() { 107 | return new Padding( 108 | padding: const EdgeInsets.only( 109 | left: 40.0, right: 40.0, top: 50.0, bottom: 50.0), 110 | child: new Row( 111 | mainAxisAlignment: MainAxisAlignment.center, //子组件的排列方式为主轴两端对齐 112 | children: [ 113 | new Image.asset( 114 | 'images/logo.png', 115 | width: 60.0, 116 | height: 60.0, 117 | ), 118 | ], 119 | ), 120 | ); 121 | } 122 | 123 | Widget _buildBody() { 124 | return new Column( 125 | children: [ 126 | _buildTips(), 127 | _buildPhoneEdit(), 128 | _buildVerifyCodeEdit(), 129 | _buildRegister(), 130 | ], 131 | ); 132 | } 133 | 134 | showTips() { 135 | showModalBottomSheet( 136 | context: context, 137 | builder: (BuildContext context) { 138 | return new Container( 139 | child: new Padding( 140 | padding: const EdgeInsets.all(32.0), 141 | child: new Text('没有相关接口,这是一个纯UI界面,提供部分交互体验', 142 | textAlign: TextAlign.center, 143 | style: new TextStyle( 144 | color: Theme.of(context).accentColor, 145 | fontSize: 24.0)))); 146 | }); 147 | } 148 | 149 | @override 150 | Widget build(BuildContext context) { 151 | return new Material( 152 | child: new Scaffold( 153 | key: registKey, 154 | backgroundColor: Colors.white, 155 | appBar: new AppBar( 156 | title: new Text(GlobalConfig.githubLogin), 157 | actions: [ 158 | new InkWell( 159 | child: new Padding( 160 | padding: EdgeInsets.all(18.0), 161 | child: new Text(GlobalConfig.pwdLogin), 162 | ), 163 | onTap: () { 164 | // showTips(); 165 | CommonShare.buildShareBottomPop(context); 166 | }, 167 | ) 168 | ], 169 | ), 170 | body: _buildBody(), 171 | ), 172 | ); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/pages/mine/settingCard.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:GankFlutter/common/GlobalConfig.dart'; 3 | import 'package:GankFlutter/pages/push/PushPage.dart'; 4 | import 'package:GankFlutter/pages/update/UpdatePage.dart'; 5 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 6 | 7 | Widget settingCard(BuildContext context) { 8 | return new Container( 9 | color: GlobalConfig.cardBackgroundColor, 10 | margin: const EdgeInsets.only(top: 6.0, bottom: 6.0), 11 | padding: const EdgeInsets.only(top: 12.0, bottom: 8.0), 12 | child: new Row( 13 | mainAxisAlignment: MainAxisAlignment.start, 14 | children: [ 15 | new Container( 16 | width: MediaQuery.of(context).size.width / 4, 17 | child: new FlatButton( 18 | onPressed: () { 19 | routePagerNavigator(context, new PushPage()); 20 | }, 21 | child: new Container( 22 | child: new Column( 23 | children: [ 24 | new Container( 25 | margin: const EdgeInsets.only(bottom: 6.0), 26 | child: new CircleAvatar( 27 | radius: 20.0, 28 | child: 29 | new Icon(Icons.invert_colors, color: Colors.white), 30 | backgroundColor: new Color(0xFFB88800), 31 | ), 32 | ), 33 | new Container( 34 | child: new Text(GlobalConfig.pushTitle, 35 | style: new TextStyle( 36 | color: GlobalConfig.fontColor, fontSize: 14.0)), 37 | ) 38 | ], 39 | ), 40 | )), 41 | ), 42 | new Container( 43 | width: MediaQuery.of(context).size.width / 4, 44 | child: new FlatButton( 45 | onPressed: () { 46 | // launch(GlobalConfig.thanksEditURl); 47 | routeWebView(context, GlobalConfig.thinkTitle, 48 | GlobalConfig.thanksEditURl); 49 | }, 50 | child: new Container( 51 | child: new Column( 52 | children: [ 53 | new Container( 54 | margin: const EdgeInsets.only(bottom: 6.0), 55 | child: new CircleAvatar( 56 | radius: 20.0, 57 | child: new Icon(Icons.edit, color: Colors.white), 58 | backgroundColor: new Color(0xFF63616D), 59 | ), 60 | ), 61 | new Container( 62 | child: new Text(GlobalConfig.thinkTitle, 63 | style: new TextStyle( 64 | color: GlobalConfig.fontColor, fontSize: 14.0)), 65 | ) 66 | ], 67 | ), 68 | )), 69 | ), 70 | new Container( 71 | width: MediaQuery.of(context).size.width / 4, 72 | child: new FlatButton( 73 | onPressed: () { 74 | routePagerNavigator(context, new UpdatePage()); 75 | // Navigator.push(context, 76 | // new MaterialPageRoute(builder: (context) => new update())); 77 | }, 78 | child: new Container( 79 | child: new Column( 80 | children: [ 81 | new Container( 82 | margin: const EdgeInsets.only(bottom: 6.0), 83 | child: new CircleAvatar( 84 | radius: 20.0, 85 | child: new Icon(Icons.update, color: Colors.white), 86 | backgroundColor: new Color(0xFFB86A0D), 87 | ), 88 | ), 89 | new Container( 90 | child: new Text(GlobalConfig.updateTitle, 91 | style: new TextStyle( 92 | color: GlobalConfig.fontColor, fontSize: 14.0)), 93 | ) 94 | ], 95 | ), 96 | )), 97 | ), 98 | new Container( 99 | width: MediaQuery.of(context).size.width / 4, 100 | child: new FlatButton( 101 | onPressed: () { 102 | // launch(GlobalConfig.aboutAuthorURl); 103 | routeWebView(context, GlobalConfig.aboutTitle, 104 | GlobalConfig.aboutAuthorURl); 105 | }, 106 | child: new Container( 107 | child: new Column( 108 | children: [ 109 | new Container( 110 | margin: const EdgeInsets.only(bottom: 6.0), 111 | child: new CircleAvatar( 112 | radius: 20.0, 113 | child: new Icon(Icons.perm_data_setting, 114 | color: Colors.white), 115 | backgroundColor: new Color(0xFF636269), 116 | ), 117 | ), 118 | new Container( 119 | child: new Text(GlobalConfig.aboutTitle, 120 | style: new TextStyle( 121 | color: GlobalConfig.fontColor, fontSize: 14.0)), 122 | ) 123 | ], 124 | ), 125 | )), 126 | ), 127 | ], 128 | ), 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /lib/common/DetailList.dart: -------------------------------------------------------------------------------- 1 | import 'package:GankFlutter/common/GlobalConfig.dart'; 2 | import 'package:GankFlutter/common/WebViewPage.dart'; 3 | import 'package:GankFlutter/model/CategoryResponse.dart'; 4 | import 'package:GankFlutter/utils/PageRouteUtils.dart'; 5 | import 'package:GankFlutter/utils/TimeUtils.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:GankFlutter/utils/ScreenUtils.dart'; 10 | 11 | Widget buildDetailListRow(context, PostData postData) { 12 | return new InkWell( 13 | onTap: () { 14 | print("------------111----------"+postData.url); 15 | routePagerNavigator(context, WebViewPage(postData.toJson())); 16 | }, 17 | child: new Card( 18 | margin: new EdgeInsets.all(2.0), 19 | child: new Padding( 20 | padding: new EdgeInsets.all(8.0), 21 | child: new Row( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | //左边 25 | new Offstage( 26 | offstage: (postData.images == null || postData.images.isEmpty), 27 | child: new CachedNetworkImage( 28 | imageUrl: (postData.images != null && 29 | postData.images.isNotEmpty) 30 | ? postData.images[0] 31 | : "https://flutter.io/images/homepage/header-illustration.png", 32 | fit: BoxFit.cover, 33 | width: 80.0, 34 | height: 130.0, 35 | ), 36 | ), 37 | //右边 38 | new Container( 39 | height: 130.0, 40 | margin: EdgeInsets.only(left: 8.0), 41 | width: (postData.images == null || postData.images.isEmpty) 42 | ? Screen.width - 32 43 | : Screen.width - 130.0, 44 | child: new Column( 45 | crossAxisAlignment: CrossAxisAlignment.start, 46 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 47 | children: [ 48 | new Row(children: [ 49 | new Container( 50 | margin: new EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 4.0), 51 | child: new Align( 52 | alignment: Alignment.centerLeft, 53 | child: new Icon( 54 | Icons.access_time, 55 | size: 12.0, 56 | color: GlobalConfig.colorPrimary, 57 | ), 58 | ), 59 | ), 60 | new Text( 61 | formatDateStr(postData.publishedAt.toString()), 62 | style: 63 | new TextStyle(fontSize: 12.0, color: Colors.grey), 64 | ), 65 | new Expanded( 66 | child: new Align( 67 | alignment: Alignment.centerRight, 68 | child: new Text( 69 | getTimestampString( 70 | DateTime.parse(postData.publishedAt)), 71 | style: 72 | new TextStyle(fontSize: 12.0, color: Colors.grey), 73 | ), 74 | )), 75 | ]), 76 | new Container( 77 | margin: new EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 14.0), 78 | child: new Text( 79 | postData.desc, 80 | maxLines: 3, 81 | style: new TextStyle( 82 | fontSize: 16.0, 83 | color: Colors.black87, 84 | ), 85 | ), 86 | ), 87 | new Row(children: [ 88 | new Material( 89 | child: new Container( 90 | margin: new EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 2.0), 91 | child: new Text( 92 | postData.author.toString(), 93 | style: new TextStyle( 94 | fontSize: 12.0, color: Color(0xFF00BFA5)), 95 | ), 96 | ), 97 | borderRadius: BorderRadius.circular(20.0), 98 | color: Color(0xFFB2DFDB), 99 | ), 100 | new Container( 101 | margin: EdgeInsets.only(left: 15), 102 | child: new Material( 103 | child: new Container( 104 | margin: new EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 2.0), 105 | child: new Text( 106 | "点赞👍"+ postData.stars.toString()+"颗🌟", 107 | style: new TextStyle( 108 | fontSize: 12.0, color: Colors.amber), 109 | ), 110 | ), 111 | borderRadius: BorderRadius.circular(20.0), 112 | color: Color(0xFFFFECB3), 113 | ), 114 | ), 115 | ]), 116 | ], 117 | ), 118 | ) 119 | ], 120 | ), 121 | ), 122 | )); 123 | } 124 | -------------------------------------------------------------------------------- /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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 30 | 227 | --------------------------------------------------------------------------------