├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ ├── com │ │ │ └── zrgteam │ │ │ │ └── tinmoi │ │ │ │ └── MainActivity.java │ │ └── io │ │ │ └── flutter │ │ │ └── plugins │ │ │ └── GeneratedPluginRegistrant.java │ │ └── res │ │ ├── drawable │ │ ├── launch_background.xml │ │ └── splash.png │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── launcher_icon.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── launcher_icon.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── launcher_icon.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── launcher_icon.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── launcher_icon.png │ │ └── values │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ ├── FFF_Tusj.ttf │ ├── Poppins-SemiBold.ttf │ └── circle.ttf ├── images │ ├── banner-1.png │ ├── banner-2.png │ ├── banner-3.png │ ├── banner-icon-2.png │ ├── banner1.png │ ├── chinhphu.jpg │ ├── congnghe.jpg │ ├── giaitri.jpg │ ├── giaoduc.jpg │ ├── honnhan.jpg │ ├── icon-banner-1.png │ ├── ictnews.png │ ├── khoahoc.jpg │ ├── kienthuc.jpeg │ ├── kinhdoanh.jpg │ ├── kinhte.jpg │ ├── lamdep.jpg │ ├── laodong.jpg │ ├── latest.jpg │ ├── login.jpg │ ├── noimage-reading.jpg │ ├── noimage.jpg │ ├── petrotimes.png │ ├── phapluat.png │ ├── saostart.jpg │ ├── swipe-left.png │ ├── swipeleft.png │ ├── swiperight.png │ ├── thanhnien.jpg │ ├── thegioi.jpg │ ├── thethao.jpg │ ├── tick.png │ ├── tienphong.png │ ├── tinhot.png │ ├── vanhoa.jpg │ ├── videoplayer.jpg │ ├── vietnamfinance.png │ ├── vietnamnet.png │ ├── vietnamplus.jpg │ ├── vnmedia.png │ ├── vov.jpg │ ├── vtc.png │ ├── xahoi.jpg │ └── zing.png └── store │ ├── feature-graphic.png │ ├── good-3.png │ ├── good-4.png │ ├── good-5.png │ ├── good-6.png │ ├── good-7.png │ ├── hi-res.png │ ├── icon.png │ ├── store-2.png │ ├── store-3-512.jpg │ ├── store-3.jpg │ ├── store-3.png │ ├── store-4.png │ ├── store.png │ └── store.svg ├── documents └── ABOUT.md ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── common │ ├── actions │ │ └── common.dart │ ├── components │ │ ├── BottomBar.dart │ │ ├── ContentLoading.dart │ │ ├── Form.dart │ │ ├── FrostedGlassAppBar.dart │ │ ├── GradientAppBar.dart │ │ ├── ImageCached.dart │ │ ├── InputFields.dart │ │ ├── MainDrawer.dart │ │ ├── MiniNewsList.dart │ │ ├── MiniNewsfeed.dart │ │ ├── NewsList.dart │ │ ├── Newsfeed.dart │ │ └── SpinLoading.dart │ ├── configs.dart │ ├── reducers │ │ ├── app.dart │ │ └── common.dart │ ├── repository.dart │ ├── state.dart │ ├── states │ │ └── common.dart │ ├── store.dart │ └── utils │ │ └── navigation.dart ├── main.dart ├── modules │ ├── category │ │ ├── actions.dart │ │ ├── components │ │ │ ├── CategoriesView.dart │ │ │ ├── NewDetailTopicView.dart │ │ │ ├── SubNewsView.dart │ │ │ ├── TopicDetailView.dart │ │ │ └── TopicItemView.dart │ │ ├── containers │ │ │ └── SubNews.dart │ │ └── repository.dart │ ├── dashboard │ │ ├── actions.dart │ │ ├── components │ │ │ ├── ContinueReadingDetailView.dart │ │ │ ├── ContinueReadingView.dart │ │ │ ├── NewsTabView.dart │ │ │ ├── ReadingView.dart │ │ │ ├── TopicView.dart │ │ │ └── VideoNewsView.dart │ │ ├── containers │ │ │ ├── ContinueReading.dart │ │ │ ├── ContinueReadingDetail.dart │ │ │ ├── NewsTab.dart │ │ │ └── Reading.dart │ │ ├── reducer.dart │ │ ├── repository.dart │ │ ├── state.dart │ │ └── styles.dart │ ├── soccer │ │ ├── actions.dart │ │ ├── components │ │ │ └── SoccerCalendarView.dart │ │ ├── containers │ │ │ └── SoccerCalendar.dart │ │ ├── reducer.dart │ │ ├── repository.dart │ │ └── state.dart │ ├── user │ │ ├── actions.dart │ │ ├── animations │ │ │ └── login.dart │ │ ├── components │ │ │ ├── LoginFormView.dart │ │ │ ├── SavedNewsView.dart │ │ │ └── SignInButton.dart │ │ ├── containers │ │ │ ├── LoginForm.dart │ │ │ └── SavedNews.dart │ │ ├── models │ │ │ └── user.dart │ │ ├── reducer.dart │ │ ├── state.dart │ │ └── styles.dart │ └── welcome │ │ ├── animations │ │ ├── page_dragger.dart │ │ └── page_reveal.dart │ │ └── components │ │ ├── PagerIndicatorView.dart │ │ └── PagerView.dart ├── pages │ ├── AboutPage.dart │ ├── BoardingPage.dart │ ├── CategoriesPage.dart │ ├── ContentLoadingPage.dart │ ├── ContinueReadingPage.dart │ ├── HomePage.dart │ ├── LoadingPage.dart │ ├── LoginPage.dart │ ├── NewsSourcePage.dart │ ├── OverlayLoadingPage.dart │ ├── ReadingPage.dart │ ├── SavedNewsPage.dart │ ├── SoccerPage.dart │ ├── SubNewsPage.dart │ ├── TestAnimationPage.dart │ ├── TopicDetailPage.dart │ ├── TopicPage.dart │ └── VideosPage.dart ├── presentation │ └── platform_adaptive.dart └── styles │ ├── colors.dart │ └── texts.dart ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/key.properties 39 | **/android/**/my-release-key.keystore 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | 74 | # Firebase config 75 | ios/Runner/GoogleService-Info.plist 76 | android/app/google-services.json 77 | 78 | 79 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Protocol Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | **Flutter_redux** is a simple flutter application. It demonstrates how to implement Redux for Dart. 4 | 5 | ## Redux 6 | 7 | Redux.JS author advice : If you've got a simple app, use the simplest thing possible. In Flutter, StatefulWidget is perfect for a simple counter app. 8 | 9 | However, say you have more complex app, such as an E-commerce app with a Shopping Cart. The Shopping Cart should appear on multiple screens in your app and should be updated by many different types of Widgets on those different screens (An "Add Item to Cart" Widget on all your Product Screens, "Remove Item from Cart" Widget on the Shopping Cart Screen, "Change quantity" Widgets, etc). 10 | Now, in this case, you could create a Testable ShoppingCart class as a Singleton or Create a Root StatefulWidget that passes the ShoppingCartDown Down Down through your widget hierarchy to the "add to cart" or "remove from cart" Widgets . 11 | Yet passing the ShoppingCart all over the place can get messy. It also means it's way harder to move that "Add to Item" button to a new location, b/c you'd need up update the Widgets throughout your app that passes the state down. 12 | 13 | Furthermore, you'd need a way to Observe when the ShoppingCart Changes so you could rebuild your Widgets when it does (from an "Add" button to an "Added" button, as an example). 14 | One way to handle it would be to simply setState every time the ShoppingCart changes in your Root Widget, but then your whole app below the RootWidget would be required to rebuild as well! Flutter is fast, but we should be smart about what we ask Flutter to rebuild! 15 | 16 | ## Project structure 17 | * **main.dart** is where navigation live and where Redux's Store is provided to the app, and also the main entry point of the app that initiliaze a new Redux's Store. 18 | * **common**: 19 | 1. components: Flutter Widget with usage as common 20 | 2. actions: common actions. 21 | 3. reducers: common reducer and main app reducer. 22 | 4. states: common state class. 23 | 5. configs.dart: config of your application. 24 | 6. repository.dart: common api fetch. 25 | 7. state.dart: main state. 26 | 8. store.dart: create redux store. 27 | * **modules**: every application make up of many module. Sample: todo application make up of user (login information, user infomation, etc), todo (list todo, temp todo, etc). It make simpler to develop and easier finding problems. 28 | 1. components: module's View Widget 29 | 2. containers: where Flutter's widgets are connected to Redux state 30 | 3. state.dart: describe the object Redux's state 31 | 4. repository.dart: api fetch 32 | 5. reducers.dart: module reducer 33 | 6. actions.dart: contains Redux actions that can be triggered from widgets 34 | * **pages**: Screen of your application which made up of many components of module 35 | * **styles**: your styles 36 | 37 | ## Demo 38 | download at: https://play.google.com/store/apps/details?id=com.zrgteam.tinmoi 39 | 40 | Checkout "develop" branch 41 | 42 | ![Demo 1](https://raw.githubusercontent.com/zrg-team/flutter_redux/develop/assets/store/good-6.png) 43 | ![Demo 2](https://raw.githubusercontent.com/zrg-team/flutter_redux/develop/assets/store/good-7.png) 44 | ![Demo 3](https://github.com/zrg-team/flutter_redux/blob/develop/assets/store/good-3.png?raw=true) 45 | ![Demo 4](https://github.com/zrg-team/flutter_redux/blob/develop/assets/store/good-4.png?raw=true) 46 | 47 | ## Install 48 | 49 | + Step 1 : `flutter packages get` 50 | + Step 2 : Create a firebase project at `https://firebase.google.com/` 51 | + Step 3 : Put firebase's project file into `android/app/google-services.json` and `ios/Runner/GoogleService-Info.plist` 52 | + Step 4 : Correct your admob APPLICATION_ID in `android/app/src/AndroidManifest.xml` 53 | 54 | **Checkout branch `starter` for easier setup** 55 | 56 | ## Donate 57 | ethereum address: 0x46D1c53249cA6232eb7d3E046713Bc3D2Ae15BA3 58 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | def keystoreProperties = new Properties() 28 | def keystorePropertiesFile = rootProject.file('key.properties') 29 | if (keystorePropertiesFile.exists()) { 30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 31 | } 32 | 33 | android { 34 | compileSdkVersion 27 35 | 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "com.zrgteam.tinmoi" 43 | minSdkVersion 16 44 | targetSdkVersion 27 45 | versionCode 31 46 | versionName flutterVersionName 47 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 48 | } 49 | 50 | signingConfigs { 51 | release { 52 | keyAlias keystoreProperties['keyAlias'] 53 | keyPassword keystoreProperties['keyPassword'] 54 | storeFile file(keystoreProperties['storeFile']) 55 | storePassword keystoreProperties['storePassword'] 56 | } 57 | } 58 | buildTypes { 59 | release { 60 | signingConfig signingConfigs.release 61 | minifyEnabled true 62 | useProguard true 63 | 64 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 65 | } 66 | } 67 | 68 | compileOptions { 69 | sourceCompatibility JavaVersion.VERSION_1_8 70 | targetCompatibility JavaVersion.VERSION_1_8 71 | } 72 | } 73 | 74 | flutter { 75 | source '../..' 76 | } 77 | 78 | dependencies { 79 | testImplementation 'junit:junit:4.12' 80 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 81 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 82 | } 83 | 84 | // ADD THIS AT THE BOTTOM 85 | apply plugin: 'com.google.gms.google-services' 86 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 14 | 18 | 21 | 24 | 31 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/zrgteam/tinmoi/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zrgteam.tinmoi; 2 | 3 | import android.os.Bundle; 4 | import android.view.WindowManager; 5 | import android.view.ViewTreeObserver; 6 | import io.flutter.app.FlutterActivity; 7 | import io.flutter.plugins.GeneratedPluginRegistrant; 8 | 9 | public class MainActivity extends FlutterActivity { 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | //make transparent status bar 14 | getWindow().setStatusBarColor(0x00000000); 15 | GeneratedPluginRegistrant.registerWith(this); 16 | //Remove full screen flag after load 17 | ViewTreeObserver vto = getFlutterView().getViewTreeObserver(); 18 | vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 19 | @Override 20 | public void onGlobalLayout() { 21 | getFlutterView().getViewTreeObserver().removeOnGlobalLayoutListener(this); 22 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | import io.flutter.plugins.firebaseadmob.FirebaseAdMobPlugin; 5 | import io.flutter.plugins.pathprovider.PathProviderPlugin; 6 | import io.flutter.plugins.share.SharePlugin; 7 | import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin; 8 | import io.flutter.plugins.urllauncher.UrlLauncherPlugin; 9 | 10 | /** 11 | * Generated file. Do not edit. 12 | */ 13 | public final class GeneratedPluginRegistrant { 14 | public static void registerWith(PluginRegistry registry) { 15 | if (alreadyRegisteredWith(registry)) { 16 | return; 17 | } 18 | FirebaseAdMobPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebaseadmob.FirebaseAdMobPlugin")); 19 | PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); 20 | SharePlugin.registerWith(registry.registrarFor("io.flutter.plugins.share.SharePlugin")); 21 | SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); 22 | UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin")); 23 | } 24 | 25 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 26 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 27 | if (registry.hasPlugin(key)) { 28 | return true; 29 | } 30 | registry.registrarFor(key); 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | #FAFAFA 10 | 11 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | mavenLocal() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath 'com.google.gms:google-services:4.2.0' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | mavenLocal() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /assets/fonts/FFF_Tusj.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/fonts/FFF_Tusj.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/circle.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/fonts/circle.ttf -------------------------------------------------------------------------------- /assets/images/banner-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/banner-1.png -------------------------------------------------------------------------------- /assets/images/banner-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/banner-2.png -------------------------------------------------------------------------------- /assets/images/banner-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/banner-3.png -------------------------------------------------------------------------------- /assets/images/banner-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/banner-icon-2.png -------------------------------------------------------------------------------- /assets/images/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/banner1.png -------------------------------------------------------------------------------- /assets/images/chinhphu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/chinhphu.jpg -------------------------------------------------------------------------------- /assets/images/congnghe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/congnghe.jpg -------------------------------------------------------------------------------- /assets/images/giaitri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/giaitri.jpg -------------------------------------------------------------------------------- /assets/images/giaoduc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/giaoduc.jpg -------------------------------------------------------------------------------- /assets/images/honnhan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/honnhan.jpg -------------------------------------------------------------------------------- /assets/images/icon-banner-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/icon-banner-1.png -------------------------------------------------------------------------------- /assets/images/ictnews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/ictnews.png -------------------------------------------------------------------------------- /assets/images/khoahoc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/khoahoc.jpg -------------------------------------------------------------------------------- /assets/images/kienthuc.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/kienthuc.jpeg -------------------------------------------------------------------------------- /assets/images/kinhdoanh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/kinhdoanh.jpg -------------------------------------------------------------------------------- /assets/images/kinhte.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/kinhte.jpg -------------------------------------------------------------------------------- /assets/images/lamdep.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/lamdep.jpg -------------------------------------------------------------------------------- /assets/images/laodong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/laodong.jpg -------------------------------------------------------------------------------- /assets/images/latest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/latest.jpg -------------------------------------------------------------------------------- /assets/images/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/login.jpg -------------------------------------------------------------------------------- /assets/images/noimage-reading.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/noimage-reading.jpg -------------------------------------------------------------------------------- /assets/images/noimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/noimage.jpg -------------------------------------------------------------------------------- /assets/images/petrotimes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/petrotimes.png -------------------------------------------------------------------------------- /assets/images/phapluat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/phapluat.png -------------------------------------------------------------------------------- /assets/images/saostart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/saostart.jpg -------------------------------------------------------------------------------- /assets/images/swipe-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/swipe-left.png -------------------------------------------------------------------------------- /assets/images/swipeleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/swipeleft.png -------------------------------------------------------------------------------- /assets/images/swiperight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/swiperight.png -------------------------------------------------------------------------------- /assets/images/thanhnien.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/thanhnien.jpg -------------------------------------------------------------------------------- /assets/images/thegioi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/thegioi.jpg -------------------------------------------------------------------------------- /assets/images/thethao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/thethao.jpg -------------------------------------------------------------------------------- /assets/images/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/tick.png -------------------------------------------------------------------------------- /assets/images/tienphong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/tienphong.png -------------------------------------------------------------------------------- /assets/images/tinhot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/tinhot.png -------------------------------------------------------------------------------- /assets/images/vanhoa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vanhoa.jpg -------------------------------------------------------------------------------- /assets/images/videoplayer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/videoplayer.jpg -------------------------------------------------------------------------------- /assets/images/vietnamfinance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vietnamfinance.png -------------------------------------------------------------------------------- /assets/images/vietnamnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vietnamnet.png -------------------------------------------------------------------------------- /assets/images/vietnamplus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vietnamplus.jpg -------------------------------------------------------------------------------- /assets/images/vnmedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vnmedia.png -------------------------------------------------------------------------------- /assets/images/vov.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vov.jpg -------------------------------------------------------------------------------- /assets/images/vtc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/vtc.png -------------------------------------------------------------------------------- /assets/images/xahoi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/xahoi.jpg -------------------------------------------------------------------------------- /assets/images/zing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/images/zing.png -------------------------------------------------------------------------------- /assets/store/feature-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/feature-graphic.png -------------------------------------------------------------------------------- /assets/store/good-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/good-3.png -------------------------------------------------------------------------------- /assets/store/good-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/good-4.png -------------------------------------------------------------------------------- /assets/store/good-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/good-5.png -------------------------------------------------------------------------------- /assets/store/good-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/good-6.png -------------------------------------------------------------------------------- /assets/store/good-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/good-7.png -------------------------------------------------------------------------------- /assets/store/hi-res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/hi-res.png -------------------------------------------------------------------------------- /assets/store/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/icon.png -------------------------------------------------------------------------------- /assets/store/store-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store-2.png -------------------------------------------------------------------------------- /assets/store/store-3-512.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store-3-512.jpg -------------------------------------------------------------------------------- /assets/store/store-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store-3.jpg -------------------------------------------------------------------------------- /assets/store/store-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store-3.png -------------------------------------------------------------------------------- /assets/store/store-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store-4.png -------------------------------------------------------------------------------- /assets/store/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/assets/store/store.png -------------------------------------------------------------------------------- /assets/store/store.svg: -------------------------------------------------------------------------------- 1 | icon-stage- copy 3@3x -------------------------------------------------------------------------------- /documents/ABOUT.md: -------------------------------------------------------------------------------- 1 | # Thông Tin 2 | 3 | **Tin Mới** là ứng dụng đọc báo đơn giản, tổng hợp tin tức tự động. Ứng dụng cho phép bạn đọc nhanh các tin tức nóng và mới nhất trong ngày được chọn lọc từ hơn các báo điện tử tại Việt Nam. 4 | cung cấp các chức năng chính : 5 | * Duyệt các tin tức nóng hổi 6 | * Nguồn tin tức phong phú 7 | * Nhiều thể loại tin tức phong phú 8 | * Lưu trữ tin tức offline 9 | * Dễ dàng chia sẻ thông tin 10 | * ... 11 | 12 | ## Công nghệ 13 | 14 | Ứng dụng là sản phẩm của quá trình nghiên cứu công nghệ "Flutter" nhằm viết ứng dụng đa nền tảng với một codebase duy nhất 15 | Ứng dụng có thời gian hoàn thiện 5 ngày: từ ban đầu cho đến khi hoàn thiện thành sản phẩm bao gồm: design, logic, source structure, ... 16 | 17 | ## Tác giả 18 | [Hungdt](https://zrg-team.github.io/store/) 19 | 20 | ## Source 21 | https://github.com/zrg-team/flutter_redux 22 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/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/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zrg-team/flutter_redux/cb8b8600f0ce3ffb5f732bb7f89cf40199c39b35/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | cat_dog 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAppTransportSecurity 32 | 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/common/actions/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:cat_dog/common/state.dart'; 3 | import 'package:cat_dog/common/repository.dart'; 4 | 5 | class SetUserLanguage { 6 | final String language; 7 | SetUserLanguage(this.language); 8 | } 9 | 10 | class SetAboutInformation { 11 | final String data; 12 | SetAboutInformation(this.data); 13 | } 14 | 15 | class SetFirstOpen { 16 | final bool first; 17 | SetFirstOpen(this.first); 18 | } 19 | 20 | class AddReadingCount { 21 | AddReadingCount(); 22 | } 23 | 24 | class ClearReadingCount { 25 | ClearReadingCount(); 26 | } 27 | 28 | final Function actionSetUserLanguage = (String language) { 29 | return (Store store) { 30 | if (language != '') { 31 | store.dispatch(new SetUserLanguage(language)); 32 | } 33 | }; 34 | }; 35 | 36 | final Function getAboutAction = () { 37 | return (Store store) async { 38 | var result = await fetchAboutInformation(); 39 | store.dispatch(SetAboutInformation(result)); 40 | }; 41 | }; 42 | 43 | final Function setFirstOpenAction = () { 44 | return (Store store) async { 45 | store.dispatch(SetFirstOpen(false)); 46 | }; 47 | }; 48 | 49 | final Function addReadingCountAction = () { 50 | return (Store store) async { 51 | store.dispatch(AddReadingCount()); 52 | }; 53 | }; 54 | 55 | final Function clearReadingCountAction = () { 56 | return (Store store) async { 57 | store.dispatch(ClearReadingCount()); 58 | }; 59 | }; -------------------------------------------------------------------------------- /lib/common/components/BottomBar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:cat_dog/presentation/platform_adaptive.dart'; 4 | import 'package:cat_dog/styles/texts.dart'; 5 | 6 | class BottomBar extends StatelessWidget { 7 | 8 | final int _index; 9 | final Function onTap; 10 | final double barHeight = 66.0; 11 | 12 | BottomBar(this._index, this.onTap); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final double statusBarHeight = MediaQuery 17 | .of(context) 18 | .padding 19 | .top; 20 | 21 | return new Container( 22 | padding: new EdgeInsets.only(top: statusBarHeight), 23 | height: statusBarHeight + barHeight, 24 | decoration: new BoxDecoration( 25 | gradient: new LinearGradient( 26 | colors: [ 27 | AppColors.appBarGradientStart, 28 | AppColors.appBarGradientEnd 29 | ], 30 | begin: const FractionalOffset(0.0, 0.0), 31 | end: const FractionalOffset(0.5, 0.0), 32 | stops: [0.0, 0.5], 33 | tileMode: TileMode.clamp 34 | ), 35 | ), 36 | child: PlatformAdaptiveBottomBar( 37 | currentIndex: _index, 38 | onTap: onTap, 39 | items: TabItems.map((TabItem item) { 40 | return new BottomNavigationBarItem( 41 | title: new Text( 42 | item.title, 43 | style: textStyles['bottom_label'], 44 | ), 45 | icon: new Icon(item.icon), 46 | ); 47 | }).toList(), 48 | ) 49 | ); 50 | } 51 | } 52 | 53 | class TabItem { 54 | final String title; 55 | final IconData icon; 56 | 57 | const TabItem({ this.title, this.icon }); 58 | } 59 | 60 | const List TabItems = const [ 61 | const TabItem(title: 'Hot', icon: Icons.assignment), 62 | const TabItem(title: 'Categories', icon: Icons.category), 63 | const TabItem(title: 'Discover', icon: Icons.group_work) 64 | ]; -------------------------------------------------------------------------------- /lib/common/components/ContentLoading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | class ContentLoading extends StatelessWidget { 5 | 6 | ContentLoading(); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Shimmer.fromColors( 11 | baseColor: Colors.grey[300], 12 | highlightColor: Colors.grey[100], 13 | child: Column( 14 | crossAxisAlignment: CrossAxisAlignment.start, 15 | children: [ 16 | Container( 17 | width: MediaQuery.of(context).size.width - 40.0, 18 | height: 8.0, 19 | margin: EdgeInsets.only(left: 40), 20 | color: Colors.white, 21 | ), 22 | Padding( 23 | padding: 24 | const EdgeInsets.symmetric(vertical: 4.0), 25 | ), 26 | Container( 27 | width: double.infinity, 28 | height: 8.0, 29 | color: Colors.white, 30 | ), 31 | Padding( 32 | padding: 33 | const EdgeInsets.symmetric(vertical: 4.0), 34 | ), 35 | Container( 36 | width: double.infinity, 37 | height: 8.0, 38 | color: Colors.white, 39 | ), 40 | Padding( 41 | padding: 42 | const EdgeInsets.symmetric(vertical: 4.0), 43 | ), 44 | Container( 45 | width: 100.0, 46 | height: 8.0, 47 | color: Colors.white, 48 | ), 49 | Padding( 50 | padding: 51 | const EdgeInsets.symmetric(vertical: 4.0), 52 | ), 53 | Container( 54 | width: double.infinity, 55 | height: 300, 56 | color: Colors.white, 57 | ), 58 | Container( 59 | width: double.infinity, 60 | height: 8.0, 61 | color: Colors.white, 62 | ), 63 | Padding( 64 | padding: 65 | const EdgeInsets.symmetric(vertical: 4.0), 66 | ), 67 | Container( 68 | width: double.infinity, 69 | height: 8.0, 70 | color: Colors.white, 71 | ), 72 | Padding( 73 | padding: 74 | const EdgeInsets.symmetric(vertical: 4.0), 75 | ), 76 | Container( 77 | width: 40.0, 78 | height: 8.0, 79 | color: Colors.white, 80 | ), 81 | Padding( 82 | padding: 83 | const EdgeInsets.symmetric(vertical: 4.0), 84 | ) 85 | ] 86 | ) 87 | ); 88 | } 89 | } -------------------------------------------------------------------------------- /lib/common/components/Form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import './InputFields.dart'; 3 | 4 | class FormContainer extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return (new Container( 8 | margin: new EdgeInsets.symmetric(horizontal: 20.0), 9 | child: new Column( 10 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 11 | children: [ 12 | new Form( 13 | child: new Column( 14 | mainAxisAlignment: MainAxisAlignment.spaceAround, 15 | children: [ 16 | new InputFieldArea( 17 | hint: "Username", 18 | obscureText: false, 19 | icon: Icons.person_outline, 20 | ), 21 | new InputFieldArea( 22 | hint: "Password", 23 | obscureText: true, 24 | icon: Icons.lock_outline, 25 | ), 26 | ], 27 | )), 28 | ], 29 | ), 30 | )); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/common/components/FrostedGlassAppBar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/styles/colors.dart'; 4 | 5 | class FrostedGlassAppBar extends StatelessWidget { 6 | 7 | final String title; 8 | final dynamic iconLeftButton; 9 | final Function onPressLeftButton; 10 | final dynamic iconRighButton; 11 | final Function onPressRightButton; 12 | final double barHeight = 60.0; 13 | 14 | FrostedGlassAppBar(this.title, this.iconLeftButton, this.onPressLeftButton, this.iconRighButton, this.onPressRightButton); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final double statusBarHeight = MediaQuery 19 | .of(context) 20 | .padding 21 | .top; 22 | 23 | return new Container( 24 | padding: new EdgeInsets.only(top: 0), 25 | height: statusBarHeight + barHeight, 26 | child: new Center( 27 | child: new ClipRect( 28 | child: new BackdropFilter( 29 | filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), 30 | child: new Container( 31 | padding: new EdgeInsets.only(top: statusBarHeight), 32 | width: MediaQuery.of(context).size.width, 33 | height: statusBarHeight + barHeight, 34 | decoration: new BoxDecoration( 35 | gradient: new LinearGradient( 36 | colors: [ 37 | AppColors.appBarGradientStart.withOpacity(0.8), 38 | AppColors.appBarGradientEnd.withOpacity(0.8) 39 | ], 40 | begin: const FractionalOffset(0.0, 0.0), 41 | end: const FractionalOffset(0.5, 0.0), 42 | stops: [0.0, 0.5], 43 | tileMode: TileMode.clamp 44 | ), 45 | ), 46 | child: Row( 47 | mainAxisSize: MainAxisSize.max, 48 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 49 | children: [ 50 | iconLeftButton != null ? Container( 51 | child: IconButton( 52 | color: AppColors.white, 53 | icon: iconLeftButton, 54 | iconSize: 32, 55 | onPressed: onPressLeftButton 56 | ) 57 | ) : Container(width: 32, height: 32), 58 | Container( 59 | child: new Center( 60 | child: new Text( 61 | title, 62 | style: const TextStyle( 63 | color: Colors.white, 64 | fontWeight: FontWeight.bold, 65 | fontSize: 30.0 66 | ) 67 | ) 68 | ) 69 | ), 70 | iconRighButton != null ? Container( 71 | child: IconButton( 72 | color: AppColors.white, 73 | icon: iconRighButton, 74 | iconSize: 32, 75 | onPressed: onPressRightButton 76 | ) 77 | ) : Container(width: 32, height: 32) 78 | ] 79 | ) 80 | ), 81 | ), 82 | ), 83 | ), 84 | ); 85 | } 86 | } -------------------------------------------------------------------------------- /lib/common/components/GradientAppBar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | 4 | class GradientAppBar extends StatelessWidget { 5 | 6 | final String title; 7 | final dynamic iconLeftButton; 8 | final Function onPressLeftButton; 9 | final dynamic iconRighButton; 10 | final Function onPressRightButton; 11 | final Function onPressRightButtonDownload; 12 | final Function onPressRightButtonShare; 13 | final double barHeight = 60.0; 14 | 15 | GradientAppBar( 16 | this.title, 17 | this.iconLeftButton, 18 | this.onPressLeftButton, 19 | this.iconRighButton, 20 | this.onPressRightButton, 21 | { 22 | dynamic onPressRightButtonDownload, 23 | dynamic onPressRightButtonShare 24 | } 25 | ) : 26 | onPressRightButtonShare = onPressRightButtonShare, 27 | onPressRightButtonDownload = onPressRightButtonDownload, 28 | super(); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final double statusBarHeight = MediaQuery 33 | .of(context) 34 | .padding 35 | .top; 36 | 37 | return new Container( 38 | padding: new EdgeInsets.only(top: statusBarHeight), 39 | height: statusBarHeight + barHeight, 40 | decoration: new BoxDecoration( 41 | gradient: new LinearGradient( 42 | colors: [ 43 | AppColors.appBarGradientStart, 44 | AppColors.appBarGradientEnd 45 | ], 46 | begin: const FractionalOffset(0.0, 0.0), 47 | end: const FractionalOffset(0.5, 0.0), 48 | stops: [0.0, 0.5], 49 | tileMode: TileMode.clamp 50 | ), 51 | ), 52 | child: Row( 53 | mainAxisSize: MainAxisSize.max, 54 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 55 | children: [ 56 | iconLeftButton != null ? Container( 57 | child: IconButton( 58 | color: AppColors.white, 59 | icon: iconLeftButton, 60 | iconSize: 32, 61 | onPressed: onPressLeftButton 62 | ) 63 | ) : Container(width: 32, height: 32), 64 | Container( 65 | child: new Center( 66 | child: new Text( 67 | title, 68 | style: const TextStyle( 69 | color: Colors.white, 70 | fontWeight: FontWeight.bold, 71 | fontSize: 24.0 72 | ) 73 | ) 74 | ) 75 | ), 76 | new Row( 77 | children: [ 78 | iconRighButton != null ? Container( 79 | child: IconButton( 80 | color: AppColors.white, 81 | icon: iconRighButton, 82 | iconSize: 32, 83 | onPressed: onPressRightButton 84 | ) 85 | ) : Container(width: 32, height: 32), 86 | onPressRightButtonDownload != null ? Container( 87 | child: IconButton( 88 | color: AppColors.white, 89 | icon: Icon(Icons.save), 90 | iconSize: 32, 91 | onPressed: onPressRightButtonDownload 92 | ) 93 | ) : Container(width: 0, height: 0), 94 | onPressRightButtonShare != null ? Container( 95 | child: IconButton( 96 | color: AppColors.white, 97 | icon: Icon(Icons.share), 98 | iconSize: 32, 99 | onPressed: onPressRightButtonShare 100 | ) 101 | ) : Container(width: 0, height: 0) 102 | ] 103 | ) 104 | ] 105 | ) 106 | ); 107 | } 108 | } -------------------------------------------------------------------------------- /lib/common/components/ImageCached.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:cat_dog/styles/colors.dart'; 5 | 6 | class ImageCached extends StatelessWidget { 7 | final String url; 8 | final double width; 9 | final double height; 10 | final Widget placeholder; 11 | final String noimage; 12 | final BoxFit fit; 13 | ImageCached({ 14 | Key key, 15 | this.url, 16 | dynamic fit, 17 | dynamic width, 18 | dynamic height, 19 | dynamic noimage, 20 | dynamic placeholder 21 | }) : 22 | noimage = noimage, 23 | fit = fit == null ? BoxFit.cover : fit, 24 | width = width == null ? double.infinity : width, 25 | height = height == null ? double.infinity : height, 26 | placeholder = placeholder != null 27 | ? placeholder 28 | : Center( 29 | child: SpinKitPulse( 30 | color: AppColors.specicalBackgroundColor, 31 | size: 32 32 | ) 33 | ), 34 | super(key: key); 35 | @override 36 | Widget build(BuildContext context) { 37 | return CachedNetworkImage( 38 | imageUrl: url, 39 | width: width, 40 | height: height, 41 | placeholder: placeholder, 42 | errorWidget: noimage == null 43 | ? Icon(Icons.broken_image, color: AppColors.specicalBackgroundColor) 44 | : Image.asset( 45 | noimage, 46 | fit: BoxFit.cover 47 | ), 48 | fit: fit 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/common/components/InputFields.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class InputFieldArea extends StatelessWidget { 4 | final String hint; 5 | final bool obscureText; 6 | final IconData icon; 7 | final Function validator; 8 | final Function onSaved; 9 | InputFieldArea({this.hint, this.obscureText, this.icon, this.validator, this.onSaved}); 10 | @override 11 | Widget build(BuildContext context) { 12 | return (new Container( 13 | decoration: new BoxDecoration( 14 | border: new Border( 15 | bottom: new BorderSide( 16 | width: 0.5, 17 | color: Colors.white24, 18 | ), 19 | ), 20 | ), 21 | child: new TextFormField( 22 | onSaved: onSaved, 23 | obscureText: obscureText, 24 | validator: validator, 25 | style: const TextStyle( 26 | color: Colors.white, 27 | ), 28 | decoration: new InputDecoration( 29 | icon: new Icon( 30 | icon, 31 | color: Colors.white, 32 | ), 33 | border: InputBorder.none, 34 | hintText: hint, 35 | hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0), 36 | contentPadding: const EdgeInsets.only( 37 | top: 30.0, right: 30.0, bottom: 30.0, left: 5.0), 38 | ), 39 | ), 40 | )); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/common/components/MiniNewsList.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/common/components/MiniNewsfeed.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:folding_cell/folding_cell.dart'; 5 | import 'package:cat_dog/common/components/Newsfeed.dart'; 6 | 7 | class MiniNewsList extends StatefulWidget { 8 | MiniNewsList({ 9 | Key key, 10 | dynamic metaData, 11 | dynamic folding, 12 | List list, 13 | dynamic controller, 14 | dynamic widget, 15 | dynamic features, 16 | Function onTap, 17 | Function handleRefresh, 18 | }) : 19 | metaData = metaData != null ? metaData : false, 20 | folding = folding != null ? folding : false, 21 | list = list, 22 | onTap = onTap, 23 | parentWidget = widget, 24 | controller = controller, 25 | handleRefresh = handleRefresh, 26 | super(key: key); 27 | 28 | final List list; 29 | final ScrollController controller; 30 | final Function onTap; 31 | final Function handleRefresh; 32 | final parentWidget; 33 | final bool metaData; 34 | final bool folding; 35 | 36 | @override 37 | _MiniNewsListState createState() => new _MiniNewsListState(); 38 | } 39 | class _MiniNewsListState extends State { 40 | Widget _buildItem( 41 | BuildContext context, int index) { 42 | if (widget.list[index] == null) { 43 | return new Container(); 44 | } 45 | if (widget.folding == true && widget.list[index]['news'] != null && widget.list[index]['news'].length >= 2) { 46 | return SimpleFoldingCell( 47 | frontWidget: MiniNewsfeed( 48 | item: widget.list[index], 49 | metaData: widget.metaData, 50 | imageWidth: 120.0, 51 | imageHeight: 120.0, 52 | folding: true 53 | ), 54 | innerTopWidget: Newsfeed( 55 | item: widget.list[index]['news'][0], 56 | onTap: (seleted) { 57 | pushByName('/reading', context, { 'news': seleted }); 58 | }, 59 | onDownload: null, 60 | onShare: null, 61 | onRemove: null 62 | ), 63 | innerBottomWidget: Newsfeed( 64 | item: widget.list[index]['news'][1], 65 | onTap: (seleted) { 66 | pushByName('/reading', context, { 'news': seleted }); 67 | }, 68 | onDownload: null, 69 | onShare: null, 70 | onRemove: null 71 | ), 72 | cellSize: Size(MediaQuery.of(context).size.width, 165), 73 | padding: EdgeInsets.all(0.0), 74 | ); 75 | } 76 | return MiniNewsfeed( 77 | item: widget.list[index], 78 | metaData: widget.metaData, 79 | onTap: (seleted) { 80 | if (widget.onTap != null) { 81 | widget.onTap(seleted); 82 | } else { 83 | pushByName('/reading', context, { 'news': seleted }); 84 | } 85 | } 86 | ); 87 | } 88 | @override 89 | Widget build(BuildContext context) { 90 | return ListView.builder( 91 | controller: widget.controller, 92 | itemCount: widget.list.length, 93 | itemBuilder: _buildItem, 94 | ); 95 | } 96 | } -------------------------------------------------------------------------------- /lib/common/components/MiniNewsfeed.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:timeago/timeago.dart' as timeago; 4 | 5 | class MiniNewsfeed extends StatelessWidget { 6 | const MiniNewsfeed( 7 | { 8 | Key key, 9 | this.onTap, 10 | dynamic folding, 11 | dynamic metaData, 12 | dynamic imageWidth, 13 | dynamic imageHeight, 14 | @required this.item 15 | }) 16 | : assert(item != null), 17 | imageWidth = imageWidth != null ? imageWidth : 100.0, 18 | imageHeight = imageHeight != null ? imageHeight : 100.0, 19 | metaData = metaData != null ? metaData : false, 20 | folding = folding != null ? folding : false, 21 | super(key: key); 22 | final Function onTap; 23 | final dynamic item; 24 | final bool metaData; 25 | final bool folding; 26 | final double imageWidth; 27 | final double imageHeight; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return new Card( 32 | elevation: 1.7, 33 | child: new Container( 34 | decoration: new BoxDecoration(color: AppColors.white), 35 | child: new Padding( 36 | padding: new EdgeInsets.all(5.0), 37 | child: new Column( 38 | children: [ 39 | new GestureDetector( 40 | onTap: !folding ? () { 41 | onTap != null && onTap(item); 42 | } : null, 43 | child: new Row( 44 | children: [ 45 | new Column( 46 | children: [ 47 | new Padding( 48 | padding: 49 | new EdgeInsets.only(top: 8.0), 50 | child: new SizedBox( 51 | height: imageWidth, 52 | width: imageHeight, 53 | child: Hero( 54 | tag: "mini-news-feed-${item['url']}", 55 | child: new Image.network( 56 | item['image'], 57 | fit: BoxFit.cover, 58 | ) 59 | ), 60 | ), 61 | ) 62 | ] 63 | ), 64 | new Expanded( 65 | child: new Column( 66 | mainAxisAlignment: MainAxisAlignment.start, 67 | crossAxisAlignment: 68 | CrossAxisAlignment.start, 69 | children: [ 70 | this.metaData != null ? new Row( 71 | children: [ 72 | new Padding( 73 | padding: new EdgeInsets.only(left: 4.0), 74 | child: new Text( 75 | timeago.format(DateTime.parse(item['time']), allowFromNow: true), 76 | style: new TextStyle( 77 | fontWeight: FontWeight.w400, 78 | color: Colors.grey[600], 79 | ), 80 | ), 81 | ), 82 | new Padding( 83 | padding: new EdgeInsets.all(5.0), 84 | child: new Text( 85 | item['source'], 86 | style: new TextStyle( 87 | fontWeight: FontWeight.w500, 88 | color: Colors.grey[700], 89 | ), 90 | ), 91 | ), 92 | ], 93 | ) : new Container(), 94 | new Padding( 95 | padding: new EdgeInsets.only( 96 | left: 4.0, 97 | right: 8.0, 98 | bottom: 8.0, 99 | top: 8.0), 100 | child: new Text( 101 | item['heading'].trim(), 102 | style: new TextStyle( 103 | fontWeight: FontWeight.bold, 104 | ), 105 | ), 106 | ) 107 | ], 108 | ) 109 | ) 110 | ] 111 | ) 112 | ) 113 | ] 114 | ) 115 | ) 116 | ) 117 | ); 118 | } 119 | } -------------------------------------------------------------------------------- /lib/common/components/SpinLoading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:cat_dog/styles/colors.dart'; 4 | 5 | class SpinLoading extends StatelessWidget { 6 | final bool overlay; 7 | final Color iconColor; 8 | SpinLoading({ Key key, dynamic overlay, dynamic iconColor }) : 9 | overlay = overlay == null ? false : overlay, 10 | iconColor = iconColor == null ? AppColors.specicalBackgroundColor : iconColor, 11 | super(key: key); 12 | @override 13 | Widget build(BuildContext context) { 14 | return (new Container( 15 | decoration: new BoxDecoration( 16 | color: overlay ? Colors.transparent : AppColors.commonBackgroundColor 17 | ), 18 | child: new Center( 19 | child: SpinKitDoubleBounce( 20 | color: iconColor, 21 | size: 100 22 | ) 23 | ) 24 | )); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/common/configs.dart: -------------------------------------------------------------------------------- 1 | const String GET_NEWS_API = 'https://m.baomoi.com'; 2 | const String DEFAULT_URL = 'https://baomoi.com'; 3 | 4 | const String FEEDBACK_URL = 'https://goo.gl/forms/lGaQ86rtkEKhXLZ22'; 5 | const String ABOUT_URL = 'https://raw.githubusercontent.com/zrg-team/flutter_redux/develop/documents/ABOUT.md'; 6 | 7 | const List SOURCE_NEWS = [ 8 | { 9 | 'name': 'Zing', 10 | 'image': 'assets/images/zing.png', 11 | 'url': 'https://m.baomoi.com/bao-tri-thuc-truc-tuyen-zing/p/119.epi' 12 | }, 13 | { 14 | 'name': 'VOV', 15 | 'image': 'assets/images/vov.jpg', 16 | 'url': 'https://m.baomoi.com/bao-vov-vov/p/65.epi' 17 | }, 18 | { 19 | 'name': 'Thanh Niên', 20 | 'image': 'assets/images/thanhnien.jpg', 21 | 'url': 'https://m.baomoi.com/bao-thanh-nien-thanh-nien/p/19.epi' 22 | }, 23 | { 24 | 'name': 'Lao Động', 25 | 'image': 'assets/images/laodong.jpg', 26 | 'url': 'https://m.baomoi.com/bao-lao-dong-lao-dong/p/12.epi' 27 | }, 28 | { 29 | 'name': 'Vietnam Plus', 30 | 'image': 'assets/images/vietnamplus.jpg', 31 | 'url': 'https://m.baomoi.com/bao-vietnamplus-vietnamplus/p/293.epi' 32 | }, 33 | { 34 | 'name': 'Vietnam Net', 35 | 'image': 'assets/images/vietnamnet.png', 36 | 'url': 'https://m.baomoi.com/bao-vietnamnet-vietnamnet/p/23.epi' 37 | }, 38 | { 39 | 'name': 'VTC', 40 | 'image': 'assets/images/vtc.png', 41 | 'url': 'https://m.baomoi.com/bao-vtc-news-vtc/p/83.epi' 42 | }, 43 | { 44 | 'name': 'SaoStart', 45 | 'image': 'assets/images/saostart.jpg', 46 | 'url': 'https://m.baomoi.com/saostar-saostar/p/329.epi' 47 | }, 48 | { 49 | 'name': 'VN Media', 50 | 'image': 'assets/images/vnmedia.png', 51 | 'url': 'https://m.baomoi.com/vnmedia-vnmedia/p/22.epi' 52 | }, 53 | { 54 | 'name': 'ICT News', 55 | 'image': 'assets/images/ictnews.png', 56 | 'url': 'https://m.baomoi.com/ictnews-ictnews/p/107.epi' 57 | }, 58 | { 59 | 'name': 'Tiền Phong', 60 | 'image': 'assets/images/tienphong.png', 61 | 'url': 'https://m.baomoi.com/bao-tien-phong-tien-phong/p/20.epi' 62 | }, 63 | { 64 | 'name': 'Kiến Thức', 65 | 'image': 'assets/images/kienthuc.jpeg', 66 | 'url': 'https://m.baomoi.com/bao-kien-thuc-kien-thuc/p/180.epi' 67 | }, 68 | { 69 | 'name': 'Petro Times', 70 | 'image': 'assets/images/petrotimes.png', 71 | 'url': 'https://m.baomoi.com/bao-nang-luong-moi-petrotimes/p/232.epi' 72 | }, 73 | { 74 | 'name': 'Chính Phủ', 75 | 'image': 'assets/images/chinhphu.jpg', 76 | 'url': 'https://m.baomoi.com/bao-chinh-phu-chinh-phu/p/146.epi' 77 | }, 78 | { 79 | 'name': 'Chính Phủ', 80 | 'image': 'assets/images/phapluat.png', 81 | 'url': 'https://m.baomoi.com/bao-chinh-phu-chinh-phu/p/146.epi' 82 | } 83 | ]; 84 | 85 | 86 | const String APP_ID = 'com.zrgteam.tinmoi'; 87 | const String ADMOB_APP_ID = 'ca-app-pub-6308632307209872~8632944973'; 88 | 89 | const int SHOW_ADS_COUNT = 5; 90 | const int SHOW_ADS_COUNT_MAX = 10; 91 | const String READING_ADS_ID = 'ca-app-pub-6308632307209872/4190797775'; 92 | -------------------------------------------------------------------------------- /lib/common/reducers/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:cat_dog/common/state.dart'; 2 | import 'package:cat_dog/modules/user/reducer.dart'; 3 | import 'package:cat_dog/common/reducers/common.dart'; 4 | import 'package:cat_dog/modules/dashboard/reducer.dart'; 5 | import 'package:cat_dog/modules/soccer/reducer.dart'; 6 | 7 | AppState appReducer(AppState state, action){ 8 | return new AppState( 9 | user: authReducer(state.user, action), 10 | common: commonReducer(state.common, action), 11 | dashboard: dashboardReducer(state.dashboard, action), 12 | soccer: soccerReducer(state.soccer, action) 13 | ); 14 | } -------------------------------------------------------------------------------- /lib/common/reducers/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:cat_dog/common/actions/common.dart'; 3 | import 'package:cat_dog/common/states/common.dart'; 4 | 5 | Reducer commonReducer = combineReducers([ 6 | new TypedReducer(setFirstOpenReducer), 7 | new TypedReducer(setUserLanguageReducer), 8 | new TypedReducer(setAboutInformationReducer), 9 | new TypedReducer(addReadingCountReducer), 10 | new TypedReducer(clearReadingCountReducer), 11 | ]); 12 | 13 | CommonState setUserLanguageReducer(CommonState common, SetUserLanguage action) { 14 | return common.copyWith( 15 | language: action.language 16 | ); 17 | } 18 | 19 | CommonState setAboutInformationReducer(CommonState common, SetAboutInformation action) { 20 | return common.copyWith( 21 | about: action.data 22 | ); 23 | } 24 | 25 | CommonState setFirstOpenReducer(CommonState common, SetFirstOpen action) { 26 | return common.copyWith( 27 | first: action.first 28 | ); 29 | } 30 | 31 | CommonState addReadingCountReducer(CommonState common, AddReadingCount action) { 32 | return common.copyWith( 33 | readingCount: (common.readingCount ?? 0) + 1 34 | ); 35 | } 36 | 37 | CommonState clearReadingCountReducer(CommonState common, ClearReadingCount action) { 38 | return common.copyWith( 39 | readingCount: 0 40 | ); 41 | } -------------------------------------------------------------------------------- /lib/common/repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:cat_dog/common/configs.dart'; 3 | 4 | Future fetchAboutInformation() async { 5 | const url = ABOUT_URL; 6 | var response = await http.get(url); 7 | // print("Response status: ${response.statusCode}"); 8 | // print("Response body: ${response.body}"); 9 | return response.body; 10 | } -------------------------------------------------------------------------------- /lib/common/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:cat_dog/common/states/common.dart'; 3 | import 'package:cat_dog/modules/user/state.dart'; 4 | import 'package:cat_dog/modules/dashboard/state.dart'; 5 | import 'package:cat_dog/modules/soccer/state.dart'; 6 | 7 | @immutable 8 | class AppState { 9 | final UserState user; 10 | final CommonState common; 11 | final DashboardState dashboard; 12 | final SoccerState soccer; 13 | 14 | AppState({ UserState user, CommonState common, DashboardState dashboard, SoccerState soccer }): 15 | user = user ?? new UserState(), 16 | dashboard = dashboard ?? new DashboardState(), 17 | common = common ?? new CommonState(), 18 | soccer = soccer ?? new SoccerState(); 19 | 20 | static AppState rehydrationJSON(dynamic json) { 21 | return new AppState( 22 | user: json != null ? new UserState.fromJSON(json['user']) : new UserState(), 23 | common: json != null ? new CommonState.fromJSON(json['common']) : new CommonState(), 24 | dashboard: json != null ? new DashboardState.fromJSON(json['dashboard']) : new DashboardState(), 25 | soccer: json != null ? new SoccerState.fromJSON(json['soccer']) : new SoccerState() 26 | ); 27 | } 28 | 29 | Map toJson() => { 30 | 'user': user.toJSON(), 31 | 'common': common.toJSON(), 32 | 'dashboard': dashboard.toJSON(), 33 | 'soccer': soccer.toJSON() 34 | }; 35 | 36 | AppState copyWith({ 37 | bool rehydrated, 38 | UserState user, 39 | CommonState common, 40 | DashboardState dashboard, 41 | SoccerState soccer, 42 | }) { 43 | return new AppState( 44 | user: user ?? this.user, 45 | common: common ?? this.common, 46 | dashboard: dashboard ?? this.dashboard, 47 | soccer: soccer ?? this.soccer 48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/common/states/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | @immutable 4 | class CommonState { 5 | // properties 6 | final String language; 7 | final String about; 8 | final bool first; 9 | final int readingCount; 10 | 11 | // constructor with default 12 | CommonState({ 13 | this.language = 'en', 14 | this.first = true, 15 | this.about = '', 16 | this.readingCount = 0 17 | }); 18 | 19 | // allows to modify AuthState parameters while cloning previous ones 20 | CommonState copyWith({ 21 | bool first, 22 | String about, 23 | String language, 24 | int readingCount, 25 | }) { 26 | return new CommonState( 27 | first: first ?? this.first, 28 | about: about ?? this.about, 29 | language: language ?? this.language, 30 | readingCount: readingCount ?? this.readingCount 31 | ); 32 | } 33 | 34 | factory CommonState.fromJSON(Map json) => new CommonState( 35 | first: json['first'], 36 | language: json['language'], 37 | about: json['about'], 38 | readingCount: json['readingCount'] 39 | ); 40 | 41 | Map toJSON() => { 42 | 'first': this.first, 43 | 'language': this.language, 44 | 'about': this.about, 45 | 'readingCount': this.readingCount 46 | }; 47 | 48 | @override 49 | String toString() { 50 | return '''{ 51 | first: $first, 52 | language: $language, 53 | about: $about, 54 | readingCount: $readingCount 55 | }'''; 56 | } 57 | } -------------------------------------------------------------------------------- /lib/common/store.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:redux_persist/redux_persist.dart'; 3 | import 'package:redux_persist_flutter/redux_persist_flutter.dart'; 4 | import 'package:redux_thunk/redux_thunk.dart'; 5 | import 'package:cat_dog/common/reducers/app.dart'; 6 | import 'package:cat_dog/common/state.dart'; 7 | import 'package:redux_logging/redux_logging.dart'; 8 | 9 | Future> createStore() async { 10 | final persistor = Persistor( 11 | storage: FlutterStorage(), 12 | serializer: JsonSerializer(AppState.rehydrationJSON) 13 | ); 14 | // Load initial state 15 | var initialState; 16 | try { 17 | initialState = await persistor.load(); 18 | } catch (err) {} 19 | final store = Store( 20 | appReducer, 21 | initialState: initialState ?? AppState(), 22 | middleware: [ 23 | thunkMiddleware, 24 | persistor.createMiddleware(), 25 | new LoggingMiddleware.printer() 26 | ], 27 | ); 28 | 29 | return store; 30 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:redux/redux.dart'; 5 | import 'package:cat_dog/common/store.dart'; 6 | import 'package:cat_dog/common/state.dart'; 7 | import 'package:cat_dog/styles/colors.dart'; 8 | import 'package:cat_dog/presentation/platform_adaptive.dart'; 9 | // PAGES 10 | import 'package:cat_dog/pages/HomePage.dart'; 11 | 12 | void main() async { 13 | final store = await createStore(); 14 | runApp(new App( 15 | store: store 16 | )); 17 | } 18 | 19 | class App extends StatelessWidget { 20 | final Store store; 21 | 22 | const App({Key key, this.store}) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return StoreProvider( 27 | store: store, 28 | child: new MaterialApp( 29 | title: 'Tin Mới', 30 | color: AppColors.commonBackgroundColor, 31 | debugShowCheckedModeBanner: false, 32 | showSemanticsDebugger: false, 33 | theme: defaultTargetPlatform == TargetPlatform.iOS 34 | ? kIOSTheme 35 | : kDefaultTheme, 36 | routes: { 37 | // Login feature comming soon 38 | '/': (BuildContext context) => new StoreConnector( 39 | converter: (store) { 40 | return store.state.common.first; 41 | }, 42 | builder: (BuildContext context, first) => new HomePage() 43 | ) 44 | } 45 | ) 46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /lib/modules/category/actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:html/parser.dart' show parse; 2 | import 'package:cat_dog/modules/category/repository.dart'; 3 | 4 | 5 | getNewsFromUrl (String url,int page) async { 6 | String urlPage = "${url.replaceAll('.epi', '')}/trang$page.epi?loadmore=1"; 7 | String result = await fetchNewsFromUrl(urlPage); 8 | if (result != '') { 9 | List data = new List(); 10 | var document = parse(result); 11 | var elements = document 12 | .getElementsByTagName('body')[0] 13 | .getElementsByClassName('story'); 14 | int i; 15 | int length = elements.length; 16 | for (i = 0; i < length; i++) { 17 | try { 18 | var element = elements[i]; 19 | var tagLink = element.getElementsByClassName('story__link')[0]; 20 | var tagHeading = element.getElementsByClassName('story__heading')[0]; 21 | var tagSummary = element.getElementsByClassName('story__summary')[0]; 22 | var tagImage = element.getElementsByTagName('img')[0]; 23 | var tagMeta = element.getElementsByClassName('story__meta')[0]; 24 | var item = { 25 | 'url': tagLink.attributes['href'], 26 | 'heading': tagHeading.innerHtml, 27 | 'summary': tagSummary.innerHtml, 28 | 'source': tagMeta.getElementsByClassName('source')[0].innerHtml, 29 | 'time': tagMeta.getElementsByTagName('time')[0].attributes['datetime'], 30 | 'image': tagImage.attributes['data-src'] 31 | }; 32 | data.add(item); 33 | } catch (err) { 34 | print(err); 35 | } 36 | } 37 | if (data.length > 0) { 38 | return data; 39 | } 40 | } 41 | return []; 42 | } 43 | 44 | getTopics (int page) async { 45 | try { 46 | String urlPage = "https://m.baomoi.com/chu-de/trang$page.epi?loadmore=1"; 47 | String result = await fetchNewsFromUrl(urlPage); 48 | if (result != '') { 49 | List data = new List(); 50 | var document = parse(result); 51 | var elements = document 52 | .getElementsByTagName('body')[0] 53 | .getElementsByClassName('topic-timeline'); 54 | int i; 55 | int length = elements.length; 56 | for (i = 0; i < length; i++) { 57 | try { 58 | var item = parsetTopicItem(elements[i]); 59 | data.add(item); 60 | } catch (err) { 61 | } 62 | } 63 | if (data.length > 0) { 64 | return { 65 | 'data': data 66 | }; 67 | } 68 | } 69 | } catch (err) { 70 | } 71 | return { 72 | 'data': [] 73 | }; 74 | } 75 | 76 | parsetTopicItem (element) { 77 | var tagTopic = element.getElementsByTagName('h2')[0].getElementsByTagName('a')[0]; 78 | var storyTag = element.getElementsByClassName('story')[0]; 79 | var tagSummary = storyTag.getElementsByClassName('story__summary')[0]; 80 | var tagImage = storyTag.getElementsByTagName('img')[0]; 81 | var tagMeta = storyTag.getElementsByClassName('story__meta')[0]; 82 | return { 83 | 'url': tagTopic.attributes['href'], 84 | 'heading': tagTopic.innerHtml, 85 | 'summary': tagSummary.innerHtml, 86 | 'source': tagMeta.getElementsByClassName('source')[0].innerHtml, 87 | 'time': tagMeta.getElementsByTagName('time')[0].attributes['datetime'], 88 | 'image': tagImage.attributes['data-src'] 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /lib/modules/category/components/SubNewsView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:cat_dog/pages/ContentLoadingPage.dart'; 4 | import 'package:cat_dog/modules/category/actions.dart'; 5 | import 'package:cat_dog/common/components/NewsList.dart'; 6 | 7 | class SubNewsView extends StatefulWidget { 8 | final dynamic view; 9 | final Function saveNews; 10 | final BuildContext scaffoldContext; 11 | const SubNewsView({ 12 | Key key, 13 | this.view, 14 | this.saveNews, 15 | this.scaffoldContext 16 | }) : super(key: key); 17 | 18 | @override 19 | _SubNewsViewState createState() => new _SubNewsViewState(); 20 | } 21 | 22 | class _SubNewsViewState extends State { 23 | bool loading = true; 24 | bool onLoadMore = false; 25 | int page = 1; 26 | GlobalKey pageKey = new GlobalKey(); 27 | List list = []; 28 | // ScrollController controller = new ScrollController(); 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | Future.delayed(const Duration(milliseconds: 180), () { 34 | getNews(true); 35 | }); 36 | // controller.addListener(() { 37 | // if (controller.offset >= controller.position.maxScrollExtent - 100 && !onLoadMore) { 38 | // setState(() { 39 | // page += 1; 40 | // onLoadMore = true; 41 | // }); 42 | // getNews(false); 43 | // } 44 | // }); 45 | } 46 | 47 | getNews (bool replace) async { 48 | try { 49 | List data = await getNewsFromUrl(widget.view['url'], page); 50 | setState(() { 51 | if (replace) { 52 | list = data; 53 | } else { 54 | list.addAll(data); 55 | } 56 | }); 57 | } catch (err) { 58 | print(err); 59 | } 60 | Future.delayed(const Duration(milliseconds: 300), () { 61 | setState(() { 62 | loading = false; 63 | onLoadMore = false; 64 | }); 65 | }); 66 | } 67 | 68 | @override 69 | void dispose() { 70 | // controller.removeListener(() {}); 71 | // controller.dispose(); 72 | super.dispose(); 73 | } 74 | 75 | void handleRefresh (dynamic refreshController, bool isUp) { 76 | try { 77 | if (isUp) { 78 | Future.delayed(Duration(microseconds: 320), () { 79 | getNews(true); 80 | }); 81 | } else { 82 | setState(() { 83 | page += 1; 84 | onLoadMore = true; 85 | }); 86 | getNews(false); 87 | } 88 | } catch (err) { 89 | } 90 | refreshController.sendBack(isUp, 3); // Status completed 91 | } 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return ContentLoadingPage( 96 | key: pageKey, 97 | loading: loading, 98 | component: Container( 99 | decoration: BoxDecoration(color: AppColors.commonBackgroundColor), 100 | child: NewsList( 101 | list: list, 102 | widget: widget, 103 | // controller: controller, 104 | handleRefresh: handleRefresh, 105 | features: { 'download': true, 'share': true } 106 | ) 107 | ) 108 | ); 109 | } 110 | } -------------------------------------------------------------------------------- /lib/modules/category/components/TopicItemView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | 4 | class TopicItemView extends StatelessWidget { 5 | TopicItemView(this.news, this.onTap); 6 | final dynamic news; 7 | final Function onTap; 8 | 9 | BoxDecoration _buildShadowAndRoundedCorners() { 10 | return BoxDecoration( 11 | color: AppColors.itemDefaultColor.withOpacity(0.4), 12 | borderRadius: BorderRadius.circular(10.0), 13 | boxShadow: [ 14 | BoxShadow( 15 | spreadRadius: 2.0, 16 | blurRadius: 10.0, 17 | // color: Colors.black26, 18 | ), 19 | ], 20 | ); 21 | } 22 | 23 | Widget _buildThumbnail() { 24 | return ClipRRect( 25 | borderRadius: BorderRadius.circular(8.0), 26 | child: Stack( 27 | children: [ 28 | Image.network(news['image']) 29 | // Positioned( 30 | // bottom: 12.0, 31 | // right: 12.0, 32 | // child: _buildPlayButton(), 33 | // ), 34 | ], 35 | ), 36 | ); 37 | } 38 | 39 | Widget _buildInfo() { 40 | return Padding( 41 | padding: const EdgeInsets.only(top: 16.0, left: 4.0, right: 4.0), 42 | child: Text( 43 | news['heading'], 44 | style: TextStyle(color: Colors.white.withOpacity(0.85)), 45 | ), 46 | ); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return FlatButton( 52 | onPressed: () { 53 | onTap(news); 54 | }, 55 | child: Container( 56 | width: 175.0, 57 | height: 245, 58 | padding: const EdgeInsets.all(5.0), 59 | margin: const EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.0), 60 | decoration: _buildShadowAndRoundedCorners(), 61 | child: Column( 62 | crossAxisAlignment: CrossAxisAlignment.start, 63 | children: [ 64 | Flexible(flex: 3, child: _buildThumbnail()), 65 | Flexible(flex: 2, child: _buildInfo()), 66 | ] 67 | ) 68 | ) 69 | ); 70 | } 71 | } -------------------------------------------------------------------------------- /lib/modules/category/containers/SubNews.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/category/components/SubNewsView.dart'; 6 | import 'package:cat_dog/modules/user/actions.dart'; 7 | 8 | class SubNews extends StatelessWidget { 9 | final dynamic view; 10 | final BuildContext scaffoldContext; 11 | SubNews({Key key, scaffoldContext, view}) : 12 | view = view, 13 | scaffoldContext = scaffoldContext, 14 | super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return new StoreConnector( 19 | converter: (Store store) { 20 | return { 21 | 'saveNews': (item) async => 22 | await saveNewsAction(store, item) 23 | }; 24 | }, 25 | builder: (BuildContext context, props) { 26 | return new SubNewsView( 27 | key: key, 28 | view: view, 29 | saveNews: props['saveNews'], 30 | scaffoldContext: scaffoldContext 31 | ); 32 | } 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/modules/category/repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | 3 | Future fetchNewsFromUrl(url) async { 4 | var response = await http.get(url); 5 | return response.body; 6 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/components/TopicView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:cat_dog/pages/ContentLoadingPage.dart'; 4 | import 'package:cat_dog/common/utils/navigation.dart'; 5 | import 'package:cat_dog/modules/category/actions.dart'; 6 | import 'package:cat_dog/common/components/MiniNewsList.dart'; 7 | 8 | class TopicView extends StatefulWidget { 9 | final BuildContext scaffoldContext; 10 | const TopicView({ 11 | Key key, 12 | this.scaffoldContext 13 | }) : super(key: key); 14 | 15 | @override 16 | _TopicViewState createState() => new _TopicViewState(); 17 | } 18 | 19 | class _TopicViewState extends State { 20 | bool loading = true; 21 | bool onLoadMore = false; 22 | int page = 1; 23 | GlobalKey pageKey = new GlobalKey(); 24 | List list = []; 25 | ScrollController controller = new ScrollController(); 26 | @override 27 | void initState() { 28 | super.initState(); 29 | 30 | Future.delayed(const Duration(milliseconds: 180), () { 31 | getNews(true); 32 | }); 33 | 34 | controller.addListener(() { 35 | if (controller.offset >= controller.position.maxScrollExtent - 100 && !onLoadMore) { 36 | setState(() { 37 | page += 1; 38 | onLoadMore = true; 39 | }); 40 | getNews(false); 41 | } 42 | }); 43 | } 44 | 45 | getNews (bool replace) async { 46 | try { 47 | var data = await getTopics(page); 48 | setState(() { 49 | if (replace) { 50 | list = data['data']; 51 | } else { 52 | list.addAll(data['data']); 53 | } 54 | }); 55 | } catch (err) { 56 | print(err); 57 | } 58 | Future.delayed(const Duration(milliseconds: 200), () { 59 | setState(() { 60 | loading = false; 61 | onLoadMore = false; 62 | }); 63 | }); 64 | } 65 | 66 | @override 67 | void dispose() { 68 | controller.removeListener(() {}); 69 | controller.dispose(); 70 | super.dispose(); 71 | } 72 | 73 | Future handleRefresh () { 74 | return getNews(true); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return ContentLoadingPage( 80 | key: pageKey, 81 | loading: loading, 82 | component: Container( 83 | height: MediaQuery.of(context).size.height, 84 | decoration: BoxDecoration(color: AppColors.commonBackgroundColor), 85 | child: MiniNewsList( 86 | list: list, 87 | widget: widget, 88 | controller: controller, 89 | handleRefresh: handleRefresh, 90 | onTap: (seleted) { 91 | pushByName('/topic-detail', context, { 'topic': seleted }); 92 | }, 93 | ) 94 | ) 95 | ); 96 | } 97 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/components/VideoNewsView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:cat_dog/pages/ContentLoadingPage.dart'; 4 | import 'package:cat_dog/modules/category/actions.dart'; 5 | import 'package:cat_dog/common/components/MiniNewsList.dart'; 6 | 7 | class VideoNewsView extends StatefulWidget { 8 | final BuildContext scaffoldContext; 9 | const VideoNewsView({ 10 | Key key, 11 | this.scaffoldContext 12 | }) : super(key: key); 13 | 14 | @override 15 | _VideoNewsViewState createState() => new _VideoNewsViewState(); 16 | } 17 | 18 | class _VideoNewsViewState extends State { 19 | bool loading = true; 20 | bool onLoadMore = false; 21 | int page = 1; 22 | GlobalKey pageKey = new GlobalKey(); 23 | List list = []; 24 | ScrollController controller = new ScrollController(); 25 | @override 26 | void initState() { 27 | super.initState(); 28 | Future.delayed(const Duration(milliseconds: 180), () { 29 | getNews(true); 30 | }); 31 | controller.addListener(() { 32 | if (controller.offset >= controller.position.maxScrollExtent - 100 && !onLoadMore) { 33 | setState(() { 34 | page += 1; 35 | onLoadMore = true; 36 | }); 37 | getNews(false); 38 | } 39 | }); 40 | } 41 | 42 | getNews (bool replace) async { 43 | try { 44 | List data = await getNewsFromUrl('https://m.baomoi.com/tin-video.epi', page); 45 | setState(() { 46 | if (replace) { 47 | list = data; 48 | } else { 49 | list.addAll(data); 50 | } 51 | }); 52 | } catch (err) { 53 | print(err); 54 | } 55 | Future.delayed(const Duration(milliseconds: 200), () { 56 | setState(() { 57 | loading = false; 58 | onLoadMore = false; 59 | }); 60 | }); 61 | } 62 | 63 | @override 64 | void dispose() { 65 | controller.removeListener(() {}); 66 | controller.dispose(); 67 | super.dispose(); 68 | } 69 | 70 | Future handleRefresh () { 71 | return getNews(true); 72 | } 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return ContentLoadingPage( 77 | key: pageKey, 78 | loading: loading, 79 | component: Container( 80 | decoration: BoxDecoration(color: AppColors.commonBackgroundColor), 81 | child: MiniNewsList( 82 | list: list, 83 | widget: widget, 84 | controller: controller, 85 | metaData: true, 86 | handleRefresh: handleRefresh 87 | ) 88 | ) 89 | ); 90 | } 91 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/containers/ContinueReading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/dashboard/actions.dart'; 6 | import 'package:cat_dog/modules/dashboard/components/ContinueReadingView.dart'; 7 | import 'package:cat_dog/common/actions/common.dart'; 8 | 9 | class ContinueReading extends StatelessWidget { 10 | // final Function refreshCallback; 11 | final Function hideCallback; 12 | final BuildContext scaffoldContext; 13 | ContinueReading({Key key, hideCallback, scaffoldContext}) : 14 | hideCallback = hideCallback, 15 | scaffoldContext = scaffoldContext, 16 | super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new StoreConnector( 21 | converter: (Store store) { 22 | return { 23 | 'checkFirstOpen': () { 24 | if (store.state.common.first) { 25 | store.dispatch(setFirstOpenAction()); 26 | } 27 | }, 28 | 'getHotNews': (page) async => 29 | await getHotNewsAction(store, page), 30 | 'getLatestNews': (page) async => 31 | await getLatestNewsAction(store, page), 32 | 'getTopicNews': (page) async => 33 | await getTopicNewsAction(store, page), 34 | 'getVideoNews': (page) async => 35 | await getVideoNewsAction(store, page), 36 | 'getMoreHotNews': (page) async => 37 | await getMoreHotNewsAction(store, page), 38 | 'getMoreLatestNews': (page) async => 39 | await getMoreLatestNewsAction(store, page), 40 | 'getMoreTopicNews': (page) async => 41 | await getMoreTopicNewsAction(store, page), 42 | 'getMoreVideoNews': (page) async => 43 | await getMoreVideoNewsAction(store, page), 44 | 'shouldLoading': store.state.dashboard.hot == null || store.state.dashboard.hot.length == 0 45 | }; 46 | }, 47 | builder: (BuildContext context, props) { 48 | return new ContinueReadingView( 49 | key: key, 50 | hideCallback: hideCallback, 51 | getHotNews: props['getHotNews'], 52 | getLatestNews: props['getLatestNews'], 53 | getMoreHotNews: props['getMoreHotNews'], 54 | getTopicNews: props['getTopicNews'], 55 | getVideoNews: props['getVideoNews'], 56 | getMoreTopicNews: props['getMoreTopicNews'], 57 | getMoreVideoNews: props['getMoreVideoNews'], 58 | getMoreLatestNews: props['getMoreLatestNews'], 59 | checkFirstOpen: props['checkFirstOpen'], 60 | shouldLoading: props['shouldLoading'], 61 | scaffoldContext: scaffoldContext, 62 | ); 63 | } 64 | ); 65 | } 66 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/containers/ContinueReadingDetail.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/dashboard/components/ContinueReadingDetailView.dart'; 6 | import 'package:cat_dog/common/actions/common.dart'; 7 | 8 | class ContinueReadingDetail extends StatelessWidget { 9 | final dynamic news; 10 | final dynamic lastNews; 11 | final dynamic nextNews; 12 | final dynamic newsKey; 13 | final Function onDismissed; 14 | final BuildContext scaffoldContext; 15 | ContinueReadingDetail({ 16 | Key key, 17 | Object news, 18 | dynamic lastNews, 19 | dynamic nextNews, 20 | dynamic newsKey, 21 | Function onDismissed, 22 | BuildContext scaffoldContext 23 | }) : 24 | news = news, 25 | newsKey = newsKey, 26 | lastNews = lastNews, 27 | nextNews = nextNews, 28 | onDismissed = onDismissed, 29 | scaffoldContext = scaffoldContext, 30 | super(key: key); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return new StoreConnector( 35 | converter: (Store store) { 36 | return { 37 | 'readingCount': store.state.common.readingCount, 38 | 'addReadingCount': () { 39 | store.dispatch(addReadingCountAction()); 40 | }, 41 | 'clearReadingCount': () { 42 | store.dispatch(clearReadingCountAction()); 43 | } 44 | }; 45 | }, 46 | builder: (BuildContext context, props) { 47 | return new ContinueReadingDetailView( 48 | key: key, 49 | news: news, 50 | newsKey: newsKey, 51 | lastNews: lastNews, 52 | nextNews: nextNews, 53 | onDismissed: onDismissed, 54 | scaffoldContext: scaffoldContext, 55 | readingCount: props['readingCount'], 56 | addReadingCount: props['addReadingCount'], 57 | clearReadingCount: props['clearReadingCount'] 58 | ); 59 | } 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/containers/NewsTab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/dashboard/actions.dart'; 6 | import 'package:cat_dog/modules/dashboard/components/NewsTabView.dart'; 7 | import 'package:cat_dog/modules/user/actions.dart'; 8 | import 'package:cat_dog/modules/soccer/actions.dart'; 9 | import 'package:cat_dog/common/actions/common.dart'; 10 | 11 | class NewsTab extends StatelessWidget { 12 | // final Function refreshCallback; 13 | final Function hideCallback; 14 | final BuildContext scaffoldContext; 15 | NewsTab({Key key, hideCallback, scaffoldContext}) : 16 | hideCallback = hideCallback, 17 | scaffoldContext = scaffoldContext, 18 | super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new StoreConnector( 23 | converter: (Store store) { 24 | return { 25 | 'checkFirstOpen': () { 26 | if (store.state.common.first) { 27 | store.dispatch(setFirstOpenAction()); 28 | } 29 | }, 30 | 'getHotNews': (page) async => 31 | await getHotNewsAction(store, page), 32 | 'getLatestNews': (page) async => 33 | await getLatestNewsAction(store, page), 34 | 'getTopicNews': (page) async => 35 | await getTopicNewsAction(store, page), 36 | 'getVideoNews': (page) async => 37 | await getVideoNewsAction(store, page), 38 | 'getMoreHotNews': (page) async => 39 | await getMoreHotNewsAction(store, page), 40 | 'getMoreLatestNews': (page) async => 41 | await getMoreLatestNewsAction(store, page), 42 | 'getMoreTopicNews': (page) async => 43 | await getMoreTopicNewsAction(store, page), 44 | 'getMoreVideoNews': (page) async => 45 | await getMoreVideoNewsAction(store, page), 46 | 'getSoccerCalendar': () => 47 | getTodaySoccerCalendarAction(store), 48 | 'saveNews': (item) async => 49 | await saveNewsAction(store, item), 50 | 'shouldLoading': store.state.dashboard.hot == null || store.state.dashboard.hot.length == 0 51 | }; 52 | }, 53 | builder: (BuildContext context, props) { 54 | return new NewsTabView( 55 | key: key, 56 | hideCallback: hideCallback, 57 | getHotNews: props['getHotNews'], 58 | getLatestNews: props['getLatestNews'], 59 | getMoreHotNews: props['getMoreHotNews'], 60 | getTopicNews: props['getTopicNews'], 61 | getVideoNews: props['getVideoNews'], 62 | getMoreTopicNews: props['getMoreTopicNews'], 63 | getMoreVideoNews: props['getMoreVideoNews'], 64 | getMoreLatestNews: props['getMoreLatestNews'], 65 | checkFirstOpen: props['checkFirstOpen'], 66 | shouldLoading: props['shouldLoading'], 67 | saveNews: props['saveNews'], 68 | getSoccerCalendar: props['getSoccerCalendar'], 69 | scaffoldContext: scaffoldContext, 70 | ); 71 | } 72 | ); 73 | } 74 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/containers/Reading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/dashboard/components/ReadingView.dart'; 6 | import 'package:cat_dog/common/actions/common.dart'; 7 | 8 | class Reading extends StatelessWidget { 9 | final dynamic news; 10 | final bool push; 11 | final BuildContext scaffoldContext; 12 | Reading({ 13 | Key key, 14 | Object news, 15 | dynamic push, 16 | BuildContext scaffoldContext 17 | }) : 18 | news = news, 19 | push = push != null ? push : false, 20 | scaffoldContext = scaffoldContext, 21 | super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return new StoreConnector( 26 | converter: (Store store) { 27 | return { 28 | 'readingCount': store.state.common.readingCount, 29 | 'addReadingCount': () { 30 | store.dispatch(addReadingCountAction()); 31 | }, 32 | 'clearReadingCount': () { 33 | store.dispatch(clearReadingCountAction()); 34 | } 35 | }; 36 | }, 37 | builder: (BuildContext context, props) { 38 | return new ReadingView( 39 | key: key, 40 | news: news, 41 | push: push, 42 | scaffoldContext: scaffoldContext, 43 | readingCount: props['readingCount'], 44 | addReadingCount: props['addReadingCount'], 45 | clearReadingCount: props['clearReadingCount'] 46 | ); 47 | } 48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | 3 | import 'package:cat_dog/modules/dashboard/actions.dart'; 4 | import 'package:cat_dog/modules/dashboard/state.dart'; 5 | 6 | Reducer dashboardReducer = combineReducers([ 7 | new TypedReducer(setHotNewsReducer), 8 | new TypedReducer(appendHotNewsReducer), 9 | new TypedReducer(setLatestNewsReducer), 10 | new TypedReducer(appendLatestNewsReducer), 11 | new TypedReducer(setTopicNewsReducer), 12 | new TypedReducer(appendTopicNewsReducer), 13 | new TypedReducer(setVideoNewsReducer), 14 | new TypedReducer(appendVideoNewsReducer) 15 | ]); 16 | 17 | DashboardState setHotNewsReducer(DashboardState dashboard, SetHotNews action) { 18 | return dashboard.copyWith( 19 | hot: action.news 20 | ); 21 | } 22 | 23 | DashboardState appendHotNewsReducer(DashboardState dashboard, AppendHotNews action) { 24 | dashboard.hot.addAll(action.news); 25 | return dashboard.copyWith( 26 | hot: dashboard.hot 27 | ); 28 | } 29 | 30 | DashboardState setLatestNewsReducer(DashboardState dashboard, SetLatestNews action) { 31 | return dashboard.copyWith( 32 | news: action.news 33 | ); 34 | } 35 | 36 | DashboardState appendLatestNewsReducer(DashboardState dashboard, AppendLatestNews action) { 37 | dashboard.news.addAll(action.news); 38 | return dashboard.copyWith( 39 | news: dashboard.news 40 | ); 41 | } 42 | 43 | DashboardState setTopicNewsReducer(DashboardState dashboard, SetTopicNews action) { 44 | return dashboard.copyWith( 45 | topicNews: action.news 46 | ); 47 | } 48 | 49 | DashboardState appendTopicNewsReducer(DashboardState dashboard, AppendTopicNews action) { 50 | dashboard.topicNews.addAll(action.news); 51 | return dashboard.copyWith( 52 | topicNews: dashboard.topicNews 53 | ); 54 | } 55 | 56 | DashboardState setVideoNewsReducer(DashboardState dashboard, SetVideoNews action) { 57 | return dashboard.copyWith( 58 | videos: action.news 59 | ); 60 | } 61 | 62 | DashboardState appendVideoNewsReducer(DashboardState dashboard, AppendVideoNews action) { 63 | dashboard.videos.addAll(action.news); 64 | return dashboard.copyWith( 65 | videos: dashboard.videos 66 | ); 67 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:cat_dog/common/configs.dart'; 3 | 4 | Future fetchHotNews(int page) async { 5 | String url = GET_NEWS_API + "/trang$page.epi?loadmore=1"; 6 | var response = await http.get(url); 7 | // print("Response status: ${response.statusCode}"); 8 | // print("Response body: ${response.body}"); 9 | return response.body; 10 | } 11 | 12 | Future fetchLatestNews(int page) async { 13 | String url = GET_NEWS_API + "/tin-moi/trang$page.epi?loadmore=1"; 14 | var response = await http.get(url); 15 | return response.body; 16 | } 17 | 18 | Future fetchTopicNews(int page) async { 19 | String url = GET_NEWS_API + "/chu-de/trang$page.epi?loadmore=1"; 20 | var response = await http.get(url); 21 | return response.body; 22 | } 23 | 24 | Future fetchVideoNews(int page) async { 25 | String url = GET_NEWS_API + "/tin-video/trang$page.epi?loadmore=1"; 26 | var response = await http.get(url); 27 | return response.body; 28 | } 29 | 30 | Future fetchDetailNews(item) async { 31 | String url = GET_NEWS_API + item; 32 | var response = await http.get(url); 33 | return response.body; 34 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:meta/meta.dart'; 3 | 4 | @immutable 5 | class DashboardState { 6 | // properties 7 | final List news; 8 | final List hot; 9 | final List topicNews; 10 | final List videos; 11 | 12 | // constructor with default 13 | DashboardState({ 14 | this.news, 15 | this.hot, 16 | this.topicNews, 17 | this.videos 18 | }); 19 | 20 | // allows to modify AuthState parameters while cloning previous ones 21 | DashboardState copyWith({ 22 | List news, 23 | List hot, 24 | List topicNews, 25 | List videos 26 | }) { 27 | return new DashboardState( 28 | news: news ?? this.news, 29 | hot: hot ?? this.hot, 30 | topicNews: topicNews ?? this.topicNews, 31 | videos: videos ?? this.videos 32 | ); 33 | } 34 | 35 | factory DashboardState.fromJSON(Map input) => new DashboardState( 36 | news: json != null ? json.decode(input['news']) : [], 37 | hot: json != null ? json.decode(input['hot']) : [], 38 | topicNews: json != null ? json.decode(input['topicNews']) : [], 39 | videos: json != null ? json.decode(input['videos']) : [] 40 | ); 41 | 42 | Map toJSON() => { 43 | 'news': json.encode(this.news), 44 | 'hot': json.encode(this.hot), 45 | 'topicNews': json.encode(this.topicNews), 46 | 'videos': json.encode(this.videos) 47 | }; 48 | 49 | @override 50 | String toString() { 51 | return '''{ 52 | news: $news 53 | hot: $hot 54 | topicNews: $topicNews 55 | videos: $videos 56 | }'''; 57 | } 58 | } -------------------------------------------------------------------------------- /lib/modules/dashboard/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DashboardColors { 4 | 5 | const DashboardColors(); 6 | //static const Color planetListBackground = const Color(0xFF3E3963); 7 | static const Color textTitle = const Color(0xFFFFFFFF); 8 | static const Color textSummary = const Color(0x66FFFFFF); 9 | 10 | } -------------------------------------------------------------------------------- /lib/modules/soccer/actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:html/parser.dart' show parse; 2 | import 'package:redux/redux.dart'; 3 | import 'package:cat_dog/common/state.dart'; 4 | import 'package:cat_dog/modules/soccer/repository.dart'; 5 | 6 | class SetSoccerGames { 7 | final List games; 8 | final List days; 9 | final List matchs; 10 | SetSoccerGames(this.games, this.days, this.matchs); 11 | } 12 | 13 | parseSoccerNews (result) { 14 | List data = new List(); 15 | List days = new List(); 16 | List matchs = new List(); 17 | try { 18 | var document = parse(result); 19 | 20 | var matchDays = document 21 | .getElementsByClassName('s-weekday')[0] 22 | .getElementsByTagName('a'); 23 | 24 | int j; 25 | int lengthDay = matchDays.length; 26 | 27 | for (j = 0; j < lengthDay; j++) { 28 | var day = matchDays[j]; 29 | days.add({ 30 | 'heading': day.innerHtml, 31 | 'url': day.attributes['href'] 32 | }); 33 | } 34 | 35 | var elements = document 36 | .getElementsByClassName('s-list')[0] 37 | .getElementsByClassName('row'); 38 | int i; 39 | int length = elements.length; 40 | int indexData; 41 | for (i = 0; i < length; i++) { 42 | try { 43 | var item = elements[i]; 44 | if (!item.className.contains('match')) { 45 | var tagA = item.getElementsByTagName('a'); 46 | String league = tagA[0].innerHtml; 47 | String info = tagA[1].innerHtml; 48 | String time = item.getElementsByTagName('time')[0].innerHtml; 49 | var leagueInfo = { 50 | 'league': league, 51 | 'info': info, 52 | 'time': time, 53 | 'matchs': [], 54 | 'match': false 55 | }; 56 | matchs.add(leagueInfo); 57 | data.add(leagueInfo); 58 | if (indexData == null) { 59 | indexData = 0; 60 | } else { 61 | indexData++; 62 | } 63 | } else { 64 | var tagTeam = item.getElementsByClassName('team'); 65 | 66 | var home = tagTeam[0]; 67 | String homeName = home.getElementsByTagName('span')[0].innerHtml; 68 | var homeLogo = home.getElementsByTagName('img'); 69 | var away = tagTeam[1]; 70 | String awayName = away.getElementsByTagName('span')[0].innerHtml; 71 | var awayLogo = away.getElementsByTagName('img'); 72 | var scoTag = item.getElementsByClassName('sco')[0]; 73 | String homeScore = scoTag.getElementsByTagName('span')[0].innerHtml; 74 | String awayScore = scoTag.getElementsByTagName('span')[2].innerHtml; 75 | var match = { 76 | 'match': true, 77 | 'home': homeName, 78 | 'homeLogo': homeLogo.length > 0 79 | ? homeLogo[0].attributes['src'].replaceAll('//', 'https://') : null, 80 | 'away': awayName, 81 | 'awayLogo': awayLogo.length > 0 82 | ? awayLogo[0].attributes['src'].replaceAll('//', 'https://') : null, 83 | 'homeScore': homeScore, 84 | 'awayScore': awayScore, 85 | 'time': data[indexData]['time'] 86 | }; 87 | matchs.add(match); 88 | data[indexData]['matchs'].add(match); 89 | } 90 | } catch (err) { 91 | print(err); 92 | } 93 | } 94 | } catch (err) { 95 | } 96 | return { 97 | 'data': data, 98 | 'days': days, 99 | 'matchs': matchs 100 | }; 101 | } 102 | 103 | final Function getTodaySoccerCalendarAction = (Store store) async { 104 | String result = await fetchSoccerCalendar(); 105 | if (result != '') { 106 | var data = parseSoccerNews(result); 107 | store.dispatch(new SetSoccerGames(data['data'], data['days'], data['matchs'])); 108 | return data['data']; 109 | } 110 | return []; 111 | }; 112 | 113 | final Function getSoccerCalendarAction = (String url) async { 114 | String result = await fetchSoccerCalendarByDay(url); 115 | if (result != '') { 116 | var data = parseSoccerNews(result); 117 | return data['data']; 118 | } 119 | return []; 120 | }; -------------------------------------------------------------------------------- /lib/modules/soccer/containers/SoccerCalendar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/soccer/components/SoccerCalendarView.dart'; 6 | import 'package:cat_dog/modules/soccer/actions.dart'; 7 | 8 | class SoccerCalendar extends StatelessWidget { 9 | final BuildContext scaffoldContext; 10 | SoccerCalendar({Key key, BuildContext scaffoldContext}) : 11 | scaffoldContext = scaffoldContext, 12 | super(key: key); 13 | @override 14 | Widget build(BuildContext context) { 15 | return new StoreConnector( 16 | converter: (Store store) { 17 | return { 18 | 'getTodaySoccerCalendar': () => getTodaySoccerCalendarAction(store), 19 | 'getSoccerCalendarAction': (url) => getSoccerCalendarAction(url) 20 | }; 21 | }, 22 | builder: (BuildContext context, props) { 23 | return new SoccerCalendarView( 24 | key: key, 25 | scaffoldContext: scaffoldContext, 26 | getSoccerCalendar: props['getSoccerCalendarAction'], 27 | getTodaySoccerCalendar: props['getTodaySoccerCalendar'] 28 | ); 29 | } 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/modules/soccer/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:cat_dog/modules/soccer/actions.dart'; 3 | import 'package:cat_dog/modules/soccer/state.dart'; 4 | 5 | Reducer soccerReducer = combineReducers([ 6 | new TypedReducer(setSoccerGamesReducer) 7 | ]); 8 | 9 | SoccerState setSoccerGamesReducer(SoccerState state, SetSoccerGames action) { 10 | return state.copyWith( 11 | games: action.games, 12 | days: action.days, 13 | matchs: action.matchs 14 | ); 15 | } -------------------------------------------------------------------------------- /lib/modules/soccer/repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:cat_dog/common/configs.dart'; 3 | 4 | Future fetchSoccerCalendar() async { 5 | String url = GET_NEWS_API + "/soccer/m"; 6 | var response = await http.get(url); 7 | return response.body; 8 | } 9 | 10 | Future fetchSoccerCalendarByDay(String day) async { 11 | String url = GET_NEWS_API + day; 12 | var response = await http.get(url); 13 | return response.body; 14 | } -------------------------------------------------------------------------------- /lib/modules/soccer/state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:meta/meta.dart'; 3 | 4 | @immutable 5 | class SoccerState { 6 | // properties 7 | final List games; 8 | final List matchs; 9 | final List days; 10 | 11 | // constructor with default 12 | SoccerState({ 13 | this.games, 14 | this.matchs, 15 | this.days 16 | }); 17 | 18 | // allows to modify AuthState parameters while cloning previous ones 19 | SoccerState copyWith({ 20 | List games, 21 | List matchs, 22 | List days 23 | }) { 24 | return new SoccerState( 25 | games: games ?? this.games, 26 | matchs: matchs ?? this.matchs, 27 | days: days ?? this.days 28 | ); 29 | } 30 | 31 | factory SoccerState.fromJSON(Map input) => new SoccerState( 32 | games: json != null ? json.decode(input['games']) : [], 33 | matchs: json != null ? json.decode(input['matchs']) : [], 34 | days: json != null ? json.decode(input['days']) : [] 35 | ); 36 | 37 | Map toJSON() => { 38 | 'games': json.encode(this.games), 39 | 'matchs': json.encode(this.matchs), 40 | 'days': json.encode(this.days) 41 | }; 42 | 43 | @override 44 | String toString() { 45 | return '''{ 46 | games: $games 47 | matchs: $matchs 48 | days: $days 49 | }'''; 50 | } 51 | } -------------------------------------------------------------------------------- /lib/modules/user/actions.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:cat_dog/modules/dashboard/actions.dart'; 3 | import 'package:cat_dog/modules/user/models/user.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | 6 | class UserLoginRequest {} 7 | 8 | class UserLoginSuccess { 9 | final User user; 10 | UserLoginSuccess(this.user); 11 | } 12 | 13 | class UserLoginFailure { 14 | final String error; 15 | UserLoginFailure(this.error); 16 | } 17 | 18 | class UserLogout {} 19 | 20 | class UserSavedNews { 21 | final Object news; 22 | UserSavedNews(this.news); 23 | } 24 | 25 | class UserRemoveSavedNews { 26 | final dynamic news; 27 | UserRemoveSavedNews(this.news); 28 | } 29 | 30 | final Function loginAction = (String username, String password) { 31 | return (Store store) { 32 | store.dispatch(new UserLoginRequest()); 33 | if (username == 'admin' && password == 'admin') { 34 | store.dispatch(new UserLoginSuccess(new User('placeholder_token', 'placeholder_id'))); 35 | return true; 36 | } else { 37 | store.dispatch(new UserLoginFailure('Username or password were incorrect.')); 38 | return false; 39 | } 40 | }; 41 | }; 42 | final Function logoutAction = () { 43 | return (Store store) { 44 | store.dispatch(new UserLogout()); 45 | }; 46 | }; 47 | 48 | final Function saveNewsAction = (Store store, Map item) async { 49 | var result = await getDetailNews(item['url']); 50 | dynamic data = item; 51 | data['data'] = result['text']; 52 | store.dispatch(UserSavedNews(item)); 53 | return true; 54 | }; 55 | 56 | final Function removeSavedNewsAction = (Map item) { 57 | return (Store store) { 58 | store.dispatch(UserRemoveSavedNews(item)); 59 | }; 60 | }; -------------------------------------------------------------------------------- /lib/modules/user/animations/login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | 5 | class StaggerAnimation extends StatelessWidget { 6 | StaggerAnimation({Key key, this.buttonController}) 7 | : buttonSqueezeanimation = new Tween( 8 | begin: 320.0, 9 | end: 70.0, 10 | ) 11 | .animate( 12 | new CurvedAnimation( 13 | parent: buttonController, 14 | curve: new Interval( 15 | 0.0, 16 | 0.150, 17 | ), 18 | ), 19 | ), 20 | buttomZoomOut = new Tween( 21 | begin: 70.0, 22 | end: 1000.0, 23 | ) 24 | .animate( 25 | new CurvedAnimation( 26 | parent: buttonController, 27 | curve: new Interval( 28 | 0.550, 29 | 0.999, 30 | curve: Curves.bounceOut, 31 | ), 32 | ), 33 | ), 34 | containerCircleAnimation = new EdgeInsetsTween( 35 | begin: const EdgeInsets.only(bottom: 50.0), 36 | end: const EdgeInsets.only(bottom: 0.0), 37 | ) 38 | .animate( 39 | new CurvedAnimation( 40 | parent: buttonController, 41 | curve: new Interval( 42 | 0.500, 43 | 0.800, 44 | curve: Curves.ease, 45 | ), 46 | ), 47 | ), 48 | super(key: key); 49 | 50 | final AnimationController buttonController; 51 | final Animation containerCircleAnimation; 52 | final Animation buttonSqueezeanimation; 53 | final Animation buttomZoomOut; 54 | 55 | Future _playAnimation() async { 56 | try { 57 | await buttonController.forward(); 58 | await buttonController.reverse(); 59 | } on TickerCanceled {} 60 | } 61 | 62 | Widget _buildAnimation(BuildContext context, Widget child) { 63 | return new Padding( 64 | padding: buttomZoomOut.value == 70 65 | ? const EdgeInsets.only(bottom: 50.0) 66 | : containerCircleAnimation.value, 67 | child: new InkWell( 68 | onTap: () { 69 | _playAnimation(); 70 | }, 71 | child: new Hero( 72 | tag: "fade", 73 | child: buttomZoomOut.value <= 300 74 | ? new Container( 75 | width: buttomZoomOut.value == 70 76 | ? buttonSqueezeanimation.value 77 | : buttomZoomOut.value, 78 | height: 79 | buttomZoomOut.value == 70 ? 60.0 : buttomZoomOut.value, 80 | alignment: FractionalOffset.center, 81 | decoration: new BoxDecoration( 82 | color: const Color.fromRGBO(247, 64, 106, 1.0), 83 | borderRadius: buttomZoomOut.value < 400 84 | ? new BorderRadius.all(const Radius.circular(30.0)) 85 | : new BorderRadius.all(const Radius.circular(0.0)), 86 | ), 87 | child: buttonSqueezeanimation.value > 75.0 88 | ? new Text( 89 | "Sign In", 90 | style: new TextStyle( 91 | color: Colors.white, 92 | fontSize: 20.0, 93 | fontWeight: FontWeight.w300, 94 | letterSpacing: 0.3, 95 | ), 96 | ) 97 | : buttomZoomOut.value < 300.0 98 | ? new CircularProgressIndicator( 99 | value: null, 100 | strokeWidth: 1.0, 101 | valueColor: new AlwaysStoppedAnimation( 102 | Colors.white), 103 | ) 104 | : null) 105 | : new Container( 106 | width: buttomZoomOut.value, 107 | height: buttomZoomOut.value, 108 | decoration: new BoxDecoration( 109 | shape: buttomZoomOut.value < 500 110 | ? BoxShape.circle 111 | : BoxShape.rectangle, 112 | color: const Color.fromRGBO(247, 64, 106, 1.0), 113 | ), 114 | ), 115 | )), 116 | ); 117 | } 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | buttonController.addListener(() { 122 | if (buttonController.isCompleted) { 123 | pushAndRemoveByName('/home', context, {}); 124 | } 125 | }); 126 | return new AnimatedBuilder( 127 | builder: _buildAnimation, 128 | animation: buttonController, 129 | ); 130 | } 131 | } -------------------------------------------------------------------------------- /lib/modules/user/components/LoginFormView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/common/utils/navigation.dart'; 3 | import 'package:cat_dog/presentation/platform_adaptive.dart'; 4 | import 'package:cat_dog/common/components/InputFields.dart'; 5 | import 'package:cat_dog/modules/user/components/SignInButton.dart'; 6 | 7 | class LoginFormView extends StatefulWidget { 8 | final Function login; 9 | 10 | const LoginFormView({ 11 | Key key, 12 | this.login 13 | }) : super(key: key); 14 | @override 15 | _LoginFormViewState createState() => new _LoginFormViewState(); 16 | } 17 | 18 | class _LoginFormViewState extends State { 19 | final formKey = new GlobalKey(); 20 | 21 | String _username; 22 | String _password; 23 | 24 | void _submit() { 25 | final form = formKey.currentState; 26 | 27 | if (form.validate()) { 28 | form.save(); 29 | } 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return new Form( 35 | key: formKey, 36 | child: new Column( 37 | children: [ 38 | new InputFieldArea( 39 | hint: "Username", 40 | obscureText: false, 41 | icon: Icons.person_outline, 42 | validator: (val) => 43 | val.isEmpty ? 'Please enter your username.' : null, 44 | onSaved: (val) => _username = val, 45 | ), 46 | new InputFieldArea( 47 | hint: "Password", 48 | icon: Icons.lock_outline, 49 | validator: (val) => 50 | val.isEmpty ? 'Please enter your password.' : null, 51 | onSaved: (val) => _password = val, 52 | obscureText: true, 53 | ), 54 | new Padding( 55 | padding: new EdgeInsets.only(top: 20.0), 56 | child: new PlatformAdaptiveButton( 57 | onPressed:() { 58 | _submit(); 59 | var result = widget.login(_username, _password); 60 | if (result) { 61 | pushAndRemoveByName('/home', context, {}); 62 | } 63 | }, 64 | icon: new Icon(Icons.done), 65 | child: new SignInButton(), 66 | ), 67 | ) 68 | ], 69 | ), 70 | ); 71 | } 72 | } -------------------------------------------------------------------------------- /lib/modules/user/components/SavedNewsView.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/state.dart'; 4 | import 'package:flutter_redux/flutter_redux.dart'; 5 | import 'package:cat_dog/styles/colors.dart'; 6 | import 'package:cat_dog/pages/LoadingPage.dart'; 7 | import 'package:cat_dog/common/components/NewsList.dart'; 8 | 9 | class SavedNewsView extends StatefulWidget { 10 | final Function removeSavedNews; 11 | final BuildContext scaffoldContext; 12 | const SavedNewsView({ 13 | Key key, 14 | this.removeSavedNews, 15 | this.scaffoldContext 16 | }) : super(key: key); 17 | 18 | @override 19 | _SavedNewsViewState createState() => new _SavedNewsViewState(); 20 | } 21 | 22 | class _SavedNewsViewState extends State { 23 | bool loading = false; 24 | GlobalKey pageKey = new GlobalKey(); 25 | final ScrollController scrollController = new ScrollController(); 26 | List list = []; 27 | @override 28 | void initState() { 29 | super.initState(); 30 | } 31 | 32 | void callbackStart (type, item) { 33 | if (type == 'remove') { 34 | setState(() { 35 | loading = true; 36 | }); 37 | } 38 | } 39 | 40 | void callbackEnd (type, item) { 41 | if (type == 'remove') { 42 | Future.delayed(new Duration(milliseconds: 1000), () { 43 | setState(() { 44 | loading = false; 45 | }); 46 | }); 47 | } 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return new LoadingPage( 53 | key: pageKey, 54 | loading: loading, 55 | component: new Container( 56 | // height: MediaQuery.of(context).size.height - 476, 57 | decoration: new BoxDecoration(color: AppColors.commonBackgroundColor), 58 | child: new StoreConnector( 59 | converter: (Store store) { 60 | return store.state.user.saved; 61 | }, 62 | builder: (BuildContext context, news) { 63 | return new NewsList( 64 | list: news ?? [], 65 | widget: widget, 66 | callbackEnd: callbackEnd, 67 | controller: scrollController, 68 | callbackStart: callbackStart, 69 | features: { 'remove': true, 'share': true } 70 | ); 71 | } 72 | ) 73 | ) 74 | ); 75 | } 76 | } -------------------------------------------------------------------------------- /lib/modules/user/components/SignInButton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SignInButton extends StatelessWidget { 4 | SignInButton(); 5 | @override 6 | Widget build(BuildContext context) { 7 | return (new Container( 8 | width: 320.0, 9 | height: 60.0, 10 | alignment: FractionalOffset.center, 11 | decoration: new BoxDecoration( 12 | color: const Color.fromRGBO(247, 64, 106, 1.0), 13 | borderRadius: new BorderRadius.all(const Radius.circular(30.0)), 14 | ), 15 | child: new Text( 16 | "Sign In", 17 | style: new TextStyle( 18 | color: Colors.white, 19 | fontSize: 20.0, 20 | fontWeight: FontWeight.w300, 21 | letterSpacing: 0.3, 22 | ), 23 | ), 24 | )); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/modules/user/containers/LoginForm.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | 5 | import 'package:cat_dog/common/state.dart'; 6 | import 'package:cat_dog/modules/user/actions.dart'; 7 | import 'package:cat_dog/modules/user/components/LoginFormView.dart'; 8 | 9 | class LoginForm extends StatelessWidget { 10 | LoginForm({Key key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return new StoreConnector( 15 | converter: (Store store) { 16 | return (String username, String password) => 17 | store.dispatch(loginAction(username, password)); 18 | }, 19 | builder: (BuildContext context, login) { 20 | return new LoginFormView( 21 | key: key, 22 | login: login 23 | ); 24 | } 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/modules/user/containers/SavedNews.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/modules/user/components/SavedNewsView.dart'; 6 | import 'package:cat_dog/modules/user/actions.dart'; 7 | 8 | class SavedNews extends StatelessWidget { 9 | final BuildContext scaffoldContext; 10 | SavedNews({Key key, BuildContext scaffoldContext}) : 11 | scaffoldContext = scaffoldContext, 12 | super(key: key); 13 | @override 14 | Widget build(BuildContext context) { 15 | return new StoreConnector( 16 | converter: (Store store) { 17 | return (item) => store.dispatch(removeSavedNewsAction(item)); 18 | }, 19 | builder: (BuildContext context, removeSavedNews) { 20 | return new SavedNewsView( 21 | key: key, 22 | scaffoldContext: scaffoldContext, 23 | removeSavedNews: removeSavedNews 24 | ); 25 | } 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/modules/user/models/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | final String token; 3 | final String id; 4 | 5 | User(this.token, this.id); 6 | 7 | Map toJSON() => { 8 | 'token': this.token, 9 | 'id': this.id 10 | }; 11 | 12 | factory User.fromJSON(Map json) => new User( 13 | json['token'], 14 | json['id'], 15 | ); 16 | 17 | @override 18 | String toString() { 19 | return '{token: $token, id: $id}'; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/modules/user/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:redux/redux.dart'; 2 | import 'package:cat_dog/modules/user/actions.dart'; 3 | import 'package:cat_dog/modules/user/state.dart'; 4 | 5 | Reducer authReducer = combineReducers([ 6 | new TypedReducer(userLoginRequestReducer), 7 | new TypedReducer(userLoginSuccessReducer), 8 | new TypedReducer(userLoginFailureReducer), 9 | new TypedReducer(userLogoutReducer), 10 | new TypedReducer(userSavedNewsReducer), 11 | new TypedReducer(userRemoveSavedNewsReducer), 12 | ]); 13 | 14 | UserState userLoginRequestReducer(UserState user, UserLoginRequest action) { 15 | return new UserState().copyWith( 16 | isAuthenticated: false, 17 | isAuthenticating: true, 18 | ); 19 | } 20 | 21 | UserState userLoginSuccessReducer(UserState user, UserLoginSuccess action) { 22 | return new UserState().copyWith( 23 | isAuthenticated: true, 24 | isAuthenticating: false, 25 | user: action.user 26 | ); 27 | } 28 | 29 | UserState userLoginFailureReducer(UserState user, UserLoginFailure action) { 30 | return new UserState().copyWith( 31 | isAuthenticated: false, 32 | isAuthenticating: false, 33 | error: action.error 34 | ); 35 | } 36 | 37 | UserState userSavedNewsReducer(UserState user, UserSavedNews action) { 38 | var news = user.saved; 39 | news.insert(0, action.news); 40 | return user.copyWith( 41 | saved: news 42 | ); 43 | } 44 | 45 | UserState userLogoutReducer(UserState user, UserLogout action) { 46 | return new UserState(); 47 | } 48 | 49 | UserState userRemoveSavedNewsReducer(UserState user, UserRemoveSavedNews action) { 50 | var news = user.saved; 51 | int index = user.saved.indexWhere((dynamic item) { 52 | return item['url'] == action.news['url']; 53 | }); 54 | news.removeAt(index); 55 | return user.copyWith( 56 | saved: List.from(news) 57 | ); 58 | } -------------------------------------------------------------------------------- /lib/modules/user/state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:cat_dog/modules/user/models/user.dart'; 4 | 5 | @immutable 6 | class UserState { 7 | // properties 8 | final bool isAuthenticated; 9 | final bool isAuthenticating; 10 | final User user; 11 | final String error; 12 | final List saved; 13 | 14 | // constructor with default 15 | UserState({ 16 | this.isAuthenticated = false, 17 | this.isAuthenticating = false, 18 | this.user, 19 | this.error, 20 | this.saved = const [] 21 | }); 22 | 23 | // allows to modify AuthState parameters while cloning previous ones 24 | UserState copyWith({ 25 | bool isAuthenticated, 26 | bool isAuthenticating, 27 | String error, 28 | User user, 29 | List saved 30 | }) { 31 | return new UserState( 32 | isAuthenticated: isAuthenticated ?? this.isAuthenticated, 33 | isAuthenticating: isAuthenticating ?? this.isAuthenticating, 34 | error: error ?? this.error, 35 | user: user ?? this.user, 36 | saved: saved ?? this.saved, 37 | ); 38 | } 39 | 40 | factory UserState.fromJSON(Map input) { 41 | return new UserState( 42 | isAuthenticated: input != null ? input['isAuthenticated'] : false, 43 | isAuthenticating: input != null ? input['isAuthenticating'] : false, 44 | error: input != null ? input['error'] : null, 45 | user: input != null && input['user'] != null ? new User.fromJSON(input['user']) : null, 46 | saved: input != null ? json.decode(input['saved']) : [] 47 | ); 48 | } 49 | 50 | Map toJSON() => { 51 | 'isAuthenticated': this.isAuthenticated, 52 | 'isAuthenticating': this.isAuthenticating, 53 | 'user': this.user == null ? null : this.user.toJSON(), 54 | 'error': this.error, 55 | 'saved': json.encode(this.saved) 56 | }; 57 | 58 | @override 59 | String toString() { 60 | return '''{ 61 | isAuthenticated: $isAuthenticated, 62 | isAuthenticating: $isAuthenticating, 63 | user: $user, 64 | error: $error, 65 | saved: $saved 66 | }'''; 67 | } 68 | } -------------------------------------------------------------------------------- /lib/modules/user/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | DecorationImage backgroundImage = new DecorationImage( 4 | image: new ExactAssetImage('assets/images/login.jpg'), 5 | fit: BoxFit.cover, 6 | ); 7 | 8 | DecorationImage tick = new DecorationImage( 9 | image: new ExactAssetImage('assets/images/tick.png'), 10 | fit: BoxFit.cover, 11 | ); -------------------------------------------------------------------------------- /lib/modules/welcome/animations/page_reveal.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PageReveal extends StatelessWidget { 5 | 6 | final double revealPercent; 7 | final Widget child; 8 | 9 | PageReveal({ 10 | this.revealPercent, 11 | this.child 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ClipOval( 17 | clipper: new CircleRevealClipper(revealPercent), 18 | child: child, 19 | ); 20 | } 21 | } 22 | 23 | class CircleRevealClipper extends CustomClipper{ 24 | 25 | final double revealPercent; 26 | 27 | 28 | CircleRevealClipper( 29 | this.revealPercent 30 | ); 31 | 32 | @override 33 | Rect getClip(Size size) { 34 | 35 | final epicenter = new Offset(size.width / 2, size.height * 0.9); 36 | 37 | double theta = atan(epicenter.dy / epicenter.dx); 38 | final distanceToCorner = epicenter.dy / sin(theta); 39 | 40 | final radius = distanceToCorner * revealPercent; 41 | final diameter = 2 * radius; 42 | 43 | return new Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter); 44 | } 45 | 46 | @override 47 | bool shouldReclip(CustomClipper oldClipper) { 48 | // TODO: implement shouldReclip 49 | return true; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /lib/modules/welcome/components/PagerIndicatorView.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:cat_dog/modules/welcome/components/PagerView.dart'; 5 | 6 | const BUBBLE_WIDHT = 55.0 ; 7 | class PagerIndicatorView extends StatelessWidget { 8 | final PagerIndicatorViewModel viewModel; 9 | 10 | PagerIndicatorView({ 11 | this.viewModel, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | 17 | List bubbles = []; 18 | for(var i = 0; i < viewModel.pages.length; ++i ){ 19 | final page = viewModel.pages[i]; 20 | 21 | var percentActive; 22 | 23 | if(i == viewModel.activeIndex){ 24 | percentActive = 1.0 - viewModel.slidePercent; 25 | } else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight){ 26 | percentActive = viewModel.slidePercent; 27 | } else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft){ 28 | percentActive = viewModel.slidePercent; 29 | }else { 30 | percentActive = 0.0; 31 | } 32 | 33 | bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight); 34 | 35 | bubbles.add( 36 | new PageBubble( 37 | viewModel: new PageBubbleViewModel( 38 | page.icon, 39 | page.color, 40 | isHollow, 41 | percentActive, 42 | ), 43 | ), 44 | ); 45 | } 46 | final baseTranslation = ((viewModel.pages.length * BUBBLE_WIDHT) / 2) - (BUBBLE_WIDHT / 2) ; 47 | var translation = baseTranslation - (viewModel.activeIndex * BUBBLE_WIDHT); 48 | 49 | if (viewModel.slideDirection == SlideDirection.leftToRight){ 50 | translation += BUBBLE_WIDHT * viewModel.slidePercent; 51 | } else if (viewModel.slideDirection == SlideDirection.rightToLeft){ 52 | translation -= BUBBLE_WIDHT * viewModel.slidePercent; 53 | } 54 | 55 | return new Column( 56 | children: [ 57 | new Expanded(child: new Container()), 58 | new Transform( 59 | transform: new Matrix4.translationValues(translation, 0.0, 0.0), 60 | child: new Row( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | children: bubbles, 63 | ), 64 | ), 65 | ], 66 | ); 67 | } 68 | } 69 | 70 | enum SlideDirection{ 71 | leftToRight, 72 | rightToLeft, 73 | none, 74 | } 75 | 76 | 77 | class PagerIndicatorViewModel{ 78 | final List pages; 79 | final int activeIndex; 80 | final SlideDirection slideDirection; 81 | final double slidePercent; 82 | 83 | PagerIndicatorViewModel( 84 | this.pages, 85 | this.activeIndex, 86 | this.slideDirection, 87 | this.slidePercent 88 | ); 89 | } 90 | 91 | class PageBubble extends StatelessWidget { 92 | final PageBubbleViewModel viewModel; 93 | 94 | PageBubble({ 95 | this.viewModel 96 | }); 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | return new Container( 101 | width: 55.0, 102 | height: 65.0, 103 | child: new Center( 104 | child: new Container( 105 | width: lerpDouble(20.0,45.0,viewModel.activePercent), 106 | height: lerpDouble(20.0,45.0,viewModel.activePercent), 107 | decoration: new BoxDecoration( 108 | shape: BoxShape.circle, 109 | color: viewModel.isHollow 110 | ? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round()) 111 | : const Color(0x88FFFFFF), 112 | border: new Border.all( 113 | color: viewModel.isHollow 114 | ? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round()) 115 | : Colors.transparent, 116 | width: 3.0, 117 | ), 118 | ), 119 | child: new Opacity( 120 | opacity: viewModel.activePercent, 121 | child: Icon( 122 | viewModel.icon, 123 | color: viewModel.color, 124 | ) 125 | ), 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | 132 | 133 | class PageBubbleViewModel { 134 | final IconData icon; 135 | final Color color; 136 | final bool isHollow; 137 | final double activePercent; 138 | 139 | PageBubbleViewModel ( 140 | this.icon, 141 | this.color, 142 | this.isHollow, 143 | this.activePercent, 144 | ); 145 | } -------------------------------------------------------------------------------- /lib/modules/welcome/components/PagerView.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final pages = [ 4 | new PageViewModel( 5 | const Color(0xFF548CFF), 6 | 'assets/images/banner-1.png', 7 | 'Tin tức phong phú', 8 | 'Tổng hợp tin tức nóng và mới nhất từ hơn 150 báo điện tử Việt Nam.', 9 | Icons.near_me), 10 | new PageViewModel( 11 | const Color(0xFFE4534D), 12 | 'assets/images/banner-2.png', 13 | 'Chủ đề nóng', 14 | 'Liên tục cập nhật các sự kiện nóng: U23 Việt Nam, Chiến sự syrian, ...', 15 | Icons.nature_people), 16 | new PageViewModel( 17 | const Color(0xFFFF682D), 18 | 'assets/images/banner-3.png', 19 | 'Dành cho bạn', 20 | 'Miễn phí, đơn giản và tiện lợi !', 21 | Icons.devices, 22 | ), 23 | ]; 24 | 25 | class PagerView extends StatelessWidget { 26 | final PageViewModel viewModel; 27 | final double percentVisible; 28 | 29 | PagerView({ 30 | this.viewModel, 31 | this.percentVisible = 1.0, 32 | }); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return new Container( 37 | width: double.infinity, 38 | color: viewModel.color, 39 | child: 40 | new Opacity( 41 | opacity: percentVisible, 42 | child: new Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | children: [ 45 | new Transform( 46 | transform: new Matrix4.translationValues(0.0, 50.0 * (1.0 - percentVisible), 0.0), 47 | child: new Padding( 48 | padding: new EdgeInsets.only(bottom: 25.0), 49 | child: 50 | new Image.asset( 51 | viewModel.heroAssetPath, 52 | width: 200.0, 53 | height: 200.0), 54 | ), 55 | ), 56 | new Transform( 57 | transform: new Matrix4.translationValues(0.0, 30.0 * (1.0 - percentVisible), 0.0), 58 | child: new Padding( 59 | padding: new EdgeInsets.only(top: 10.0, bottom: 10.0), 60 | child: new Text( 61 | viewModel.title, 62 | style: new TextStyle( 63 | color: Colors.white, 64 | fontFamily: 'FlamanteRoma', 65 | fontSize: 34.0, 66 | ), 67 | ), 68 | ), 69 | ), 70 | new Transform( 71 | transform: new Matrix4.translationValues(0.0, 30.0 * (1.0 - percentVisible), 0.0), 72 | child: new Padding( 73 | padding: new EdgeInsets.only(bottom: 75.0), 74 | child: new Text( 75 | viewModel.body, 76 | textAlign: TextAlign.center, 77 | style: new TextStyle( 78 | color: Colors.white, 79 | fontFamily: 'FlamanteRomaItalic', 80 | fontSize: 18.0, 81 | ), 82 | ), 83 | ), 84 | ), 85 | ]), 86 | )); 87 | } 88 | } 89 | 90 | class PageViewModel { 91 | final Color color; 92 | final String heroAssetPath; 93 | final String title; 94 | final String body; 95 | final IconData icon; 96 | 97 | PageViewModel( 98 | this.color, 99 | this.heroAssetPath, 100 | this.title, 101 | this.body, 102 | this.icon, 103 | ); 104 | } -------------------------------------------------------------------------------- /lib/pages/AboutPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_redux/flutter_redux.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:cat_dog/common/state.dart'; 5 | import 'package:cat_dog/common/utils/navigation.dart'; 6 | import 'package:cat_dog/common/actions/common.dart'; 7 | import 'package:flutter_markdown/flutter_markdown.dart'; 8 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 9 | 10 | class AboutView extends StatefulWidget { 11 | final Function getAbout; 12 | const AboutView({ 13 | Key key, 14 | Function getAbout, 15 | }) : 16 | getAbout = getAbout, 17 | super(key: key); 18 | 19 | @override 20 | _AboutViewState createState() => new _AboutViewState(); 21 | } 22 | 23 | class _AboutViewState extends State { 24 | @override 25 | void initState() { 26 | super.initState(); 27 | this.getAbout(); 28 | } 29 | getAbout () async { 30 | try { 31 | await widget.getAbout(); 32 | } catch (err) { 33 | } 34 | } 35 | @override 36 | Widget build(BuildContext context) { 37 | return new Center( 38 | child: new Container( 39 | child: new StoreConnector( 40 | converter: (Store store) { 41 | return store.state.common.about; 42 | }, 43 | builder: (BuildContext context, html) { 44 | return new Markdown( 45 | data: html ?? '' 46 | ); 47 | } 48 | ) 49 | ) 50 | ); 51 | } 52 | } 53 | 54 | class AboutPage extends StatelessWidget { 55 | AboutPage({Key key}) : super(key: key); 56 | final GlobalKey _mainKey = new GlobalKey(); 57 | @override 58 | Widget build(BuildContext context) { 59 | return new Scaffold( 60 | key: _mainKey, 61 | appBar: new PreferredSize( 62 | preferredSize: const Size.fromHeight(48.0), 63 | child: new GradientAppBar( 64 | 'Thông Tin', 65 | Icon( 66 | Icons.arrow_back, 67 | size: 32 68 | ), 69 | () { 70 | navigationPop(context); 71 | }, 72 | null, 73 | () async { 74 | } 75 | ) 76 | ), 77 | body: Builder( 78 | builder: (context) => new StoreConnector( 79 | converter: (Store store) { 80 | return () => 81 | store.dispatch(getAboutAction()); 82 | }, 83 | builder: (BuildContext context, getAbout) { 84 | return new AboutView( 85 | key: key, 86 | getAbout: getAbout 87 | ); 88 | } 89 | ) 90 | ) 91 | ); 92 | } 93 | } -------------------------------------------------------------------------------- /lib/pages/BoardingPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:cat_dog/common/utils/navigation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:cat_dog/modules/welcome/animations/page_dragger.dart'; 5 | import 'package:cat_dog/modules/welcome/animations/page_reveal.dart'; 6 | import 'package:cat_dog/modules/welcome/components/PagerIndicatorView.dart'; 7 | import 'package:cat_dog/modules/welcome/components/PagerView.dart'; 8 | 9 | class BoardingPage extends StatefulWidget { 10 | @override 11 | _BoardingPageState createState() => new _BoardingPageState(); 12 | } 13 | class _BoardingPageState extends State with TickerProviderStateMixin { 14 | 15 | StreamController slideUpdateStream; 16 | AnimatedPageDragger animatedPageDragger; 17 | 18 | int activeIndex = 0 ; 19 | SlideDirection slideDirection = SlideDirection.none; 20 | int nextPageIndex = 0 ; 21 | double slidePercent= 0.0; 22 | 23 | _BoardingPageState(){ 24 | slideUpdateStream = new StreamController(); 25 | 26 | slideUpdateStream.stream.listen((SlideUpdate event){ 27 | setState(() { 28 | if (event.updateType == UpdateType.dragging) { 29 | slideDirection = event.direction; 30 | slidePercent = event.slidePercent; 31 | 32 | if (slideDirection == SlideDirection.leftToRight) { 33 | nextPageIndex = activeIndex - 1; 34 | } else if (slideDirection == SlideDirection.rightToLeft){ 35 | nextPageIndex = activeIndex + 1; 36 | } else { 37 | nextPageIndex = activeIndex; 38 | } 39 | } else if (event.updateType == UpdateType.doneDragging) { 40 | if (slidePercent > 0.5) { 41 | animatedPageDragger = new AnimatedPageDragger( 42 | slideDirection: slideDirection, 43 | transitionGoal: TransitionGoal.open, 44 | slidePercent: slidePercent, 45 | slideUpdateStream: slideUpdateStream, 46 | vsync: this, 47 | ); 48 | 49 | } else { 50 | animatedPageDragger = new AnimatedPageDragger( 51 | slideDirection: slideDirection, 52 | transitionGoal: TransitionGoal.close, 53 | slidePercent: slidePercent, 54 | slideUpdateStream: slideUpdateStream, 55 | vsync: this, 56 | ); 57 | nextPageIndex = activeIndex; 58 | } 59 | 60 | animatedPageDragger.run(); 61 | } else if (event.updateType == UpdateType.animating) { 62 | slideDirection = event.direction; 63 | slidePercent = event.slidePercent; 64 | } else if (event.updateType == UpdateType.doneAnimating) { 65 | activeIndex = nextPageIndex; 66 | slideDirection = SlideDirection.none; 67 | slidePercent = 0.0; 68 | animatedPageDragger.dispose(); 69 | } 70 | if (activeIndex == 2) { 71 | Future.delayed(Duration(milliseconds: 1000), () { 72 | navigationPop(context); 73 | }); 74 | } 75 | }); 76 | }); 77 | } 78 | @override 79 | void dispose() { 80 | super.dispose(); 81 | slideUpdateStream.close(); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return new Scaffold( 87 | body: new Stack( 88 | children: [ 89 | new PagerView( 90 | viewModel: pages[activeIndex], 91 | percentVisible: 1.0 , 92 | ), 93 | new PageReveal( 94 | revealPercent: slidePercent, 95 | child: new PagerView( 96 | viewModel: pages[nextPageIndex], 97 | percentVisible: slidePercent , 98 | ), 99 | ), 100 | new PagerIndicatorView( 101 | viewModel: new PagerIndicatorViewModel( 102 | pages, 103 | activeIndex, 104 | slideDirection, 105 | slidePercent, 106 | ) 107 | ), 108 | new PageDragger( 109 | canDragLeftToRight: activeIndex > 0 , 110 | canDragRightToLeft: activeIndex < pages.length - 1 , 111 | slideUpdateStream: this.slideUpdateStream, 112 | ) 113 | ], 114 | ), 115 | ); 116 | } 117 | } -------------------------------------------------------------------------------- /lib/pages/CategoriesPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/common/utils/navigation.dart'; 3 | import 'package:cat_dog/modules/category/components/CategoriesView.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | 6 | 7 | class CategoriesPage extends StatefulWidget { 8 | CategoriesPage({Key key}) : super(key: key); 9 | @override 10 | State createState() => new _CategoriesPageState(); 11 | } 12 | class _CategoriesPageState extends State { 13 | final GlobalKey _mainKey = new GlobalKey(); 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new Scaffold( 23 | key: _mainKey, 24 | appBar: new PreferredSize( 25 | preferredSize: const Size.fromHeight(48.0), 26 | child: new GradientAppBar( 27 | 'Thể Loại', 28 | Icon( 29 | Icons.arrow_back, 30 | size: 32 31 | ), 32 | () { 33 | navigationPop(context); 34 | }, 35 | null, 36 | () async { 37 | } 38 | ) 39 | ), 40 | body: new CategoriesView() 41 | ); 42 | } 43 | } -------------------------------------------------------------------------------- /lib/pages/ContentLoadingPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | class ContentLoadingPage extends StatefulWidget { 5 | final Widget component; 6 | final bool loading; 7 | const ContentLoadingPage({ 8 | Key key, 9 | this.component, 10 | this.loading 11 | }) : super(key: key); 12 | 13 | @override 14 | _ContentLoadingPageState createState() => new _ContentLoadingPageState(); 15 | } 16 | 17 | class _ContentLoadingPageState extends State { 18 | _ContentLoadingPageState({Key key}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | if (widget.loading) { 23 | return Container( 24 | width: double.infinity, 25 | padding: EdgeInsets.all(10), 26 | child: SingleChildScrollView( 27 | child: Shimmer.fromColors( 28 | baseColor: Colors.grey[300], 29 | highlightColor: Colors.grey[100], 30 | child: Column( 31 | children: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 32 | .map((_) => Padding( 33 | padding: const EdgeInsets.only(bottom: 8.0), 34 | child: Row( 35 | crossAxisAlignment: CrossAxisAlignment.start, 36 | children: [ 37 | Container( 38 | width: 48.0, 39 | height: 48.0, 40 | color: Colors.white, 41 | ), 42 | Padding( 43 | padding: 44 | const EdgeInsets.symmetric(horizontal: 8.0), 45 | ), 46 | Expanded( 47 | child: Column( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | Container( 51 | width: double.infinity, 52 | height: 8.0, 53 | color: Colors.white, 54 | ), 55 | Padding( 56 | padding: 57 | const EdgeInsets.symmetric(vertical: 2.0), 58 | ), 59 | Container( 60 | width: double.infinity, 61 | height: 8.0, 62 | color: Colors.white, 63 | ), 64 | Padding( 65 | padding: 66 | const EdgeInsets.symmetric(vertical: 2.0), 67 | ), 68 | Container( 69 | width: 40.0, 70 | height: 8.0, 71 | color: Colors.white, 72 | ) 73 | ] 74 | ) 75 | ) 76 | ] 77 | ) 78 | )) 79 | .toList(), 80 | ) 81 | ) 82 | ) 83 | ); 84 | } 85 | return widget.component; 86 | } 87 | } -------------------------------------------------------------------------------- /lib/pages/ContinueReadingPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/styles/colors.dart'; 3 | import 'package:cat_dog/modules/dashboard/containers/ContinueReading.dart'; 4 | 5 | class ContinueReadingPage extends StatelessWidget { 6 | final GlobalKey _mainKey = new GlobalKey(); 7 | 8 | ContinueReadingPage({Key key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final double statusBarHeight = MediaQuery 13 | .of(context) 14 | .padding 15 | .top; 16 | return new Scaffold( 17 | key: _mainKey, 18 | appBar: new PreferredSize( 19 | preferredSize: const Size.fromHeight(0.0), 20 | child: new Container( 21 | padding: new EdgeInsets.only(top: statusBarHeight), 22 | height: statusBarHeight, 23 | decoration: new BoxDecoration( 24 | gradient: new LinearGradient( 25 | colors: [ 26 | AppColors.appBarGradientStart, 27 | AppColors.appBarGradientEnd 28 | ], 29 | begin: const FractionalOffset(0.0, 0.0), 30 | end: const FractionalOffset(0.5, 0.0), 31 | stops: [0.0, 0.5], 32 | tileMode: TileMode.clamp 33 | ) 34 | ) 35 | ) 36 | ), 37 | body: Builder( 38 | builder: (context) => new ContinueReading(key: key, scaffoldContext: context) 39 | ) 40 | ); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/pages/HomePage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/modules/dashboard/containers/NewsTab.dart'; 4 | import 'package:cat_dog/common/components/MainDrawer.dart'; 5 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 6 | 7 | 8 | class HomePage extends StatefulWidget { 9 | HomePage({Key key}) : super(key: key); 10 | @override 11 | State createState() => new _HomePageState(); 12 | } 13 | class _HomePageState extends State with TickerProviderStateMixin { 14 | final GlobalKey mainKey = new GlobalKey(); 15 | 16 | Function hideCallback; 17 | 18 | // Function refreshCallback; 19 | // AnimationController animationController; 20 | // Animation animation; 21 | // Future timeout; 22 | // var timeoutSteam; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | // animationController = AnimationController( 28 | // duration: const Duration(milliseconds: 2000), vsync: this); 29 | // animation = CurvedAnimation(parent: animationController, curve: Curves.easeIn); 30 | 31 | // animation.addStatusListener((status) { 32 | // if (status == AnimationStatus.completed) { 33 | // animationController.repeat(); 34 | // } 35 | // }); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return new Scaffold( 41 | key: mainKey, 42 | appBar: new PreferredSize( 43 | preferredSize: const Size.fromHeight(48.0), 44 | child: new GradientAppBar( 45 | 'Trang Chủ', 46 | Icon( 47 | Icons.dehaze, 48 | size: 32 49 | ), 50 | () => mainKey.currentState.openDrawer(), 51 | Icon( 52 | Icons.chrome_reader_mode, 53 | size: 32 54 | ), 55 | () { 56 | if (hideCallback != null) { 57 | hideCallback(); 58 | } 59 | } 60 | // new AnimatedIcon(animation: animation), 61 | // () async { 62 | // if (animationController.isAnimating) { 63 | // return false; 64 | // } 65 | // if (timeout != null && timeoutSteam != null) { 66 | // timeoutSteam.cancel(); 67 | // timeout = null; 68 | // timeoutSteam = null; 69 | // } 70 | // animationController.forward(); 71 | // if (refreshCallback != null) { 72 | // refreshCallback(); 73 | // } 74 | // timeout = Future.delayed(const Duration(milliseconds: 4000)); 75 | // timeoutSteam = timeout.asStream().listen((_) { 76 | // animationController.stop(); 77 | // timeout = null; 78 | // timeoutSteam = null; 79 | // }); 80 | // } 81 | ) 82 | ), 83 | body: Builder( 84 | builder: (context) => new NewsTab( 85 | // refreshCallback: (input) { 86 | // refreshCallback = input; 87 | // }, 88 | hideCallback: (input) { 89 | hideCallback = input; 90 | }, 91 | scaffoldContext: context 92 | ) 93 | ), 94 | drawer: PreferredSize( 95 | preferredSize: const Size.fromHeight(48.0), 96 | child: new MainDrawer(scaffoldKey: mainKey) 97 | ) 98 | ); 99 | } 100 | } 101 | 102 | class AnimatedIcon extends AnimatedWidget { 103 | // The Tweens are static because they don't change. 104 | static final _sizeTween = Tween(begin: 0, end: math.pi * 50); 105 | 106 | AnimatedIcon({Key key, Animation animation}) 107 | : super(key: key, listenable: animation); 108 | 109 | Widget build(BuildContext context) { 110 | final Animation animation = listenable; 111 | final Matrix4 transform = new Matrix4.rotationZ(_sizeTween.evaluate(animation) * 0.1); 112 | return Center( 113 | child: Transform( 114 | transform: transform, 115 | alignment: FractionalOffset.center, 116 | child: new Icon( 117 | Icons.refresh, 118 | size: 32, 119 | ) 120 | ) 121 | ); 122 | } 123 | } -------------------------------------------------------------------------------- /lib/pages/LoadingPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/common/components/SpinLoading.dart'; 3 | 4 | class LoadingPage extends StatefulWidget { 5 | final Widget component; 6 | final bool loading; 7 | const LoadingPage({ 8 | Key key, 9 | this.component, 10 | this.loading 11 | }) : super(key: key); 12 | 13 | @override 14 | _LoadingPageState createState() => new _LoadingPageState(); 15 | } 16 | 17 | class _LoadingPageState extends State { 18 | _LoadingPageState({Key key}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | if (widget.loading) { 23 | return new SpinLoading(); 24 | } 25 | return widget.component; 26 | } 27 | } -------------------------------------------------------------------------------- /lib/pages/LoginPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/modules/user/containers/LoginForm.dart'; 4 | import 'package:cat_dog/styles/colors.dart'; 5 | 6 | class LoginPage extends StatelessWidget { 7 | LoginPage({Key key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return new Scaffold( 12 | body: new Container( 13 | decoration: new BoxDecoration( 14 | gradient: new LinearGradient( 15 | colors: [ 16 | AppColors.appBarGradientStart, 17 | AppColors.appBarGradientEnd 18 | ], 19 | begin: const FractionalOffset(0.0, 0.0), 20 | end: const FractionalOffset(0.5, 0.0), 21 | stops: [0.0, 0.5], 22 | tileMode: TileMode.clamp 23 | ), 24 | ), 25 | child: new Padding( 26 | padding: new EdgeInsets.fromLTRB(15.0, MediaQuery.of(context).padding.top + 15.0, 15.0, 15.0), 27 | child: new Column( 28 | mainAxisAlignment: MainAxisAlignment.start, 29 | children: [ 30 | new Expanded( 31 | child: new Center( 32 | child: new FlutterLogo( 33 | colors: AppColors.primary, 34 | size: 200.0, 35 | ), 36 | ), 37 | ), 38 | new Center( 39 | child: new ClipRect( 40 | child: new BackdropFilter( 41 | filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), 42 | child: new Container( 43 | padding: new EdgeInsets.fromLTRB(15.0, MediaQuery.of(context).padding.top + 5.0, 15.0, 5.0), 44 | decoration: new BoxDecoration( 45 | color: Colors.grey.shade200.withOpacity(0.5), 46 | borderRadius: BorderRadius.circular(40) 47 | ), 48 | child: new LoginForm() 49 | ), 50 | ), 51 | ), 52 | ), 53 | ] 54 | ) 55 | ) 56 | ) 57 | ); 58 | } 59 | } -------------------------------------------------------------------------------- /lib/pages/NewsSourcePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:cat_dog/styles/colors.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | import 'package:cat_dog/common/configs.dart'; 6 | import 'package:cat_dog/common/utils/navigation.dart'; 7 | 8 | class NewsSourcePage extends StatefulWidget { 9 | NewsSourcePage({Key key}) : super(key: key); 10 | 11 | @override 12 | _NewsSourcePageState createState() => new _NewsSourcePageState(); 13 | } 14 | 15 | class _NewsSourcePageState extends State { 16 | var sources = SOURCE_NEWS; 17 | bool change = false; 18 | final GlobalKey _mainKey = new GlobalKey(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return new Scaffold( 28 | key: _mainKey, 29 | backgroundColor: AppColors.commonBackgroundColor, 30 | appBar: PreferredSize( 31 | preferredSize: const Size.fromHeight(48.0), 32 | child: new GradientAppBar( 33 | 'Nguồn Tin', 34 | Icon( 35 | Icons.arrow_back, 36 | size: 32 37 | ), 38 | () { 39 | navigationPop(context); 40 | }, 41 | null, 42 | () async { 43 | } 44 | ) 45 | ), 46 | body: GridView.builder( 47 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 48 | crossAxisCount: 3, mainAxisSpacing: 25.0), 49 | padding: const EdgeInsets.all(10.0), 50 | itemCount: sources.length, 51 | itemBuilder: (BuildContext context, int index) { 52 | return GridTile( 53 | footer: Row( 54 | mainAxisAlignment: MainAxisAlignment.center, 55 | children: [ 56 | Flexible( 57 | child: SizedBox( 58 | height: 16.0, 59 | width: 100.0, 60 | child: Text( 61 | sources[index]['name'], 62 | maxLines: 2, 63 | textAlign: TextAlign.center, 64 | overflow: TextOverflow.ellipsis, 65 | style: TextStyle( 66 | color: Colors.grey[500] 67 | ) 68 | ), 69 | ), 70 | ) 71 | ]), 72 | child: Container( 73 | padding: const EdgeInsets.only(bottom: 32.0), 74 | child: FlatButton( 75 | child: Container( 76 | decoration: BoxDecoration( 77 | color: AppColors.white 78 | ), 79 | child: Center( 80 | child: Image.asset( 81 | sources[index]['image'], 82 | fit: BoxFit.cover, 83 | ) 84 | ) 85 | ), 86 | onPressed: () { 87 | pushByName('/subview', context, { 88 | 'view': { 89 | 'url': sources[index]['url'], 90 | 'title': sources[index]['name'] 91 | } 92 | }); 93 | }, 94 | ), 95 | ), 96 | ); 97 | }, 98 | ), 99 | ); 100 | } 101 | } -------------------------------------------------------------------------------- /lib/pages/OverlayLoadingPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:cat_dog/common/components/SpinLoading.dart'; 3 | import 'package:cat_dog/styles/colors.dart'; 4 | 5 | class OverlayLoadingPage extends StatefulWidget { 6 | final Widget component; 7 | final bool loading; 8 | const OverlayLoadingPage({ 9 | Key key, 10 | this.component, 11 | this.loading 12 | }) : super(key: key); 13 | 14 | @override 15 | _OverlayLoadingPageState createState() => new _OverlayLoadingPageState(); 16 | } 17 | 18 | class _OverlayLoadingPageState extends State { 19 | _OverlayLoadingPageState({Key key}); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Stack( 24 | children: [ 25 | widget.component, 26 | widget.loading 27 | ? Positioned( 28 | left: 0.0, 29 | right: 0.0, 30 | height: MediaQuery.of(context).size.height, 31 | child: Container( 32 | decoration: BoxDecoration( 33 | // border: new Border.all(color: AppColors.readingNewsBackgroundColor), 34 | gradient: LinearGradient( 35 | begin: Alignment.center, 36 | end: Alignment.bottomCenter, 37 | colors: [ 38 | AppColors.readingNewsBackgroundColor.withOpacity(0), 39 | AppColors.readingNewsBackgroundColor.withOpacity(1) 40 | ], 41 | stops: [0.0, 100.0], 42 | tileMode: TileMode.clamp 43 | ) 44 | ), 45 | child: Center( 46 | child: SpinLoading(overlay: true, iconColor: AppColors.specicalBackgroundColor) 47 | ) 48 | ) 49 | ) 50 | : new Container() 51 | ], 52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/pages/ReadingPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:share/share.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:cat_dog/common/configs.dart'; 5 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 6 | import 'package:cat_dog/modules/dashboard/containers/Reading.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | import 'package:cat_dog/common/utils/navigation.dart'; 9 | 10 | import 'package:cat_dog/modules/user/actions.dart'; 11 | import 'package:flutter_redux/flutter_redux.dart'; 12 | import 'package:redux/redux.dart'; 13 | import 'package:cat_dog/common/state.dart'; 14 | import 'package:cat_dog/styles/colors.dart'; 15 | 16 | class ReadingPage extends StatelessWidget { 17 | final news; 18 | final GlobalKey _mainKey = new GlobalKey(); 19 | 20 | ReadingPage({Key key, this.news}) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | // TODO: SHOULD NOT CONNECT REDUX HERE 25 | return new Scaffold( 26 | key: _mainKey, 27 | appBar: new PreferredSize( 28 | preferredSize: const Size.fromHeight(48.0), 29 | child: StoreConnector( 30 | converter: (Store store) { 31 | return (item) async => 32 | await saveNewsAction(store, item); 33 | }, 34 | builder: (BuildContext context, savedNews) { 35 | return new GradientAppBar( 36 | '', 37 | Icon( 38 | Icons.arrow_back, 39 | size: 32 40 | ), 41 | () { 42 | return navigationPop(context); 43 | }, 44 | Icon( 45 | Icons.open_in_browser, 46 | size: 32 47 | ), 48 | () async { 49 | String url = news['url']; 50 | url = DEFAULT_URL + url.replaceAll('/c/', '/r/'); 51 | if (await canLaunch(url)) { 52 | await launch(url); 53 | } else { 54 | print('Could not launch $url'); 55 | } 56 | }, 57 | onPressRightButtonDownload: () { 58 | savedNews(news); 59 | Scaffold.of(context).showSnackBar(new SnackBar( 60 | backgroundColor: AppColors.specicalBackgroundColor, 61 | content: new Text('Đã Lưu !') 62 | )); 63 | }, 64 | onPressRightButtonShare: () { 65 | String url = DEFAULT_URL + news['url']; 66 | Share.share(url.replaceAll('/c/', '/r/')); 67 | } 68 | ); 69 | } 70 | ) 71 | ), 72 | body: Builder( 73 | builder: (context) => new Reading(key: key, news: news, scaffoldContext: context, push: false) 74 | ) 75 | ); 76 | } 77 | } -------------------------------------------------------------------------------- /lib/pages/SavedNewsPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | import 'package:cat_dog/modules/user/containers/SavedNews.dart'; 6 | 7 | class SavedNewsPage extends StatelessWidget { 8 | final GlobalKey _mainKey = new GlobalKey(); 9 | SavedNewsPage({Key key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return new Scaffold( 14 | key: _mainKey, 15 | appBar: new PreferredSize( 16 | preferredSize: const Size.fromHeight(48.0), 17 | child: new GradientAppBar( 18 | 'Tin Đã Lưu', 19 | Icon( 20 | Icons.arrow_back, 21 | size: 32 22 | ), 23 | () { 24 | navigationPop(context); 25 | }, 26 | null, 27 | () async { 28 | } 29 | ) 30 | ), 31 | body: Builder( 32 | builder: (context) => new SavedNews(key: key, scaffoldContext: context) 33 | ) 34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /lib/pages/SoccerPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | import 'package:cat_dog/modules/soccer/containers/SoccerCalendar.dart'; 6 | 7 | class SoccerPage extends StatelessWidget { 8 | final GlobalKey _mainKey = new GlobalKey(); 9 | SoccerPage({Key key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return new Scaffold( 14 | key: _mainKey, 15 | appBar: new PreferredSize( 16 | preferredSize: const Size.fromHeight(48.0), 17 | child: new GradientAppBar( 18 | 'Lịch Bóng Đá', 19 | Icon( 20 | Icons.arrow_back, 21 | size: 32 22 | ), 23 | () { 24 | navigationPop(context); 25 | }, 26 | null, 27 | () async { 28 | } 29 | ) 30 | ), 31 | body: Builder( 32 | builder: (context) => new SoccerCalendar(key: key, scaffoldContext: context) 33 | ) 34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /lib/pages/SubNewsPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | import 'package:cat_dog/modules/category/containers/SubNews.dart'; 6 | 7 | class SubNewsPage extends StatelessWidget { 8 | final view; 9 | final GlobalKey _mainKey = new GlobalKey(); 10 | SubNewsPage({Key key, this.view}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return new Scaffold( 15 | key: _mainKey, 16 | appBar: new PreferredSize( 17 | preferredSize: const Size.fromHeight(48.0), 18 | child: new GradientAppBar( 19 | view['title'], 20 | Icon( 21 | Icons.arrow_back, 22 | size: 32 23 | ), 24 | () => navigationPop(context), 25 | null, 26 | () async { 27 | } 28 | ), 29 | ), 30 | body: Builder( 31 | builder: (context) => SubNews( 32 | key: key, 33 | view: view, 34 | scaffoldContext: context 35 | ) 36 | ) 37 | ); 38 | } 39 | } -------------------------------------------------------------------------------- /lib/pages/TestAnimationPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/animation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AnimatedLogo extends AnimatedWidget { 5 | // The Tweens are static because they don't change. 6 | static final _opacityTween = Tween(begin: 0.1, end: 1.0); 7 | static final _sizeTween = Tween(begin: 0.0, end: 300.0); 8 | 9 | AnimatedLogo({Key key, Animation animation}) 10 | : super(key: key, listenable: animation); 11 | 12 | Widget build(BuildContext context) { 13 | final Animation animation = listenable; 14 | return Center( 15 | child: Opacity( 16 | opacity: _opacityTween.evaluate(animation), 17 | child: Container( 18 | margin: EdgeInsets.symmetric(vertical: 10.0), 19 | height: _sizeTween.evaluate(animation), 20 | width: _sizeTween.evaluate(animation), 21 | child: FlutterLogo(), 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | 28 | class LogoApp extends StatefulWidget { 29 | _LogoAppState createState() => _LogoAppState(); 30 | } 31 | 32 | class _LogoAppState extends State with TickerProviderStateMixin { 33 | AnimationController controller; 34 | Animation animation; 35 | 36 | initState() { 37 | super.initState(); 38 | controller = AnimationController( 39 | duration: const Duration(milliseconds: 2000), vsync: this); 40 | animation = CurvedAnimation(parent: controller, curve: Curves.easeIn); 41 | 42 | animation.addStatusListener((status) { 43 | if (status == AnimationStatus.completed) { 44 | controller.reverse(); 45 | } else if (status == AnimationStatus.dismissed) { 46 | controller.forward(); 47 | } 48 | }); 49 | 50 | controller.forward(); 51 | } 52 | 53 | Widget build(BuildContext context) { 54 | return AnimatedLogo(animation: animation); 55 | } 56 | 57 | dispose() { 58 | controller.dispose(); 59 | super.dispose(); 60 | } 61 | } 62 | 63 | void main() { 64 | runApp(LogoApp()); 65 | } -------------------------------------------------------------------------------- /lib/pages/TopicDetailPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 5 | import 'package:cat_dog/modules/category/components/NewDetailTopicView.dart'; 6 | 7 | class TopicDetailPage extends StatelessWidget { 8 | final dynamic topic; 9 | final GlobalKey _mainKey = new GlobalKey(); 10 | TopicDetailPage({Key key, dynamic topic}) : 11 | topic = topic, 12 | super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return new Scaffold( 17 | key: _mainKey, 18 | appBar: new PreferredSize( 19 | preferredSize: const Size.fromHeight(48.0), 20 | child: new GradientAppBar( 21 | 'Nội Dung', 22 | Icon( 23 | Icons.arrow_back, 24 | size: 32 25 | ), 26 | () => navigationPop(context), 27 | null, 28 | () async { 29 | } 30 | ), 31 | ), 32 | body: Builder( 33 | builder: (context) => NewDetailTopicView( 34 | key: key, 35 | topic: topic, 36 | scaffoldContext: context 37 | ) 38 | ) 39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /lib/pages/TopicPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/MainDrawer.dart'; 5 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 6 | import 'package:cat_dog/modules/dashboard/components/TopicView.dart'; 7 | 8 | class TopicPage extends StatelessWidget { 9 | final GlobalKey _mainKey = new GlobalKey(); 10 | TopicPage({Key key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return new Scaffold( 15 | key: _mainKey, 16 | appBar: new PreferredSize( 17 | preferredSize: const Size.fromHeight(48.0), 18 | child: new GradientAppBar( 19 | 'Chủ Đề', 20 | Icon( 21 | Icons.arrow_back, 22 | size: 32 23 | ), 24 | () { 25 | navigationPop(context); 26 | }, 27 | null, 28 | () async { 29 | } 30 | ) 31 | ), 32 | body: Builder( 33 | builder: (context) => TopicView( 34 | key: key, 35 | scaffoldContext: context 36 | ) 37 | ), 38 | drawer: new MainDrawer(), 39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /lib/pages/VideosPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:cat_dog/common/utils/navigation.dart'; 4 | import 'package:cat_dog/common/components/MainDrawer.dart'; 5 | import 'package:cat_dog/common/components/GradientAppBar.dart'; 6 | import 'package:cat_dog/modules/dashboard/components/VideoNewsView.dart'; 7 | 8 | class VideosPage extends StatelessWidget { 9 | final GlobalKey _mainKey = new GlobalKey(); 10 | VideosPage({Key key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return new Scaffold( 15 | key: _mainKey, 16 | appBar: new PreferredSize( 17 | preferredSize: const Size.fromHeight(48.0), 18 | child: new GradientAppBar( 19 | 'Videos', 20 | Icon( 21 | Icons.arrow_back, 22 | size: 32 23 | ), 24 | () { 25 | navigationPop(context); 26 | }, 27 | null, 28 | () async { 29 | } 30 | ) 31 | ), 32 | body: Builder( 33 | builder: (context) => VideoNewsView( 34 | key: key, 35 | scaffoldContext: context 36 | ) 37 | ), 38 | drawer: new MainDrawer(), 39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /lib/presentation/platform_adaptive.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2017, the Flutter project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | 8 | import 'package:cat_dog/styles/colors.dart'; 9 | 10 | final ThemeData kIOSTheme = new ThemeData( 11 | primarySwatch: AppColors.primary, 12 | primaryColor: AppColors.primary, 13 | primaryColorBrightness: Brightness.light, 14 | ); 15 | 16 | final ThemeData kDefaultTheme = new ThemeData( 17 | primarySwatch: AppColors.primary, 18 | accentColor: AppColors.primary, 19 | ); 20 | 21 | // Bottom navigation bar that is Material on Android and Cupertino on iOS. 22 | class PlatformAdaptiveBottomBar extends StatelessWidget { 23 | PlatformAdaptiveBottomBar({Key key, this.activeColor, this.currentIndex, this.onTap, this.items}) 24 | : super(key: key); 25 | final Color activeColor; 26 | final int currentIndex; 27 | final Function onTap; 28 | final List items; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | if (Theme.of(context).platform == TargetPlatform.iOS) { 33 | return new CupertinoTabBar( 34 | activeColor: activeColor, 35 | currentIndex: currentIndex, 36 | onTap: onTap, 37 | items: items, 38 | ); 39 | } else { 40 | return new BottomNavigationBar( 41 | currentIndex: currentIndex, 42 | type: BottomNavigationBarType.fixed, 43 | onTap: onTap, 44 | items: items, 45 | ); 46 | } 47 | } 48 | } 49 | 50 | /// Button that is Material on Android and Cupertino on iOS 51 | /// On Android an icon button; on iOS, text is used 52 | class PlatformAdaptiveButton extends StatelessWidget { 53 | PlatformAdaptiveButton({Key key, this.child, this.icon, this.onPressed}) 54 | : super(key: key); 55 | final Widget child; 56 | final Widget icon; 57 | final VoidCallback onPressed; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | if (Theme.of(context).platform == TargetPlatform.iOS) { 62 | return new CupertinoButton( 63 | child: child, 64 | onPressed: onPressed, 65 | ); 66 | } else { 67 | return new IconButton( 68 | icon: icon, 69 | onPressed: onPressed, 70 | ); 71 | } 72 | } 73 | } 74 | 75 | class PlatformAdaptiveContainer extends StatelessWidget { 76 | final Widget child; 77 | final EdgeInsets margin; 78 | 79 | PlatformAdaptiveContainer({Key key, this.child, this.margin}) 80 | : super(key: key); 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return new Container( 85 | child: child, 86 | margin: margin, 87 | decoration: Theme.of(context).platform == TargetPlatform.iOS 88 | ? new BoxDecoration( 89 | border: new Border(top: new BorderSide(color: Colors.grey[200]))) 90 | : null, 91 | ); 92 | } 93 | } 94 | 95 | class PlatformChooser extends StatelessWidget { 96 | PlatformChooser({Key key, this.iosChild, this.defaultChild}); 97 | final Widget iosChild; 98 | final Widget defaultChild; 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | if (Theme.of(context).platform == TargetPlatform.iOS) return iosChild; 103 | return defaultChild; 104 | } 105 | } -------------------------------------------------------------------------------- /lib/styles/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | const AppColors(); 5 | 6 | static const Color white = Colors.white; 7 | static const Color gray = Colors.black45; 8 | static const Color red = const Color(0xFFF41128); 9 | static const Color primary = Colors.purple; 10 | static const Color appBarTitle = const Color(0xFFFFFFFF); 11 | static const Color appBarIconColor = const Color(0xFFFFFFFF); 12 | static const Color appBarDetailBackground = const Color(0x00FFFFFF); 13 | static const Color appBarGradientStart = const Color(0xFF3383FC); 14 | static const Color appBarGradientEnd = const Color(0xFF00C6FF); 15 | 16 | static const Color commonBackgroundColor = const Color(0xFFFAFAFA); 17 | static const Color specicalBackgroundColor = const Color(0xFF3E3963); 18 | static const Color itemDefaultColor = const Color(0xFFFAFAFA); 19 | static const Color specicalDefaultColor = const Color(0xFF434273); 20 | 21 | static const Color readingNewsBackgroundColor = const Color(0xFFFAFAFA); 22 | } -------------------------------------------------------------------------------- /lib/styles/texts.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Map textStyles = { 4 | 'bottom_label': new TextStyle( 5 | fontSize: 10.0, 6 | fontWeight: FontWeight.bold, 7 | ), 8 | }; -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | void main() { 9 | } 10 | --------------------------------------------------------------------------------