├── android ├── settings_aar.gradle ├── gradle.properties ├── .gitignore ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ ├── soccer_animation.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── efhem │ │ │ │ │ └── football │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── local.properties │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── lib ├── config │ ├── config.dart │ ├── theme.dart │ ├── styles.dart │ └── palette.dart ├── viewmodel │ ├── provider.dart │ ├── base_viewmodel.dart │ ├── news_provider.dart │ └── football_provider.dart ├── model │ ├── user.dart │ ├── onboarding.dart │ ├── league.dart │ ├── news.dart │ ├── team.dart │ ├── table_item.dart │ └── match.dart ├── widgets │ ├── widget.dart │ ├── list_header.dart │ ├── round_image.dart │ ├── team_item.dart │ ├── sliver_custom_appbar.dart │ ├── custom_search_bar.dart │ ├── news_item.dart │ ├── live_match_item.dart │ └── match_item.dart ├── data │ ├── datasource │ │ ├── datasource.dart │ │ ├── footbal │ │ │ ├── news_remote_datasource.dart │ │ │ ├── football_remote_datasource.dart │ │ │ ├── football_remote_data_source_imp.dart │ │ │ └── football_local_data_source.dart │ │ ├── secret_loader.dart │ │ └── news │ │ │ ├── news_remote_data_source.dart │ │ │ └── news_local_data_source.dart │ ├── model │ │ ├── news_response.dart │ │ ├── articles_remote.dart │ │ ├── articles_local.dart │ │ ├── news_response.g.dart │ │ ├── articles_remote.g.dart │ │ ├── articles_local.g.dart │ │ ├── league_table_local.dart │ │ ├── league_table_local.g.dart │ │ ├── games_local.dart │ │ ├── games_local.g.dart │ │ ├── games_response.dart │ │ └── table_response.dart │ ├── hive │ │ └── hive.dart │ ├── helper │ │ ├── network_helper.dart │ │ └── server_error.dart │ ├── retrofit │ │ ├── api_service.dart │ │ └── api_service.g.dart │ ├── repository │ │ ├── news_repository_imp.dart │ │ └── football_repository_imp.dart │ └── data.dart ├── domain │ ├── repository │ │ ├── news_repository.dart │ │ └── football_repository.dart │ ├── domain.dart │ ├── model │ │ ├── failure.dart │ │ └── secret.dart │ └── util │ │ └── utils.dart ├── screens │ ├── screen.dart │ ├── bookmark_screen.dart │ ├── home │ │ ├── search_screen.dart │ │ ├── league_table_screen.dart │ │ ├── football_screen.dart │ │ └── football_details.dart │ ├── news │ │ ├── news_details.dart │ │ └── news_screen.dart │ ├── home_screen.dart │ ├── profile_screen.dart │ └── onboarding.dart ├── utils │ ├── storage_util.dart │ └── custom_route.dart ├── injection_container.dart └── main.dart ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ │ └── LaunchImage.imageset │ │ │ ├── soccer_animation.png │ │ │ ├── soccer_animation-1.png │ │ │ ├── soccer_animation-2.png │ │ │ ├── README.md │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore ├── Podfile └── Podfile.lock ├── screenshots └── football.png ├── .metadata ├── .gitignore ├── README.md ├── pubspec.yaml └── pubspec.lock /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /lib/config/config.dart: -------------------------------------------------------------------------------- 1 | export 'styles.dart'; 2 | export 'palette.dart'; -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /screenshots/football.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/screenshots/football.png -------------------------------------------------------------------------------- /lib/viewmodel/provider.dart: -------------------------------------------------------------------------------- 1 | export 'news_provider.dart'; 2 | export 'base_viewmodel.dart'; 3 | export 'football_provider.dart'; -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/soccer_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/drawable/soccer_animation.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation.png -------------------------------------------------------------------------------- /lib/model/user.dart: -------------------------------------------------------------------------------- 1 | 2 | class User { 3 | final String avatar; 4 | final String name; 5 | final String nickName; 6 | 7 | const User({this.avatar, this.name, this.nickName}); 8 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Efhemo/football/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/soccer_animation-2.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/efhem/football/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.efhem.football 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /lib/model/onboarding.dart: -------------------------------------------------------------------------------- 1 | 2 | class OnBoarding{ 3 | final String imageAsset; 4 | final String headerTitle; 5 | final String subHeaderTitle; 6 | 7 | OnBoarding(this.imageAsset, this.headerTitle, this.subHeaderTitle); 8 | 9 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/widgets/widget.dart: -------------------------------------------------------------------------------- 1 | export 'sliver_custom_appbar.dart'; 2 | export 'custom_search_bar.dart'; 3 | export 'list_header.dart'; 4 | export 'round_image.dart'; 5 | export 'live_match_item.dart'; 6 | export 'match_item.dart'; 7 | export 'news_item.dart'; -------------------------------------------------------------------------------- /lib/data/datasource/datasource.dart: -------------------------------------------------------------------------------- 1 | export 'package:football/data/datasource/news/news_local_data_source.dart'; 2 | export 'package:football/data/datasource/news/news_remote_data_source.dart'; 3 | export 'package:football/data/datasource/secret_loader.dart'; -------------------------------------------------------------------------------- /lib/domain/repository/news_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/domain/domain.dart'; 2 | import 'package:football/model/news.dart'; 3 | 4 | abstract class NewsRepository { 5 | Future>> fetchNews(); 6 | 7 | Stream watchNews(); 8 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip 7 | -------------------------------------------------------------------------------- /lib/data/datasource/footbal/news_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:football/domain/domain.dart'; 3 | import 'package:football/data/data.dart'; 4 | 5 | abstract class NewsRemoteDataSource { 6 | Future> sport (String apiKey); 7 | } -------------------------------------------------------------------------------- /lib/domain/domain.dart: -------------------------------------------------------------------------------- 1 | export 'package:football/domain/repository/news_repository.dart'; 2 | export 'package:football/domain/repository/football_repository.dart'; 3 | export 'package:football/domain/model/failure.dart'; 4 | export 'package:football/domain/util/utils.dart'; 5 | export 'package:dartz/dartz.dart'; 6 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/model/failure.dart: -------------------------------------------------------------------------------- 1 | 2 | class Failure { 3 | final String status; 4 | final String message; 5 | 6 | Failure({this.status = "fail", this.message}); 7 | 8 | factory Failure.fromJson(Map json){ 9 | return Failure(status: json['status'], message: json['message']); 10 | } 11 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/model/secret.dart: -------------------------------------------------------------------------------- 1 | 2 | class Secret { 3 | final String newsApiKey; 4 | final String footballApiKey; 5 | 6 | Secret({this.newsApiKey, this.footballApiKey}); 7 | 8 | factory Secret.fromJson(Map json) => 9 | Secret(newsApiKey: json['news_api_key'], footballApiKey: json["football_api_key"]); 10 | } -------------------------------------------------------------------------------- /lib/screens/screen.dart: -------------------------------------------------------------------------------- 1 | export 'home/football_screen.dart'; 2 | export 'profile_screen.dart'; 3 | export 'onboarding.dart'; 4 | export 'home_screen.dart'; 5 | export 'home/search_screen.dart'; 6 | export 'home/football_details.dart'; 7 | export 'home/league_table_screen.dart'; 8 | export 'news/news_screen.dart'; 9 | export 'bookmark_screen.dart'; -------------------------------------------------------------------------------- /.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: b041144f833e05cf463b8887fa12efdec9493488 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Mon Oct 19 10:21:41 WAT 2020 8 | sdk.dir=/Users/efhem/Library/Android/sdk 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/data/datasource/footbal/football_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:football/data/model/games_response.dart'; 3 | import 'package:football/domain/domain.dart'; 4 | import 'package:football/data/data.dart'; 5 | 6 | abstract class FootballRemoteDataSource { 7 | Future> table (int leagueId); 8 | Future> games (int leagueId); 9 | } -------------------------------------------------------------------------------- /lib/config/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/utils/storage_util.dart'; 2 | import 'package:theme_mode_handler/theme_mode_manager_interface.dart'; 3 | 4 | class AppTheme extends IThemeModeManager with StorageUtil { 5 | 6 | @override 7 | Future loadThemeMode() async { 8 | return theme; 9 | } 10 | 11 | @override 12 | Future saveThemeMode(String value) async { 13 | return setTheme(value); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /lib/screens/bookmark_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/widgets/widget.dart'; 3 | 4 | class BookmarkScreen extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | body: CustomScrollView( 9 | slivers: [ 10 | SliverCustomAppBar(flexibleTitle: "Bookmark"), 11 | ], 12 | ), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/model/league.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class League { 4 | final int id; 5 | final String area; 6 | final String name; 7 | final String code; 8 | final String emblemUrl; 9 | final int currentMatchday; 10 | final String startDate; 11 | final String endDate; 12 | 13 | League( {@required this.id, this.area, @required this.name, this.code, 14 | @required this.emblemUrl, @required this.currentMatchday, this.startDate, this.endDate}); 15 | 16 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/config/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Styles { 4 | 5 | // static const textTheme = TextTheme( 6 | // headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold), 7 | // headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic), 8 | // bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'), 9 | // ); 10 | 11 | static const appBarTheme = AppBarTheme( 12 | color: Colors.transparent, 13 | brightness: Brightness.light, 14 | elevation: 0, 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "soccer_animation-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "soccer_animation.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "soccer_animation-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /lib/data/model/news_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'articles_remote.dart'; 3 | 4 | part 'news_response.g.dart'; 5 | 6 | @JsonSerializable() 7 | class NewsResponse { 8 | String status; 9 | int totalResults; 10 | List
articles; 11 | 12 | NewsResponse({this.status, this.totalResults, this.articles}); 13 | 14 | factory NewsResponse.fromJson(Map json) => 15 | _$NewsResponseFromJson(json); 16 | 17 | Map toJson() => _$NewsResponseToJson(this); 18 | } 19 | -------------------------------------------------------------------------------- /lib/domain/repository/football_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/domain/domain.dart'; 2 | import 'package:football/model/match.dart'; 3 | import 'package:football/model/table_item.dart'; 4 | import 'package:football/model/team.dart'; 5 | 6 | abstract class FootballRepository { 7 | Future> fetchTable(int leagueId); 8 | 9 | Future>> fetchGames(int leagueId); 10 | 11 | List getLeagueTeam(int leagueId); 12 | 13 | List getLeagueTable(int leagueId); 14 | 15 | List topTeams(); 16 | } -------------------------------------------------------------------------------- /lib/data/datasource/secret_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async' show Future; 2 | import 'dart:convert' show json; 3 | import 'package:flutter/services.dart' show rootBundle; 4 | import 'package:football/domain/model/secret.dart'; 5 | 6 | 7 | class SecretLoader { 8 | final String secretPath; 9 | 10 | SecretLoader({this.secretPath}); 11 | 12 | Future load() { 13 | return rootBundle.loadStructuredData(this.secretPath, 14 | (jsonStr) async { 15 | final secret = Secret.fromJson(json.decode(jsonStr)); 16 | return secret; 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/model/news.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/model/articles_local.dart'; 2 | 3 | class News { 4 | final int id; 5 | final String author; 6 | final String title; 7 | final String description; 8 | final String url; 9 | final String urlToImage; 10 | final String publishAt; 11 | 12 | News(this.id, this.author, this.title, this.description, this.url, this.urlToImage, this.publishAt); 13 | 14 | static News fromArticle(int key, ArticleLocal article){ 15 | return News(key, article.author, article.title, 16 | article.description, article.url, article.urlToImage, 17 | article.publishedAt); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/data/datasource/news/news_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:football/data/helper/network_helper.dart'; 3 | import 'package:football/data/retrofit/api_service.dart'; 4 | import 'package:football/domain/domain.dart'; 5 | import 'package:football/data/data.dart'; 6 | 7 | 8 | class NewsRemoteDataSourceImpl with NewsRemoteDataSource { 9 | final ApiService apiService; 10 | 11 | NewsRemoteDataSourceImpl({@required this.apiService}); 12 | 13 | @override 14 | Future> sport(String apiKey) async { 15 | return await safeApiResult(apiService.news(apiKey: apiKey)); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /lib/domain/util/utils.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:intl/intl.dart'; 4 | 5 | class Utils { 6 | 7 | static String dateFormat(DateTime dateTime){ 8 | return "${dateTime.year}-${dateTime.month.toString().padLeft(2,'0')}-${dateTime.day.toString().padLeft(2,'0')}"; 9 | } 10 | 11 | static String toAppDate(String date) { 12 | final parsedDate = DateTime.tryParse(date); 13 | return parsedDate != null ? DateFormat('dd MMM').format(parsedDate) : ""; 14 | } 15 | 16 | static String toAppTime(String date) { 17 | final parsedDate = DateTime.tryParse(date); 18 | return parsedDate != null ? DateFormat('HH:mm').format(parsedDate) : ""; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/data/model/articles_remote.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'articles_remote.g.dart'; 4 | 5 | @JsonSerializable() 6 | class Article { 7 | String author; 8 | String title; 9 | String description; 10 | String url; 11 | String urlToImage; 12 | String publishedAt; 13 | 14 | Article( 15 | {this.author, 16 | this.title, 17 | this.description, 18 | this.url, 19 | this.urlToImage, 20 | this.publishedAt}); 21 | 22 | factory Article.fromJson(Map json) => 23 | _$ArticleFromJson(json); 24 | 25 | Map toJson() => _$ArticleToJson(this); 26 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | -------------------------------------------------------------------------------- /lib/model/team.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:football/data/model/league_table_local.dart'; 3 | 4 | class Team { 5 | final int id; 6 | final String leauge; 7 | final String name; 8 | final String code; 9 | final String emblemUrl; 10 | final int position; 11 | 12 | Team({ @required this.id, @required this.leauge, @required this.name, this.code, @required this.emblemUrl, this.position}); 13 | 14 | static Team fromTeamLocal(LeagueTableLocal leagueTableLocal) => 15 | Team(id: leagueTableLocal.teamId, leauge: leagueTableLocal.leagueName, 16 | name: leagueTableLocal.teamName, emblemUrl: leagueTableLocal.teamLogo, 17 | position: leagueTableLocal.position); 18 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.4' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/model/table_item.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:football/data/model/league_table_local.dart'; 4 | 5 | class TableItem { 6 | final int id; 7 | final int position; 8 | final String avatar; 9 | final String name; 10 | final int point; 11 | final int gamePlayed; 12 | final int goalDifference; 13 | 14 | TableItem({@required this.id, this.position, this.avatar, this.name, this.point, 15 | this.gamePlayed, this.goalDifference}); 16 | 17 | static TableItem fromTeamLocal(LeagueTableLocal lt) => 18 | TableItem(id: lt.id, position: lt.position, avatar: lt.teamLogo, name: lt.teamName, 19 | point: lt.points, gamePlayed: lt.playedGames, goalDifference: lt.goalDifference); 20 | } -------------------------------------------------------------------------------- /lib/screens/home/search_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/widgets/widget.dart'; 3 | 4 | class SearchScreen extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | body: SafeArea( 9 | child: Column( 10 | children: [ 11 | Padding( 12 | padding: EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 5), 13 | child: CustomSearchBar ( 14 | canStartSearch: true, 15 | showCursor: true, 16 | readOnly: false, 17 | ), 18 | ), 19 | Divider(thickness: 1.0) 20 | ], 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/data/datasource/news/news_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/hive/hive.dart'; 2 | import 'package:football/data/model/articles_local.dart'; 3 | import 'package:hive/hive.dart'; 4 | 5 | class NewsLocalDataSourceImpl { 6 | final Box _articleBox = Hive.box(HiveSetup.Article); 7 | 8 | void saveArticle(ArticleLocal article) { 9 | Future.value([_articleBox.add(article)]); 10 | } 11 | 12 | Future> saveArticles(List articles) async { 13 | final listOfKeys = await _articleBox.addAll(articles); 14 | return listOfKeys.toList(); 15 | } 16 | 17 | Stream watchArticle() => _articleBox.watch(); 18 | 19 | 20 | // Removes all entries from the box. 21 | Future deleteAll() async { 22 | return await _articleBox.clear(); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/data/hive/hive.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/model/articles_local.dart'; 2 | import 'package:football/data/model/games_local.dart'; 3 | import 'package:football/data/model/league_table_local.dart'; 4 | import 'package:hive/hive.dart'; 5 | 6 | class HiveSetup { 7 | 8 | static Future init() async { 9 | 10 | Hive.registerAdapter(ArticleLocalAdapter(), 0); 11 | await Hive.openBox(HiveSetup.Article); 12 | 13 | Hive.registerAdapter(LeagueTableLocalAdapter(), 1); 14 | await Hive.openBox(HiveSetup.LeagueTeam); 15 | 16 | Hive.registerAdapter(GamesLocalAdapter(), 2); 17 | await Hive.openBox(HiveSetup.Games); 18 | } 19 | 20 | static const Article = "articleLocal"; 21 | static const LeagueTeam = "leagueTeam"; 22 | static const Games = "games"; 23 | 24 | } -------------------------------------------------------------------------------- /lib/viewmodel/base_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:football/domain/model/failure.dart'; 4 | import 'package:football/model/news.dart'; 5 | 6 | enum ViewState { initial, loading, loaded } 7 | 8 | class BaseViewModel extends ChangeNotifier { 9 | 10 | ViewState _state = ViewState.initial; 11 | 12 | ViewState get 13 | state => _state; 14 | void setViewState(ViewState state, {Failure failure}) { 15 | _state = state; 16 | notifyListeners(); 17 | } 18 | 19 | T _result; 20 | T get result => _result; 21 | void setResult(T result) { 22 | _result = result; 23 | notifyListeners(); 24 | } 25 | 26 | Failure _errorData; 27 | Failure get failure => _errorData; 28 | void setError(Failure errorData) { 29 | _errorData = errorData; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /lib/data/helper/network_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:football/domain/model/failure.dart'; 4 | import 'package:football/data/helper/server_error.dart'; 5 | import 'package:football/domain/domain.dart'; 6 | 7 | 8 | //consider this boilerplate if this implementation does not work perfectly 9 | //https://dev.to/ashishrawat2911/handling-network-calls-and-exceptions-in-flutter-54me 10 | 11 | Future> safeApiResult(Future call) async { 12 | 13 | try { 14 | final result = await call; 15 | return Right(result); 16 | } on SocketException { 17 | return Left(Failure(message: "No Internet connection")); 18 | } 19 | catch (error) { 20 | final errorBody = ServerError.withError(error: error); 21 | return Left(Failure(message: errorBody.getErrorMessage())); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/data/model/articles_local.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/model/articles_remote.dart'; 2 | import 'package:hive/hive.dart'; 3 | 4 | part 'articles_local.g.dart'; 5 | 6 | @HiveType() 7 | class ArticleLocal { 8 | @HiveField(0) 9 | String author; 10 | @HiveField(1) 11 | String title; 12 | @HiveField(2) 13 | String description; 14 | @HiveField(3) 15 | String url; 16 | @HiveField(4) 17 | String urlToImage; 18 | @HiveField(5) 19 | String publishedAt; 20 | 21 | ArticleLocal( 22 | this.author, 23 | this.title, 24 | 25 | this.description, 26 | this.url, 27 | this.urlToImage, 28 | this.publishedAt); 29 | 30 | static ArticleLocal fromArticle(Article article){ 31 | return ArticleLocal(article.author, article.title, 32 | article.description, article.url, article.urlToImage, 33 | article.publishedAt); 34 | } 35 | } -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/data/model/news_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'news_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | NewsResponse _$NewsResponseFromJson(Map json) { 10 | return NewsResponse( 11 | status: json['status'] as String, 12 | totalResults: json['totalResults'] as int, 13 | articles: (json['articles'] as List) 14 | ?.map((e) => 15 | e == null ? null : Article.fromJson(e as Map)) 16 | ?.toList(), 17 | ); 18 | } 19 | 20 | Map _$NewsResponseToJson(NewsResponse instance) => 21 | { 22 | 'status': instance.status, 23 | 'totalResults': instance.totalResults, 24 | 'articles': instance.articles, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | test/ 33 | .last_build_id 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Exceptions to above rules. 45 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 46 | 47 | .g.dart 48 | secrets.json 49 | -------------------------------------------------------------------------------- /lib/widgets/list_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ListHeader extends StatelessWidget { 4 | final String headerTitle; 5 | final String trailingTitle; 6 | final Function onTrailingTap; 7 | 8 | const ListHeader({Key key, @required this.headerTitle, this.onTrailingTap, this.trailingTitle}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Row( 13 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 14 | children: [ 15 | Text(headerTitle, 16 | style: TextStyle( 17 | fontWeight: FontWeight.w900, fontSize: 16.0)), 18 | trailingTitle != null ? InkWell( 19 | onTap: onTrailingTap, 20 | child: Text(trailingTitle.toUpperCase(), 21 | style: TextStyle( 22 | fontWeight: FontWeight.w500, color: Colors.grey)), 23 | ): SizedBox.shrink() 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/data/model/articles_remote.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'articles_remote.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Article _$ArticleFromJson(Map json) { 10 | return Article( 11 | author: json['author'] as String, 12 | title: json['title'] as String, 13 | description: json['description'] as String, 14 | url: json['url'] as String, 15 | urlToImage: json['urlToImage'] as String, 16 | publishedAt: json['publishedAt'] as String, 17 | ); 18 | } 19 | 20 | Map _$ArticleToJson(Article instance) => { 21 | 'author': instance.author, 22 | 'title': instance.title, 23 | 'description': instance.description, 24 | 'url': instance.url, 25 | 'urlToImage': instance.urlToImage, 26 | 'publishedAt': instance.publishedAt, 27 | }; 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/utils/storage_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class StorageUtil { 4 | 5 | static Future _pref() async => await SharedPreferences.getInstance(); 6 | 7 | get theme => _pref().then((value) => value.getString(_THEME_MODE)); 8 | Future setTheme(String themeValue) { 9 | return _pref().then((value) => value.setString(_THEME_MODE, themeValue)); 10 | } 11 | 12 | static setBool(String key, bool value) async { 13 | final pref = await _pref(); 14 | pref.setBool(key, value); 15 | } 16 | 17 | static Future getBoolean(String key) async { 18 | final pref = await _pref(); 19 | return pref.getBool(key) ?? false; 20 | } 21 | 22 | static setString(String key, String value) async { 23 | final pref = await _pref(); 24 | pref.setString(key, value); 25 | } 26 | 27 | static Future getString(String key) async { 28 | final pref = await _pref(); 29 | return pref.getString(key) ?? ""; 30 | } 31 | 32 | static const _THEME_MODE = "theme_mode"; 33 | static const HAS_BOARDED = "has_boarded"; 34 | 35 | } -------------------------------------------------------------------------------- /lib/data/datasource/footbal/football_remote_data_source_imp.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:football/data/helper/network_helper.dart'; 3 | import 'package:football/data/model/games_response.dart'; 4 | import 'package:football/data/retrofit/api_service.dart'; 5 | import 'package:football/domain/domain.dart'; 6 | import 'package:football/data/data.dart'; 7 | 8 | 9 | class FootballRemoteDataSourceImpl with FootballRemoteDataSource { 10 | final ApiService apiService; 11 | 12 | FootballRemoteDataSourceImpl({@required this.apiService}); 13 | 14 | @override 15 | Future> table (int leagueId) async { 16 | return await safeApiResult(apiService.standings(leagueId)); 17 | } 18 | 19 | @override 20 | Future> games (int leagueId) async { 21 | final currentDate = DateTime.now(); 22 | final yesterday = currentDate.subtract(Duration(days: 1)); 23 | final next8Days = currentDate.add(Duration(days: 8)); 24 | 25 | return await safeApiResult(apiService.games( 26 | leagueId, 27 | Utils.dateFormat(yesterday), 28 | Utils.dateFormat(next8Days) 29 | )); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /lib/utils/custom_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class Util { 5 | 6 | static Route slideUpRoute(Widget route) { 7 | return PageRouteBuilder( 8 | pageBuilder: (context, animation, secondaryAnimation) => route, 9 | transitionDuration: Duration(milliseconds: 200), 10 | transitionsBuilder: (context, animation, secondaryAnimation, child) { 11 | var begin = Offset(0.0, 0.1); 12 | var end = Offset.zero; 13 | var curve = Curves.ease; 14 | var tween = 15 | Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); 16 | return SlideTransition(position: animation.drive(tween), child: child); 17 | }, 18 | ); 19 | } 20 | 21 | static void showSnackBar(BuildContext context, String message, Function function) { 22 | Future.delayed(Duration(milliseconds: 500), (){ 23 | Scaffold.of(context).showSnackBar(SnackBar( 24 | duration: Duration(days: 1), 25 | content: Text(message), 26 | action: SnackBarAction( 27 | label: "Retry", 28 | onPressed: () { 29 | Scaffold.of(context).hideCurrentSnackBar(); 30 | function(); 31 | }), 32 | )); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/data/datasource/footbal/football_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/hive/hive.dart'; 2 | import 'package:football/data/model/games_local.dart'; 3 | import 'package:football/data/model/league_table_local.dart'; 4 | import 'package:hive/hive.dart'; 5 | 6 | class FootballLocalDataSourceImpl { 7 | 8 | final Box _leagueTableBox = Hive.box(HiveSetup.LeagueTeam); 9 | 10 | void saveTeam(int teamId, LeagueTableLocal team){ 11 | Future.value([_leagueTableBox.put(teamId, team)]); 12 | } 13 | 14 | Future saveTeams(List _teams) async { 15 | final Map teams = Map.fromIterable(_teams, key: (v) => v.teamId, value: (v) => v); 16 | return await _leagueTableBox.putAll(teams); 17 | } 18 | 19 | List getLeagueTeam(int leagueId) => 20 | _leagueTableBox.values.where((element) => element.id == leagueId).toList(); 21 | 22 | List topTeams() => _leagueTableBox.values.where((element) => element.position < 5).toList(); 23 | 24 | String teamLogo(int teamId) { 25 | final leagueTable = _leagueTableBox.values.firstWhere((element) => element.teamId == teamId, orElse: null); 26 | return leagueTable != null ? leagueTable.teamLogo : ""; 27 | } 28 | } -------------------------------------------------------------------------------- /lib/data/model/articles_local.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'articles_local.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class ArticleLocalAdapter extends TypeAdapter { 10 | @override 11 | ArticleLocal read(BinaryReader reader) { 12 | var numOfFields = reader.readByte(); 13 | var fields = { 14 | for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 15 | }; 16 | return ArticleLocal( 17 | fields[0] as String, 18 | fields[1] as String, 19 | fields[2] as String, 20 | fields[3] as String, 21 | fields[4] as String, 22 | fields[5] as String, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, ArticleLocal obj) { 28 | writer 29 | ..writeByte(6) 30 | ..writeByte(0) 31 | ..write(obj.author) 32 | ..writeByte(1) 33 | ..write(obj.title) 34 | ..writeByte(2) 35 | ..write(obj.description) 36 | ..writeByte(3) 37 | ..write(obj.url) 38 | ..writeByte(4) 39 | ..write(obj.urlToImage) 40 | ..writeByte(5) 41 | ..write(obj.publishedAt); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/data/retrofit/api_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/model/games_response.dart'; 2 | import 'package:football/domain/domain.dart'; 3 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 4 | import 'package:retrofit/http.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:retrofit/retrofit.dart'; 7 | import 'package:football/data/data.dart'; 8 | 9 | part 'api_service.g.dart'; 10 | 11 | @RestApi(baseUrl: "https://api.football-data.org/v2/") 12 | abstract class ApiService { 13 | factory ApiService(Dio dio, {String baseUrl}) = _ApiService; 14 | 15 | static ApiService create( String footballApiKey) { 16 | final dio = Dio(); 17 | dio.interceptors.add(PrettyDioLogger()); 18 | dio.options.headers["X-Auth-Token"] = footballApiKey; 19 | return ApiService(dio); 20 | } 21 | 22 | @GET("http://newsapi.org/v2/top-headlines") 23 | Future news({ 24 | @Query("category") String category = "sport", 25 | @Query("country") String country = "us", 26 | @Query("apiKey") String apiKey, 27 | }); 28 | 29 | @GET("competitions/{id}/standings") 30 | Future standings ( @Path("id") int leagueId); 31 | 32 | @GET("competitions/{id}/matches") 33 | Future games ( 34 | @Path("id") int leagueId, 35 | @Query("dateFrom") String dateFrom, 36 | @Query("dateTo") String dateTo 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/widgets/round_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | 4 | class RoundImage extends StatelessWidget { 5 | final String imageUrl; 6 | final bool isElevated; 7 | final bool isSelected; 8 | final Function onTap; 9 | 10 | const RoundImage({Key key, @required this.imageUrl, this.isElevated = false, this.onTap, this.isSelected = false}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GestureDetector( 16 | onTap: onTap, 17 | child: Container( 18 | height: 70, 19 | decoration: BoxDecoration( 20 | border: isSelected ? Border.all(color: Colors.blueAccent, width: 1.5) : null, 21 | color: Colors.white, 22 | shape: BoxShape.circle, 23 | boxShadow: isElevated ? [ 24 | BoxShadow( 25 | color: Colors.grey.withOpacity(0.2), 26 | spreadRadius: 5, 27 | blurRadius: 7, 28 | offset: Offset(0, 0.5) // changes position of shadow 29 | ) 30 | ]: null, 31 | ), 32 | child: Container( 33 | padding: EdgeInsets.all(10.0), 34 | child: SvgPicture.network( 35 | imageUrl, 36 | width: 35.0, 37 | ), 38 | )), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/config/palette.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class Palette { 5 | static Color backgroundColor = Color(0xFFF8F8F8); 6 | static Color backgroundColorDark = Color(0xFF1D1B1C); 7 | static Color darkGrey = Color(0xFF202020); 8 | static Color faintBlue(ThemeMode themeMode){ 9 | return themeMode == ThemeMode.dark ? Colors.grey : Color(0xFFEAEDF3); 10 | } 11 | static Color lightWhite = Color(0xFFEAEAEB); 12 | 13 | static Color appBarTitleColor(ThemeMode themeMode) { 14 | return themeMode == ThemeMode.dark ? Colors.white : Colors.black; 15 | } 16 | static Brightness appBarTitleBrightness(ThemeMode themeMode){ 17 | return themeMode == ThemeMode.dark ? Brightness.dark : Brightness.light; 18 | } 19 | 20 | static void setUpStatusBarThemeMode(ThemeMode themeMode) { 21 | if (themeMode == ThemeMode.dark) { 22 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 23 | systemNavigationBarColor: Colors.black, 24 | statusBarColor: backgroundColorDark, 25 | //statusBarIconBrightness: Brightness.dark, // status bar icons' color 26 | //systemNavigationBarIconBrightness: Brightness.dark, //navigation bar icons' color 27 | )); 28 | } else { 29 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 30 | systemNavigationBarColor: Colors.black, 31 | statusBarColor: backgroundColor, 32 | )); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/viewmodel/news_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:football/model/news.dart'; 5 | import 'base_viewmodel.dart'; 6 | import 'package:football/domain/domain.dart'; 7 | 8 | class NewsProvider extends BaseViewModel { 9 | final NewsRepository newsRepository; 10 | 11 | StreamController> _streamController = StreamController(); 12 | Stream> get newsStream => _streamController.stream; 13 | 14 | NewsProvider({@required this.newsRepository}){ 15 | fetchNews(); 16 | 17 | //watchNews(); 18 | } 19 | 20 | //not working 21 | void watchNews() { 22 | var listNews = List(); 23 | 24 | newsRepository.watchNews().listen((news) { 25 | listNews.add(news); 26 | print("news title is ${news.title}"); 27 | }, onDone: () { // Not excecuting 28 | _streamController.sink.add(listNews); 29 | print("onDone is ${listNews.length}"); 30 | listNews = List(); 31 | }, onError: (error) { 32 | print("on error is ${error}"); 33 | }); 34 | } 35 | 36 | void fetchNews() async { 37 | setViewState(ViewState.loading); 38 | final result = await newsRepository.fetchNews(); 39 | result.fold( 40 | (l) => setError(l), 41 | (r) => setError(null) 42 | ); 43 | setViewState(ViewState.loaded); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | _streamController.close(); 49 | super.dispose(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/screens/news/news_details.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:football/config/palette.dart'; 5 | import 'package:football/model/news.dart'; 6 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 7 | import 'package:webview_flutter/webview_flutter.dart'; 8 | 9 | class NewsDetailsScreen extends StatefulWidget { 10 | 11 | final News news; 12 | NewsDetailsScreen(this.news); 13 | 14 | @override 15 | State createState() { 16 | return NewsDetailsState(); 17 | } 18 | } 19 | 20 | class NewsDetailsState extends State { 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | // Enable hybrid composition. 26 | //if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final themeMode = ThemeModeHandler.of(context).themeMode; 32 | return Scaffold( 33 | appBar: AppBar( 34 | title: Text(widget.news.author ?? widget.news.title, style: TextStyle(color: Palette.appBarTitleColor(themeMode))), 35 | leading: GestureDetector(child: Icon(Icons.arrow_back_ios, color: Palette.appBarTitleColor(themeMode)), 36 | onTap: () { Navigator.pop(context); },), 37 | ), 38 | body: WebView( 39 | initialUrl: widget.news.url, 40 | javascriptMode: JavascriptMode.unrestricted, 41 | ), 42 | ); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - path_provider (0.0.1): 7 | - Flutter 8 | - shared_preferences (0.0.1): 9 | - Flutter 10 | - sqflite (0.0.1): 11 | - Flutter 12 | - FMDB (~> 2.7.2) 13 | - webview_flutter (0.0.1): 14 | - Flutter 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 19 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 20 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 21 | - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - FMDB 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: Flutter 30 | path_provider: 31 | :path: ".symlinks/plugins/path_provider/ios" 32 | shared_preferences: 33 | :path: ".symlinks/plugins/shared_preferences/ios" 34 | sqflite: 35 | :path: ".symlinks/plugins/sqflite/ios" 36 | webview_flutter: 37 | :path: ".symlinks/plugins/webview_flutter/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 41 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 42 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 43 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 44 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 45 | webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 46 | 47 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 48 | 49 | COCOAPODS: 1.9.1 50 | -------------------------------------------------------------------------------- /lib/data/helper/server_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart' hide Headers; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:football/domain/model/failure.dart'; 4 | 5 | class ServerError implements Exception { 6 | String _errorMessage = ""; 7 | 8 | ServerError.withError({@required DioError error}) { 9 | _handleError(error); 10 | } 11 | 12 | getErrorMessage() { 13 | return _errorMessage; 14 | } 15 | 16 | _handleError(DioError error) { 17 | switch (error.type) { 18 | case DioErrorType.CANCEL: 19 | _errorMessage = "Request was cancelled"; 20 | break; 21 | case DioErrorType.CONNECT_TIMEOUT: 22 | _errorMessage = "Connection timeout"; 23 | break; 24 | case DioErrorType.DEFAULT: 25 | _errorMessage = 26 | "Connection failed due to internet connection"; 27 | break; 28 | case DioErrorType.RECEIVE_TIMEOUT: 29 | _errorMessage = "Receive timeout in connection"; 30 | break; 31 | case DioErrorType.RESPONSE: 32 | _errorMessage = "Unknown Error"; 33 | final errorBody = extractErrorBody(error); 34 | if( errorBody != null){ _errorMessage = errorBody.message;} 35 | break; 36 | case DioErrorType.SEND_TIMEOUT: 37 | _errorMessage = "Receive timeout in send request"; 38 | break; 39 | } 40 | return _errorMessage; 41 | } 42 | 43 | Failure extractErrorBody(DioError error){ 44 | try { 45 | return Failure.fromJson(error.response.data); 46 | } catch (e){ 47 | return null; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /lib/widgets/team_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/config/palette.dart'; 3 | import 'package:football/widgets/round_image.dart'; 4 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 5 | 6 | class TeamItem extends StatelessWidget { 7 | 8 | final String title; 9 | final String imageUrl; 10 | final String subtitle; 11 | final int id; 12 | final String trailing; 13 | final Function() onTap; 14 | 15 | const TeamItem({Key key, @required this.title, @required this.imageUrl, this.subtitle, @required this.id, this.trailing, this.onTap}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final themeMode = ThemeModeHandler.of(context).themeMode; 20 | return InkWell( 21 | onTap: onTap, 22 | child: Padding( 23 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 24 | child: ListTile( 25 | contentPadding: EdgeInsets.only(left: 10, top: 3, bottom: 3, right: 23.0), 26 | leading: RoundImage(imageUrl: imageUrl), 27 | title: Text(title, style: TextStyle(fontWeight: FontWeight.w900, fontSize: 15.0)), 28 | subtitle: Text(subtitle, style: TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold)), 29 | trailing: Container( 30 | padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0), 31 | decoration: BoxDecoration( 32 | color: Palette.faintBlue(themeMode), 33 | borderRadius: BorderRadius.circular(5.0) 34 | ), 35 | child: Text(trailing, style: TextStyle(fontWeight: FontWeight.bold)), 36 | ) 37 | ) 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/data/repository/news_repository_imp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:football/data/datasource/datasource.dart'; 4 | import 'package:football/data/datasource/news/news_local_data_source.dart'; 5 | import 'package:football/domain/domain.dart'; 6 | import 'package:football/data/model/articles_local.dart'; 7 | import 'package:football/model/news.dart'; 8 | import 'package:football/data/data.dart'; 9 | 10 | class NewsRepositoryImp with NewsRepository { 11 | final NewsLocalDataSourceImpl newsLocalDataSourceImpl; 12 | final NewsRemoteDataSource newsRemoteRepository; 13 | final SecretLoader secretLoader; 14 | 15 | NewsRepositoryImp(this.newsLocalDataSourceImpl, this.newsRemoteRepository, this.secretLoader); 16 | 17 | @override 18 | Future>> fetchNews() async { 19 | Either> saveResult; 20 | 21 | final apiKey = await secretLoader.load(); 22 | 23 | final apiResult = await newsRemoteRepository.sport(apiKey.newsApiKey); 24 | 25 | if (apiResult.isRight()) { 26 | final result = apiResult.getOrElse(() => null); 27 | if (result.articles != null) { 28 | await newsLocalDataSourceImpl.deleteAll(); 29 | final listOfKeys = await newsLocalDataSourceImpl.saveArticles( 30 | result.articles.map((e) => ArticleLocal.fromArticle(e)).toList()); 31 | saveResult = Right(listOfKeys); 32 | } else 33 | saveResult = left(Failure(message: "Empty news")); 34 | } else 35 | saveResult = apiResult.flatMap((a) => null); 36 | 37 | return saveResult; 38 | } 39 | 40 | @override 41 | Stream watchNews() => 42 | newsLocalDataSourceImpl.watchArticle().map((event) => News.fromArticle(event.key, event.value)); 43 | } 44 | -------------------------------------------------------------------------------- /lib/model/match.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:football/data/model/games_local.dart'; 3 | import 'package:football/domain/domain.dart'; 4 | 5 | class Match { 6 | final int id; 7 | final String homeImageUrl; 8 | final String awayImageUrl; 9 | final String currentMinute; 10 | final bool isLive; 11 | final String league; 12 | final String homeTeam; 13 | final String awayTeam; 14 | final int homeScore; 15 | final int awayScore; 16 | final String playTime; 17 | final String playDate; 18 | 19 | Match( 20 | {@required this.id, 21 | this.homeImageUrl, 22 | this.awayImageUrl, 23 | this.currentMinute, 24 | this.isLive = false, 25 | this.league, 26 | this.homeTeam, 27 | this.awayTeam, 28 | this.homeScore, 29 | this.awayScore, 30 | this.playTime, 31 | this.playDate}); 32 | 33 | static Match fromGamesLocal(GamesLocal gamesLocal) { 34 | return Match( 35 | id: gamesLocal.id, 36 | homeImageUrl: gamesLocal.homeTeamLogo, 37 | awayImageUrl: gamesLocal.awayTeamLogo, 38 | currentMinute: "15", 39 | isLive: gamesLocal.status == "LIVE" || 40 | gamesLocal.status == "IN_PLAY" || 41 | gamesLocal.status == "PAUSED", 42 | league: gamesLocal.leagueName, 43 | homeTeam: gamesLocal.homeTeamName, 44 | awayTeam: gamesLocal.awayTeamName, 45 | homeScore: gamesLocal.scoreFullTimeHomeTeam ?? 46 | gamesLocal.scoreHalfTimeHomeTeam, 47 | awayScore: gamesLocal.scoreFullTimeAwayTeam ?? 48 | gamesLocal.scoreHalfTimeAwayTeam, 49 | playTime: Utils.toAppTime(gamesLocal.utcDate), 50 | playDate: Utils.toAppDate(gamesLocal.utcDate) ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/config/palette.dart'; 3 | import 'package:football/screens/screen.dart'; 4 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 5 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 6 | 7 | class HomeScreen extends StatefulWidget { 8 | @override 9 | _HomeScreenState createState() => _HomeScreenState(); 10 | } 11 | 12 | class _HomeScreenState extends State { 13 | final _destinations = [ 14 | FootballScreen(), 15 | NewsScreen(), 16 | BookmarkScreen(), 17 | ProfileScreen() 18 | ]; 19 | 20 | final _bottomNavItem = { 21 | "Football": MdiIcons.soccer, 22 | "News": Icons.chat_bubble_outline, 23 | "Bookmark": Icons.done_outline, 24 | "Profile": Icons.person_outline 25 | }; 26 | 27 | int _currentIndex = 0; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | 32 | Palette.setUpStatusBarThemeMode(ThemeModeHandler.of(context).themeMode); 33 | 34 | return Scaffold( 35 | body: IndexedStack( 36 | index: _currentIndex, 37 | children: _destinations, 38 | ), 39 | bottomNavigationBar: BottomNavigationBar( 40 | iconSize: 28.0, 41 | currentIndex: _currentIndex, 42 | onTap: (index) => setState(() => _currentIndex = index), 43 | type: BottomNavigationBarType.fixed, 44 | items: _bottomNavItem 45 | .map((title, icon) => MapEntry( 46 | title, 47 | BottomNavigationBarItem( 48 | icon: Icon(icon), 49 | title: Text(title) 50 | ))) 51 | .values 52 | .toList(), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | football 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | io.flutter.embedded_views_preview 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/data/model/league_table_local.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:football/data/data.dart'; 3 | import 'package:hive/hive.dart'; 4 | 5 | part 'league_table_local.g.dart'; 6 | 7 | @HiveType() 8 | class LeagueTableLocal { 9 | @HiveField(0) 10 | int id; //league id 11 | @HiveField(1) 12 | String leagueName; 13 | @HiveField(2) 14 | int position; 15 | @HiveField(3) 16 | int teamId; 17 | @HiveField(4) 18 | String teamName; 19 | @HiveField(5) 20 | String teamLogo; 21 | @HiveField(6) 22 | int playedGames; 23 | @HiveField(7) 24 | String form; 25 | @HiveField(8) 26 | int won; 27 | @HiveField(9) 28 | int draw; 29 | @HiveField(10) 30 | int lost; 31 | @HiveField(11) 32 | int points; 33 | @HiveField(12) 34 | int goalsFor; 35 | @HiveField(13) 36 | int goalsAgainst; 37 | @HiveField(14) 38 | int goalDifference; 39 | 40 | LeagueTableLocal(this.id, this.leagueName, this.position, this.teamId, 41 | this.teamName, this.teamLogo, this.playedGames, this.form, this.won, 42 | this.draw, this.lost, this.points, this.goalsFor, this.goalsAgainst, 43 | this.goalDifference); 44 | 45 | static List fromTable(TableResponse tableResponse) { 46 | final leagueId = tableResponse.competition.id; 47 | final leagueName = tableResponse.competition.name; 48 | final standing = tableResponse.standings[0]; 49 | if(standing != null && standing.table.isNotEmpty){ 50 | return standing.table.map((table) => 51 | LeagueTableLocal(leagueId, leagueName, table.position, table.team.id, 52 | table.team.name, table.team.crestUrl, table.playedGames, table.form, 53 | table.won, table.draw, table.lost, table.points, table.goalsFor, 54 | table.goalsAgainst, table.goalDifference) 55 | ).toList(); 56 | 57 | } else return null; 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /lib/widgets/sliver_custom_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/config/palette.dart'; 3 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 4 | 5 | class SliverCustomAppBar extends StatelessWidget { 6 | 7 | final String title; 8 | final String flexibleTitle; 9 | final Widget trailing; 10 | final IconData iconLeading; 11 | final Function onLeadingTap; 12 | 13 | const SliverCustomAppBar({Key key, this.title, this.flexibleTitle, this.trailing, this.iconLeading, this.onLeadingTap}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final themeMode = ThemeModeHandler.of(context).themeMode; 18 | return SliverAppBar( 19 | floating: true, 20 | pinned: title != null ? true : false, 21 | backgroundColor: Theme.of(context).scaffoldBackgroundColor, 22 | iconTheme: IconThemeData(), 23 | brightness: Palette.appBarTitleBrightness(themeMode), 24 | actions: trailing != null ? [trailing] : null, 25 | centerTitle: false, 26 | expandedHeight: flexibleTitle != null ? 100.0 : 0.0, 27 | title: title != null ? Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: Palette.appBarTitleColor(themeMode)),) : SizedBox.shrink(), 28 | leading: iconLeading != null ? InkWell(child: Icon(iconLeading, size: 20.0), onTap: onLeadingTap): SizedBox.shrink(), 29 | flexibleSpace: flexibleTitle != null ? FlexibleSpaceBar( 30 | titlePadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), 31 | centerTitle: false, 32 | title: Text(flexibleTitle, style: TextStyle( 33 | fontSize: 25.0, 34 | letterSpacing: -1, 35 | color: Palette.appBarTitleColor(themeMode), 36 | fontWeight: FontWeight.bold)), 37 | ): SizedBox.shrink(), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widgets/custom_search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSearchBar extends StatelessWidget { 4 | final Function onTapSearch; 5 | final bool canStartSearch; 6 | final bool showCursor; 7 | final bool readOnly; 8 | 9 | const CustomSearchBar( 10 | {Key key, 11 | this.onTapSearch, 12 | this.canStartSearch = false, 13 | this.showCursor = false, 14 | this.readOnly = true}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Row( 20 | children: [ 21 | Expanded( 22 | child: ClipRRect( 23 | borderRadius: BorderRadius.circular(10.0), 24 | child: Container( 25 | height: 35.0, 26 | color: Theme.of(context).hoverColor, 27 | child: Row( 28 | children: [ 29 | SizedBox(width: 10.0), 30 | Icon(Icons.search), 31 | SizedBox(width: 5.0), 32 | Expanded( 33 | child: TextFormField( 34 | onTap: readOnly ? onTapSearch : null, 35 | showCursor: showCursor, 36 | readOnly: readOnly, 37 | decoration: InputDecoration( 38 | hintText: "Search", border: InputBorder.none), 39 | ), 40 | ), 41 | Icon(Icons.mic_none), 42 | SizedBox(width: 8.0), 43 | ], 44 | ), 45 | ), 46 | ), 47 | ), 48 | canStartSearch 49 | ? FlatButton( 50 | child: Text(("Cancel")), 51 | splashColor: Colors.transparent, 52 | highlightColor: Colors.transparent, 53 | onPressed: () => Navigator.of(context).pop(), 54 | ) 55 | : SizedBox.shrink() 56 | ], 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.efhem.football" 42 | // Required by the Flutter WebView plugin. 43 | minSdkVersion 19 44 | targetSdkVersion 28 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | } 65 | -------------------------------------------------------------------------------- /lib/data/model/league_table_local.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'league_table_local.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class LeagueTableLocalAdapter extends TypeAdapter { 10 | @override 11 | LeagueTableLocal read(BinaryReader reader) { 12 | var numOfFields = reader.readByte(); 13 | var fields = { 14 | for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 15 | }; 16 | return LeagueTableLocal( 17 | fields[0] as int, 18 | fields[1] as String, 19 | fields[2] as int, 20 | fields[3] as int, 21 | fields[4] as String, 22 | fields[5] as String, 23 | fields[6] as int, 24 | fields[7] as String, 25 | fields[8] as int, 26 | fields[9] as int, 27 | fields[10] as int, 28 | fields[11] as int, 29 | fields[12] as int, 30 | fields[13] as int, 31 | fields[14] as int, 32 | ); 33 | } 34 | 35 | @override 36 | void write(BinaryWriter writer, LeagueTableLocal obj) { 37 | writer 38 | ..writeByte(15) 39 | ..writeByte(0) 40 | ..write(obj.id) 41 | ..writeByte(1) 42 | ..write(obj.leagueName) 43 | ..writeByte(2) 44 | ..write(obj.position) 45 | ..writeByte(3) 46 | ..write(obj.teamId) 47 | ..writeByte(4) 48 | ..write(obj.teamName) 49 | ..writeByte(5) 50 | ..write(obj.teamLogo) 51 | ..writeByte(6) 52 | ..write(obj.playedGames) 53 | ..writeByte(7) 54 | ..write(obj.form) 55 | ..writeByte(8) 56 | ..write(obj.won) 57 | ..writeByte(9) 58 | ..write(obj.draw) 59 | ..writeByte(10) 60 | ..write(obj.lost) 61 | ..writeByte(11) 62 | ..write(obj.points) 63 | ..writeByte(12) 64 | ..write(obj.goalsFor) 65 | ..writeByte(13) 66 | ..write(obj.goalsAgainst) 67 | ..writeByte(14) 68 | ..write(obj.goalDifference); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Football 2 | 3 | Football is an attempt to build a full featured flutter app that shows favourite league tables, teams, standings, live scores and generally sport news. 4 | The focus of this project is to use different widgets available on flutter and especially on demonstrating how to structure your code, design your architecture, 5 | and the eventual impact of adopting these patterns on testing and maintaining your app. 6 | 7 | ## Getting Started 8 | 9 | Clone the repository. The app make use of [football-data](https://www.football-data.org/) and [New](https://newsapi.org/) API. click the link to get your api key if you dont have. 10 | - Create a file at the root of your project call it `secrets.json` 11 | - Copy and paste below json into the file. 12 | 13 | ``` 14 | { 15 | "news_api_key": "your-news-api-key", 16 | "football_api_key": "your-football_api_key" 17 | } 18 | ``` 19 | 20 | ## UI 21 | UI inspired by [here](https://www.uplabs.com/posts/profile-light-mode-and-dark-mode) and [here](https://www.uplabs.com/posts/sport-news-app-564029c3-8787-4417-87b8-9047a58b29b4) 22 | 23 | ![alt text](https://github.com/Efhemo/football/blob/feature/games/screenshots/football.png) 24 | 25 | ## Libraries 26 | 27 | - [Retrofit For Dart](https://pub.dev/packages/retrofit) for easy CRUD request 28 | - [dio](https://github.com/flutterchina/dio/) A powerful Http client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc. 29 | - [get it](https://pub.dev/packages/get_it) Service Locator for Dart and Flutter projects 30 | - [dartz](https://pub.dev/packages/dartz) Functional programming in Dart 31 | - [hive](https://docs.hivedb.dev/#/README) Super fast database written in pure Dart 32 | 33 | ## Todo 34 | 35 | The app in this project aims to be simple enough that you can understand it quickly, but complex enough to showcase difficult design decisions and testing scenarios. 36 | 37 | There are some improvement i intend to make 38 | - Using BLOC for better state management 39 | - Building setting screen 40 | - Integrate firebase (chat, room or group) for user interaction on the app 41 | - Animations 42 | - Test 43 | -------------------------------------------------------------------------------- /lib/viewmodel/football_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/domain/repository/football_repository.dart'; 2 | import 'package:football/model/match.dart'; 3 | import 'package:football/model/table_item.dart'; 4 | import 'package:football/model/team.dart'; 5 | import 'package:football/viewmodel/base_viewmodel.dart'; 6 | 7 | class FootballProvider extends BaseViewModel { 8 | final FootballRepository footballRepository; 9 | 10 | FootballProvider(this.footballRepository); 11 | 12 | List _liveMatch = List(); 13 | List get liveMatch => _liveMatch; 14 | 15 | List _nonLiveMatch = List(); 16 | List get nonLiveMatch => _nonLiveMatch; 17 | 18 | List teams(int leagueId) { 19 | return footballRepository.getLeagueTeam(leagueId); 20 | } 21 | 22 | List table(int leagueId) { 23 | final result = footballRepository.getLeagueTable(leagueId); 24 | result.sort((a, b) => a.position.compareTo(b.position)); 25 | return result; 26 | } 27 | 28 | List topTeams() { 29 | return footballRepository.topTeams(); 30 | } 31 | 32 | List fiveTopTeams() { 33 | final teams = topTeams(); 34 | teams.sort((a, b) => a.position.compareTo(b.position)); 35 | return teams.length > 5 36 | ? teams.sublist(0, 5) 37 | : teams.sublist(0, teams.length); 38 | } 39 | 40 | void fetchTable(int leagueId) async { 41 | await Future.delayed(Duration(milliseconds: 500)); 42 | setViewState(ViewState.loading); 43 | 44 | final result = await footballRepository.fetchTable(leagueId); 45 | result.fold((l) => setError(l), (r) => setError(null)); 46 | setViewState(ViewState.loaded); 47 | } 48 | 49 | void fetchGames(int leagueId) async { 50 | await Future.delayed(Duration(milliseconds: 500)); 51 | setViewState(ViewState.loading); 52 | 53 | final result = await footballRepository.fetchGames(leagueId); 54 | result.fold((l) => setError(l), (r) { 55 | _liveMatch = r.where((e) => e.isLive).toList(); 56 | _nonLiveMatch = r.where((e) => e.isLive == false).toList(); 57 | notifyListeners(); 58 | }); 59 | setViewState(ViewState.loaded); 60 | } 61 | } -------------------------------------------------------------------------------- /lib/injection_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/datasource/datasource.dart'; 2 | import 'package:football/data/datasource/footbal/football_local_data_source.dart'; 3 | import 'package:football/data/datasource/footbal/football_remote_data_source_imp.dart'; 4 | import 'package:football/data/repository/football_repository_imp.dart'; 5 | import 'package:football/data/retrofit/api_service.dart'; 6 | import 'package:football/viewmodel/provider.dart'; 7 | import 'package:get_it/get_it.dart'; 8 | import 'package:football/domain/domain.dart'; 9 | import 'package:football/data/data.dart'; 10 | 11 | 12 | import 'data/repository/news_repository_imp.dart'; 13 | import 'data/datasource/footbal/news_remote_datasource.dart'; 14 | 15 | final injector = GetIt.instance; 16 | 17 | Future init() async { 18 | 19 | //provide secret keys 20 | injector.registerSingleton(SecretLoader(secretPath: "secrets.json") ); 21 | 22 | //provide ApiService 23 | final SecretLoader secretLoader = injector.get(); 24 | final apikey = await secretLoader.load(); 25 | injector.registerLazySingleton(() => ApiService.create(apikey.footballApiKey)); 26 | 27 | //NewsRemoteDataSourceImpl 28 | injector.registerLazySingleton(() => NewsRemoteDataSourceImpl(apiService: injector.get())); 29 | //NewsRemoteDataSourceImpl 30 | injector.registerLazySingleton(() => NewsLocalDataSourceImpl()); 31 | //NewsRepository 32 | injector.registerLazySingleton(() => NewsRepositoryImp(injector.get(), injector.get(), injector.get())); 33 | 34 | //FootballRemoteDataSourceImpl 35 | injector.registerLazySingleton(() => FootballRemoteDataSourceImpl(apiService: injector.get())); 36 | //FootballRemoteDataSourceImpl 37 | injector.registerLazySingleton(() => FootballLocalDataSourceImpl()); 38 | //FootballRepository 39 | injector.registerLazySingleton(() => FootballRepositoryImp(injector.get(), injector.get())); 40 | 41 | 42 | injector.registerFactory(() => NewsProvider(newsRepository: injector.get())); 43 | injector.registerFactory(() => FootballProvider(injector.get())); 44 | } 45 | -------------------------------------------------------------------------------- /lib/data/model/games_local.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/data.dart'; 2 | import 'package:football/data/model/games_response.dart'; 3 | import 'package:hive/hive.dart'; 4 | 5 | part 'games_local.g.dart'; 6 | 7 | @HiveType() 8 | class GamesLocal { 9 | @HiveField(0) 10 | int leagueId; 11 | @HiveField(1) 12 | String leagueName; 13 | @HiveField(2) 14 | int id; //match id 15 | @HiveField(3) 16 | String utcDate; 17 | @HiveField(4) 18 | String status; 19 | @HiveField(5) 20 | int matchDay; 21 | @HiveField(6) 22 | String winner; 23 | @HiveField(7) 24 | String duration; 25 | @HiveField(8) 26 | int scoreFullTimeHomeTeam; 27 | @HiveField(9) 28 | int scoreFullTimeAwayTeam; 29 | @HiveField(10) 30 | int scoreHalfTimeHomeTeam; 31 | @HiveField(11) 32 | int scoreHalfTimeAwayTeam; 33 | @HiveField(12) 34 | int homeTeamId; 35 | @HiveField(13) 36 | String homeTeamName; 37 | @HiveField(14) 38 | String homeTeamLogo; 39 | @HiveField(15) 40 | int awayTeamId; 41 | @HiveField(16) 42 | String awayTeamName; 43 | @HiveField(17) 44 | String awayTeamLogo; 45 | @HiveField(18) 46 | String lastUpdated; 47 | 48 | GamesLocal( 49 | this.leagueId, 50 | this.leagueName, 51 | this.id, 52 | this.utcDate, 53 | this.status, 54 | this.matchDay, 55 | this.winner, 56 | this.duration, 57 | this.scoreFullTimeHomeTeam, 58 | this.scoreFullTimeAwayTeam, 59 | this.scoreHalfTimeHomeTeam, 60 | this.scoreHalfTimeAwayTeam, 61 | this.homeTeamId, 62 | this.homeTeamName, 63 | this.homeTeamLogo, 64 | this.awayTeamId, 65 | this.awayTeamName, 66 | this.awayTeamLogo, 67 | this.lastUpdated); 68 | 69 | static GamesLocal fromMatches(Competition competition, Matches match, 70 | String homeTeamLogo, String awayTeamLogo){ 71 | 72 | return GamesLocal( 73 | competition.id, 74 | competition.name, 75 | match.id, match.utcDate, match.status, match.matchday, 76 | match.score.winner, 77 | match.score.duration, 78 | match.score.fullTime.homeTeam, //scoreFullTimeHomeTeam 79 | match.score.fullTime.awayTeam, 80 | match.score.halfTime.homeTeam, 81 | match.score.halfTime.awayTeam, 82 | match.homeTeam.id, //homeTeamId, 83 | match.homeTeam.name, 84 | homeTeamLogo, 85 | match.awayTeam.id, 86 | match.awayTeam.name, 87 | awayTeamLogo, 88 | match.lastUpdated 89 | ); 90 | } 91 | } -------------------------------------------------------------------------------- /lib/data/model/games_local.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'games_local.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class GamesLocalAdapter extends TypeAdapter { 10 | @override 11 | GamesLocal read(BinaryReader reader) { 12 | var numOfFields = reader.readByte(); 13 | var fields = { 14 | for (var i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 15 | }; 16 | return GamesLocal( 17 | fields[0] as int, 18 | fields[1] as String, 19 | fields[2] as int, 20 | fields[3] as String, 21 | fields[4] as String, 22 | fields[5] as int, 23 | fields[6] as String, 24 | fields[7] as String, 25 | fields[8] as int, 26 | fields[9] as int, 27 | fields[10] as int, 28 | fields[11] as int, 29 | fields[12] as int, 30 | fields[13] as String, 31 | fields[14] as String, 32 | fields[15] as int, 33 | fields[16] as String, 34 | fields[17] as String, 35 | fields[18] as String, 36 | ); 37 | } 38 | 39 | @override 40 | void write(BinaryWriter writer, GamesLocal obj) { 41 | writer 42 | ..writeByte(19) 43 | ..writeByte(0) 44 | ..write(obj.leagueId) 45 | ..writeByte(1) 46 | ..write(obj.leagueName) 47 | ..writeByte(2) 48 | ..write(obj.id) 49 | ..writeByte(3) 50 | ..write(obj.utcDate) 51 | ..writeByte(4) 52 | ..write(obj.status) 53 | ..writeByte(5) 54 | ..write(obj.matchDay) 55 | ..writeByte(6) 56 | ..write(obj.winner) 57 | ..writeByte(7) 58 | ..write(obj.duration) 59 | ..writeByte(8) 60 | ..write(obj.scoreFullTimeHomeTeam) 61 | ..writeByte(9) 62 | ..write(obj.scoreFullTimeAwayTeam) 63 | ..writeByte(10) 64 | ..write(obj.scoreHalfTimeHomeTeam) 65 | ..writeByte(11) 66 | ..write(obj.scoreHalfTimeAwayTeam) 67 | ..writeByte(12) 68 | ..write(obj.homeTeamId) 69 | ..writeByte(13) 70 | ..write(obj.homeTeamName) 71 | ..writeByte(14) 72 | ..write(obj.homeTeamLogo) 73 | ..writeByte(15) 74 | ..write(obj.awayTeamId) 75 | ..writeByte(16) 76 | ..write(obj.awayTeamName) 77 | ..writeByte(17) 78 | ..write(obj.awayTeamLogo) 79 | ..writeByte(18) 80 | ..write(obj.lastUpdated); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/data/retrofit/api_service.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'api_service.dart'; 4 | 5 | // ************************************************************************** 6 | // RetrofitGenerator 7 | // ************************************************************************** 8 | 9 | class _ApiService implements ApiService { 10 | _ApiService(this._dio, {this.baseUrl}) { 11 | ArgumentError.checkNotNull(_dio, '_dio'); 12 | baseUrl ??= 'https://api.football-data.org/v2/'; 13 | } 14 | 15 | final Dio _dio; 16 | 17 | String baseUrl; 18 | 19 | @override 20 | Future news( 21 | {category = "sport", country = "us", apiKey}) async { 22 | const _extra = {}; 23 | final queryParameters = { 24 | r'category': category, 25 | r'country': country, 26 | r'apiKey': apiKey 27 | }; 28 | queryParameters.removeWhere((k, v) => v == null); 29 | final _data = {}; 30 | final _result = await _dio.request>( 31 | 'http://newsapi.org/v2/top-headlines', 32 | queryParameters: queryParameters, 33 | options: RequestOptions( 34 | method: 'GET', 35 | headers: {}, 36 | extra: _extra, 37 | baseUrl: baseUrl), 38 | data: _data); 39 | final value = NewsResponse.fromJson(_result.data); 40 | return value; 41 | } 42 | 43 | @override 44 | Future standings(leagueId) async { 45 | ArgumentError.checkNotNull(leagueId, 'leagueId'); 46 | const _extra = {}; 47 | final queryParameters = {}; 48 | final _data = {}; 49 | final _result = await _dio.request>( 50 | 'competitions/$leagueId/standings', 51 | queryParameters: queryParameters, 52 | options: RequestOptions( 53 | method: 'GET', 54 | headers: {}, 55 | extra: _extra, 56 | baseUrl: baseUrl), 57 | data: _data); 58 | final value = TableResponse.fromJson(_result.data); 59 | return value; 60 | } 61 | 62 | @override 63 | Future games(leagueId, dateFrom, dateTo) async { 64 | ArgumentError.checkNotNull(leagueId, 'leagueId'); 65 | ArgumentError.checkNotNull(dateFrom, 'dateFrom'); 66 | ArgumentError.checkNotNull(dateTo, 'dateTo'); 67 | const _extra = {}; 68 | final queryParameters = { 69 | r'dateFrom': dateFrom, 70 | r'dateTo': dateTo 71 | }; 72 | final _data = {}; 73 | final _result = await _dio.request>( 74 | 'competitions/$leagueId/matches', 75 | queryParameters: queryParameters, 76 | options: RequestOptions( 77 | method: 'GET', 78 | headers: {}, 79 | extra: _extra, 80 | baseUrl: baseUrl), 81 | data: _data); 82 | final value = GamesResponse.fromJson(_result.data); 83 | return value; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/data/repository/football_repository_imp.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/datasource/footbal/football_local_data_source.dart'; 2 | import 'package:football/data/datasource/footbal/football_remote_datasource.dart'; 3 | import 'package:football/data/model/games_local.dart'; 4 | import 'package:football/domain/domain.dart'; 5 | import 'package:football/data/model/league_table_local.dart'; 6 | import 'package:football/model/match.dart'; 7 | import 'package:football/model/table_item.dart'; 8 | import 'package:football/model/team.dart'; 9 | 10 | class FootballRepositoryImp implements FootballRepository { 11 | final FootballRemoteDataSource footballRemoteDataSource; 12 | final FootballLocalDataSourceImpl footballLocalDataSourceImpl; 13 | 14 | FootballRepositoryImp(this.footballRemoteDataSource, this.footballLocalDataSourceImpl); 15 | 16 | @override 17 | Future> fetchTable(int leagueId) async { 18 | 19 | Either saveResult; 20 | 21 | final apiResult = await footballRemoteDataSource.table(leagueId); 22 | 23 | if (apiResult.isRight()) { 24 | final response = apiResult.getOrElse(() => null); 25 | if (response != null) { 26 | await footballLocalDataSourceImpl.saveTeams(LeagueTableLocal.fromTable(response)); 27 | saveResult = Right(1); 28 | } else 29 | saveResult = left(Failure(message: "Empty table")); 30 | } else 31 | saveResult = apiResult.flatMap((a) => null); 32 | 33 | return saveResult; 34 | } 35 | 36 | @override 37 | Future>> fetchGames(int leagueId) async { 38 | 39 | Either> saveResult; 40 | 41 | final apiResult = await footballRemoteDataSource.games(leagueId); 42 | 43 | if (apiResult.isRight()) { 44 | final response = apiResult.getOrElse(() => null); 45 | if (response != null) { 46 | final games = response.matches.map((element) { 47 | final homeTeamLogo = footballLocalDataSourceImpl.teamLogo(element.homeTeam.id); 48 | final awayTeamLogo = footballLocalDataSourceImpl.teamLogo(element.awayTeam.id); 49 | 50 | //This implementation have diverge to being online first 51 | final local = GamesLocal.fromMatches(response.competition, element, homeTeamLogo, awayTeamLogo); 52 | return Match.fromGamesLocal(local); 53 | }).toList(); 54 | saveResult = Right(games); 55 | } else saveResult = left(Failure(message: "Empty game")); 56 | } else saveResult = apiResult.flatMap((a) => null); 57 | 58 | return saveResult; 59 | } 60 | 61 | @override 62 | List getLeagueTeam(int leagueId) { 63 | return footballLocalDataSourceImpl.getLeagueTeam(leagueId).map((e) => 64 | Team.fromTeamLocal(e)).toList(); 65 | } 66 | 67 | @override 68 | List getLeagueTable(int leagueId) { 69 | return footballLocalDataSourceImpl.getLeagueTeam(leagueId).map((e) => 70 | TableItem.fromTeamLocal(e)).toList(); 71 | } 72 | 73 | @override 74 | List topTeams() => footballLocalDataSourceImpl.topTeams().map((e) => 75 | Team.fromTeamLocal(e)).toList(); 76 | 77 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/screens/news/news_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:football/data/hive/hive.dart'; 4 | import 'package:football/data/model/articles_local.dart'; 5 | import 'package:football/model/news.dart'; 6 | import 'package:football/viewmodel/provider.dart'; 7 | import 'package:football/widgets/widget.dart'; 8 | import 'package:hive/hive.dart'; 9 | import 'package:hive_flutter/hive_flutter.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class NewsScreen extends StatefulWidget { 13 | @override 14 | _NewsScreenState createState() => _NewsScreenState(); 15 | } 16 | 17 | class _NewsScreenState extends State 18 | with AutomaticKeepAliveClientMixin { 19 | @override 20 | void initState() { 21 | super.initState(); 22 | 23 | Future.delayed(Duration.zero, () { 24 | Provider.of(context, listen: false); 25 | }); 26 | } 27 | 28 | void showSnackBar(String message) { 29 | Future.delayed(Duration(milliseconds: 500), (){ 30 | Scaffold.of(context).showSnackBar(SnackBar( 31 | duration: Duration(days: 1), 32 | content: Text(message), 33 | action: SnackBarAction( 34 | label: "Retry", 35 | onPressed: () { 36 | Scaffold.of(context).hideCurrentSnackBar(); 37 | Provider.of(context, listen: false).fetchNews(); 38 | }), 39 | )); 40 | }); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | super.build(context); 46 | 47 | return Scaffold( 48 | body: ValueListenableBuilder>( 49 | valueListenable: Hive.box(HiveSetup.Article).listenable(), 50 | builder: (BuildContext context, box, _) { 51 | final article = box.values.toList(); 52 | final keys = box.keys.toList(); 53 | //or 54 | //final key = box.keyAt(index); 55 | //final article = box.getAt(index); 56 | return Stack( 57 | children: [ 58 | CustomScrollView( 59 | slivers: [ 60 | SliverCustomAppBar(flexibleTitle: "News"), 61 | SliverList( 62 | delegate: SliverChildBuilderDelegate((context, index) { 63 | final news = News.fromArticle(keys[index], article[index]); 64 | return NewsItem( 65 | news: news, 66 | onTap: () => Navigator.pushNamed(context, "/newsDetails", arguments: news), 67 | ); 68 | }, childCount: keys.length)) 69 | ], 70 | ), 71 | Consumer(builder: (context, newsProvider, child) { 72 | if (newsProvider.failure != null) { 73 | showSnackBar(newsProvider.failure.message); 74 | } 75 | return Visibility( 76 | visible: newsProvider.state == ViewState.loading, 77 | child: SpinKitSquareCircle( 78 | color: Theme.of(context).accentColor, 79 | size: 50.0, 80 | ), 81 | ); 82 | }) 83 | ], 84 | ); 85 | }, 86 | ), 87 | ); 88 | } 89 | 90 | @override 91 | bool get wantKeepAlive => true; 92 | } 93 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: football 2 | description: A new Flutter application. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | version: 1.0.0+1 9 | 10 | environment: 11 | sdk: ">=2.7.0 <3.0.0" 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | 17 | 18 | # The following adds the Cupertino Icons font to your application. 19 | # Use with the CupertinoIcons class for iOS style icons. 20 | cupertino_icons: ^0.1.3 21 | theme_mode_handler: ^2.0.0 22 | cached_network_image: ^2.2.0+1 23 | material_design_icons_flutter: ^4.0.5345 24 | shared_preferences: ^0.5.7+1 25 | http: ^0.12.0+2 26 | provider: 27 | lottie: ^0.6.0 28 | flutter_svg: ^0.20.0-nullsafety.3 29 | # data 30 | retrofit: ^1.3.4+1 31 | logging: ^0.11.4 32 | dio: ^3.0.10 33 | json_annotation: ^3.0.1 34 | pretty_dio_logger: ^1.1.0 35 | # Service locator 36 | get_it: ^3.1.0 37 | # Functional programming in Dart 38 | dartz: ^0.9.2 39 | # for local database 40 | hive: 1.2.0 41 | hive_flutter: 0.3.1 42 | # For OS-specific directory paths 43 | path_provider: ^1.3.0 44 | #spinkit 45 | flutter_spinkit: ^4.1.2 46 | #date format 47 | intl: ^0.16.1 48 | #webview 49 | webview_flutter: ^0.3.22+1 50 | 51 | dev_dependencies: 52 | flutter_test: 53 | sdk: flutter 54 | 55 | build_runner: ^1.9.0 56 | retrofit_generator: ^1.4.0 57 | json_serializable: ^3.3.0 58 | hive_generator: ^0.5.1 59 | 60 | dependency_overrides: 61 | path: ^1.6.4 62 | 63 | # For information on the generic Dart part of this file, see the 64 | # following page: https://dart.dev/tools/pub/pubspec 65 | 66 | # The following section is specific to Flutter. 67 | flutter: 68 | 69 | # The following line ensures that the Material Icons font is 70 | # included with your application, so that you can use the icons in 71 | # the material Icons class. 72 | uses-material-design: true 73 | 74 | # To add assets to your application, add an assets section, like this: 75 | assets: 76 | - secrets.json 77 | # - images/a_dot_burr.jpeg 78 | # - images/a_dot_ham.jpeg 79 | 80 | # An image asset can refer to one or more resolution-specific "variants", see 81 | # https://flutter.dev/assets-and-images/#resolution-aware. 82 | 83 | # For details regarding adding assets from package dependencies, see 84 | # https://flutter.dev/assets-and-images/#from-packages 85 | 86 | # To add custom fonts to your application, add a fonts section here, 87 | # in this "flutter" section. Each entry in this list should have a 88 | # "family" key with the font family name, and a "fonts" key with a 89 | # list giving the asset and other descriptors for the font. For 90 | # example: 91 | # fonts: 92 | # - family: Schyler 93 | # fonts: 94 | # - asset: fonts/Schyler-Regular.ttf 95 | # - asset: fonts/Schyler-Italic.ttf 96 | # style: italic 97 | # - family: Trajan Pro 98 | # fonts: 99 | # - asset: fonts/TrajanPro.ttf 100 | # - asset: fonts/TrajanPro_Bold.ttf 101 | # weight: 700 102 | # 103 | # For details regarding fonts from package dependencies, 104 | # see https://flutter.dev/custom-fonts/#from-packages 105 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/screens/profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:football/config/palette.dart'; 5 | import 'package:football/data/data.dart'; 6 | import 'package:football/widgets/sliver_custom_appbar.dart'; 7 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 8 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 9 | 10 | class ProfileScreen extends StatelessWidget { 11 | final _user = user; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final textTheme = Theme.of(context).textTheme; 16 | final themeMode = ThemeModeHandler.of(context).themeMode; 17 | return Scaffold( 18 | body: CustomScrollView( 19 | slivers: [ 20 | SliverCustomAppBar( 21 | flexibleTitle: "Profile", 22 | trailing: IconButton( 23 | icon: Icon(Icons.track_changes), onPressed: () {})), 24 | SliverToBoxAdapter( 25 | child: Column( 26 | children: [ 27 | SizedBox(height: 50.0), 28 | Container( 29 | height: 120.0, 30 | width: 120.0, 31 | padding: EdgeInsets.all(20.0), 32 | decoration: BoxDecoration( 33 | color: themeMode == ThemeMode.dark 34 | ? Colors.white 35 | : Colors.black45, 36 | shape: BoxShape.circle), 37 | child: CircleAvatar( 38 | backgroundImage: CachedNetworkImageProvider(_user.avatar), 39 | ), 40 | ), 41 | SizedBox(height: 14.0), 42 | Text(_user.nickName, 43 | style: textTheme.headline6 44 | .copyWith(fontWeight: FontWeight.bold)), 45 | SizedBox(height: 4.0), 46 | Text(_user.name, 47 | style: textTheme.subtitle2 48 | .copyWith(fontWeight: FontWeight.w400)) 49 | ], 50 | ), 51 | ), 52 | SliverPadding( 53 | padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 2.0), 54 | sliver: SliverToBoxAdapter( 55 | child: Column( 56 | children: [ 57 | ListTile( 58 | leading: Icon(MdiIcons.moonWaningCrescent, size: 30.0), 59 | title: Text("Dark mode", 60 | style: TextStyle(fontWeight: FontWeight.w500)), 61 | trailing: Switch.adaptive( 62 | value: _isDarkMode(context), 63 | onChanged: (value) { 64 | _setTheme(value, context); 65 | }, 66 | activeColor: Colors.white, 67 | inactiveThumbColor: Colors.black, 68 | activeTrackColor: Colors.white, 69 | ), 70 | ) 71 | ], 72 | ), 73 | ), 74 | ) 75 | ], 76 | ), 77 | ); 78 | } 79 | 80 | bool _setTheme(bool isDarkMode, BuildContext context) { 81 | var theme; 82 | if (isDarkMode) { 83 | theme = ThemeMode.dark; 84 | } else { 85 | theme = ThemeMode.light; 86 | } 87 | ThemeModeHandler.of(context).saveThemeMode(theme); 88 | Palette.setUpStatusBarThemeMode(theme); 89 | return isDarkMode; 90 | } 91 | 92 | bool _isDarkMode(BuildContext context) { 93 | var theme = ThemeModeHandler.of(context).themeMode; 94 | if (theme == ThemeMode.dark) { 95 | return true; 96 | } else { 97 | return false; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/widgets/news_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:football/domain/domain.dart'; 4 | import 'package:football/model/news.dart'; 5 | 6 | class NewsItem extends StatelessWidget { 7 | final News news; 8 | final Function onTap; 9 | 10 | const NewsItem({Key key, @required this.news, this.onTap}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return InkWell( 15 | onTap: onTap, 16 | child: Container( 17 | color: Theme.of(context).cardColor, 18 | margin: EdgeInsets.symmetric(vertical: 15.0), 19 | child: Column( 20 | crossAxisAlignment: CrossAxisAlignment.start, 21 | children: [ 22 | Container( 23 | height: 200, 24 | width: double.infinity, 25 | decoration: BoxDecoration( 26 | color: Colors.yellow, 27 | image: DecorationImage( 28 | image: CachedNetworkImageProvider(news.urlToImage != null ? news.urlToImage : ""), 29 | fit: BoxFit.cover), 30 | ), 31 | child: Stack( 32 | children: [ 33 | Positioned( 34 | child: Container( 35 | decoration: BoxDecoration( 36 | gradient: LinearGradient( 37 | colors: [ 38 | Colors.black.withOpacity(0.6), 39 | Colors.transparent 40 | ], 41 | ), 42 | ), 43 | ), 44 | bottom: 0.0, 45 | top: 0.0, 46 | left: 0.0, 47 | right: 0.0, 48 | ), 49 | Positioned( 50 | bottom: 16.0, 51 | left: 16.0, 52 | right: 16.0, 53 | child: Column( 54 | children: [ 55 | Text(news.author ?? "", 56 | style: TextStyle( 57 | color: Colors.white, 58 | fontWeight: FontWeight.bold, 59 | fontSize: 18.0)), 60 | SizedBox(height: 16.0), 61 | Text( 62 | news.title, 63 | style: TextStyle( 64 | color: Colors.white, 65 | fontWeight: FontWeight.bold, 66 | fontSize: 18.0)), 67 | ], 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | ), 70 | ) 71 | ], 72 | ), 73 | ), 74 | Padding( 75 | padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 76 | child: news.description != null ? Text( 77 | news.description ?? "", 78 | style: TextStyle( 79 | color: Theme.of(context).highlightColor, 80 | fontWeight: FontWeight.bold, 81 | fontSize: 14.0), 82 | ) : SizedBox.shrink(), 83 | ), 84 | Padding( 85 | padding: EdgeInsets.symmetric( 86 | horizontal: 16.0, vertical: 14.0 87 | ), 88 | child: Text( 89 | Utils.toAppDate(news.publishAt), 90 | style: TextStyle( 91 | color: Theme.of(context).highlightColor, 92 | fontWeight: FontWeight.bold, 93 | fontSize: 14.0), 94 | ), 95 | ), 96 | ], 97 | ), 98 | ), 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/screens/home/league_table_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:football/model/league.dart'; 4 | import 'package:football/model/table_item.dart'; 5 | import 'package:football/viewmodel/football_provider.dart'; 6 | import 'package:football/widgets/widget.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class TableScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | final League league = ModalRoute.of(context).settings.arguments; 13 | final nameWidth = MediaQuery.of(context).size.width / 2.4; 14 | return Scaffold( 15 | body: CustomScrollView( 16 | slivers: [ 17 | SliverCustomAppBar( 18 | title: league.name, 19 | iconLeading: Icons.arrow_back_ios, 20 | onLeadingTap: () { 21 | Navigator.pop(context); 22 | }), 23 | Consumer( 24 | builder: (context, footballProvider, child ){ 25 | final table = footballProvider.table(league.id); 26 | return SliverList( 27 | delegate: SliverChildBuilderDelegate((context, index) { 28 | final tableItem = table[index]; 29 | return _tableItem(tableItem, nameWidth); 30 | }, childCount: table.length), 31 | ); 32 | }, 33 | ) 34 | ], 35 | ), 36 | ); 37 | } 38 | 39 | Widget _tableItem(TableItem tableItem, double nameWidth) { 40 | return Column( 41 | children: [ 42 | Row( 43 | children: [ 44 | Container( 45 | width: 40.0, 46 | padding: 47 | const EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0), 48 | child: Text( 49 | tableItem.position.toString(), 50 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 51 | ), 52 | ), 53 | Padding( 54 | padding: const EdgeInsets.symmetric(horizontal: 4.0), 55 | child: SvgPicture.network(tableItem.avatar, 56 | width: 16.0, height: 16.0)), 57 | Container( 58 | padding: const EdgeInsets.symmetric(horizontal: 12.0), 59 | width: nameWidth, 60 | child: Text( 61 | tableItem.name, 62 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 63 | ), 64 | ), 65 | Expanded(child: SizedBox()), 66 | Container( 67 | width: 50.0, 68 | padding: 69 | const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), 70 | child: Text( 71 | tableItem.gamePlayed.toString(), 72 | textAlign: TextAlign.end, 73 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 74 | ), 75 | ), 76 | Container( 77 | width: 60.0, 78 | padding: 79 | const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), 80 | child: Text( 81 | tableItem.goalDifference.toString(), 82 | textAlign: TextAlign.end, 83 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 84 | ), 85 | ), 86 | Container( 87 | width: 55.0, 88 | padding: 89 | const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), 90 | child: Text( 91 | tableItem.point.toString(), 92 | textAlign: TextAlign.end, 93 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 94 | ), 95 | ) 96 | ], 97 | ), 98 | Divider(thickness: 1.5, indent: 2, endIndent: 2) 99 | ], 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/screens/home/football_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/painting.dart'; 4 | import 'package:football/data/data.dart'; 5 | import 'package:football/screens/home/search_screen.dart'; 6 | import 'package:football/utils/custom_route.dart'; 7 | import 'package:football/viewmodel/provider.dart'; 8 | import 'package:football/widgets/team_item.dart'; 9 | import 'package:football/widgets/widget.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class FootballScreen extends StatelessWidget { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | body: Center( 17 | child: CustomScrollView( 18 | slivers: [ 19 | SliverCustomAppBar ( 20 | flexibleTitle: "Football", 21 | trailing: InkWell( 22 | onTap: () async { 23 | Provider.of(context, listen: false).fetchNews(); 24 | }, 25 | child: Container( 26 | margin: const EdgeInsets.all(9.0), 27 | padding: const EdgeInsets.only(right: 10), 28 | decoration: const BoxDecoration(shape: BoxShape.circle), 29 | child: CircleAvatar( 30 | radius: 20.0, 31 | backgroundImage: CachedNetworkImageProvider(user.avatar), 32 | )), 33 | ), 34 | ), 35 | SliverToBoxAdapter( 36 | child: Padding( 37 | padding: 38 | const EdgeInsets.symmetric(vertical: 15, horizontal: 20), 39 | child: CustomSearchBar( 40 | onTapSearch: () => Navigator.of(context).push(Util.slideUpRoute(SearchScreen())), 41 | ), 42 | ), 43 | ), 44 | const SliverToBoxAdapter( 45 | child: Padding( 46 | padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20), 47 | child: const ListHeader( 48 | headerTitle: "Football league", trailingTitle: "See All"), 49 | ), 50 | ), 51 | SliverList( 52 | delegate: SliverChildListDelegate(leagues 53 | .asMap() 54 | .map((i, league) => MapEntry( 55 | i, 56 | TeamItem( 57 | title: league.name, 58 | imageUrl: league.emblemUrl, 59 | id: league.id, 60 | subtitle: league.area, 61 | trailing: league.currentMatchday.toString(), 62 | onTap: () {Navigator.pushNamed(context, "/footballDetails", arguments: league);} 63 | ))).values.toList())), 64 | const SliverToBoxAdapter( 65 | child: Padding( 66 | padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20), 67 | child: const ListHeader( 68 | headerTitle: "Top Teams", trailingTitle: "See All"), 69 | ), 70 | ), 71 | Consumer( 72 | builder: (context, provider, child){ 73 | return SliverList( 74 | delegate: SliverChildListDelegate(provider.fiveTopTeams() 75 | .asMap() 76 | .map((i, team) => MapEntry( 77 | i, 78 | TeamItem( 79 | title: team.name, 80 | imageUrl: team.emblemUrl, 81 | id: team.id, 82 | subtitle: team.leauge, 83 | trailing: team.position.toString(), 84 | onTap: () {print("id is ${team.name}");} 85 | ))).values.toList())); 86 | }, 87 | ) 88 | ], 89 | ), 90 | ), 91 | ); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:football/config/config.dart'; 4 | import 'package:football/config/theme.dart'; 5 | import 'package:football/data/hive/hive.dart'; 6 | import 'package:football/model/league.dart'; 7 | import 'package:football/model/news.dart'; 8 | import 'package:football/screens/news/news_details.dart'; 9 | import 'package:football/utils/storage_util.dart'; 10 | import 'package:football/viewmodel/provider.dart'; 11 | import 'package:hive/hive.dart'; 12 | import 'package:logging/logging.dart'; 13 | import 'package:provider/provider.dart'; 14 | import 'screens/screen.dart'; 15 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 16 | import 'injection_container.dart' as di; 17 | import 'package:path_provider/path_provider.dart' as path_provider; 18 | 19 | void main() async { 20 | WidgetsFlutterBinding.ensureInitialized(); 21 | await di.init(); 22 | 23 | //hive 24 | final appDocumentDir = await path_provider.getApplicationDocumentsDirectory(); 25 | Hive.init(appDocumentDir.path); 26 | await HiveSetup.init(); 27 | 28 | // Set default home. 29 | Widget _defaultHome = new OnBoardingScreen(); 30 | final hasBeenBoarded = await StorageUtil.getBoolean(StorageUtil.HAS_BOARDED); 31 | if(hasBeenBoarded){_defaultHome = HomeScreen();} 32 | 33 | _setUpLogging(); 34 | 35 | runApp(MyApp(launcher: _defaultHome)); 36 | } 37 | 38 | void _setUpLogging(){ 39 | Logger.root.level = Level.ALL; 40 | Logger.root.onRecord.listen((rec) { 41 | print("${rec.level.name}: ${rec.time}"); 42 | }); 43 | } 44 | 45 | class MyApp extends StatelessWidget { 46 | final Widget launcher; 47 | 48 | const MyApp({Key key, this.launcher}) : super(key: key); 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return MultiProvider( 53 | providers: [ 54 | ChangeNotifierProvider(create: (context) => di.injector()), 55 | ChangeNotifierProvider(create: (context) => di.injector()) 56 | ], 57 | child: ThemeModeHandler( 58 | manager: AppTheme(), 59 | builder: (ThemeMode themeMode) { 60 | return MaterialApp( 61 | debugShowCheckedModeBanner: false, 62 | title: 'Football', 63 | darkTheme: ThemeData.dark().copyWith( 64 | scaffoldBackgroundColor: Palette.backgroundColorDark, 65 | appBarTheme: Styles.appBarTheme, 66 | accentColor: Colors.blueAccent, 67 | cardColor: Palette.darkGrey, 68 | hoverColor: Palette.darkGrey, 69 | highlightColor: Colors.white54, 70 | brightness: Brightness.dark 71 | ), 72 | theme: ThemeData.light().copyWith( 73 | primaryColor: Colors.blue, 74 | scaffoldBackgroundColor: Palette.backgroundColor, 75 | appBarTheme: Styles.appBarTheme, 76 | accentColor: Colors.blueAccent, 77 | cardColor: Colors.white, 78 | hoverColor: Palette.lightWhite, 79 | highlightColor: Colors.black54, 80 | visualDensity: VisualDensity.adaptivePlatformDensity, 81 | brightness: Brightness.light 82 | ), 83 | home: launcher, 84 | themeMode: themeMode, 85 | onGenerateRoute: (RouteSettings settings){ 86 | var routes = { 87 | '/footballDetails': (context) => FootballDetailsScreen(settings.arguments as League), 88 | '/newsDetails': (context) => NewsDetailsScreen(settings.arguments as News), 89 | //others 90 | }; 91 | WidgetBuilder builder = routes[settings.name]; 92 | return MaterialPageRoute(builder: (ctx) => builder(ctx)); 93 | }, 94 | routes: { 95 | '/home': (context) => HomeScreen(), 96 | '/search': (context) => SearchScreen(), 97 | '/table': (context) => TableScreen(), 98 | }, 99 | ); 100 | }, 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/widgets/live_match_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:football/config/palette.dart'; 4 | import 'package:football/widgets/round_image.dart'; 5 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 6 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 7 | import 'package:football/model/match.dart'; 8 | 9 | class LiveMatchItem extends StatelessWidget { 10 | 11 | final Match match; 12 | 13 | const LiveMatchItem({Key key, this.match}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final themeMode = ThemeModeHandler.of(context).themeMode; 18 | return Container( 19 | decoration: BoxDecoration( 20 | borderRadius: BorderRadius.circular(10.0), 21 | color: themeMode == ThemeMode.light ? Colors.white : Palette.darkGrey, 22 | boxShadow: [ 23 | BoxShadow( 24 | color: Colors.grey.withOpacity(0.5), 25 | spreadRadius: 5, 26 | blurRadius: 7, 27 | offset: Offset(0, 0.5) 28 | )// changes position of shadow 29 | ] 30 | ), 31 | width: 250.0, 32 | child: Column( 33 | crossAxisAlignment: CrossAxisAlignment.start, 34 | children: [ 35 | Container( 36 | padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), 37 | child: Row( 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: [ 40 | RoundImage(imageUrl: match.homeImageUrl, isElevated: true), 41 | RoundImage(imageUrl: match.awayImageUrl), 42 | const Expanded(child: SizedBox.shrink()), 43 | Column( 44 | children: [ 45 | Row( 46 | children: [ 47 | const Icon(Icons.arrow_drop_down_circle, size: 12.0,color: Colors.red), 48 | SizedBox(width: 4), 49 | const Text("Live", style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)) 50 | ], 51 | ), 52 | Text(match.currentMinute + "'", 53 | style: TextStyle( 54 | fontSize: 25.0, 55 | fontWeight: FontWeight.bold, 56 | color: Colors.red)) 57 | ], 58 | ) 59 | ], 60 | ), 61 | ), 62 | Padding( 63 | padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0 ), 64 | child: Text(match.league, style: TextStyle(color: Colors.grey,fontSize: 12.0, fontWeight: FontWeight.bold),), 65 | ), 66 | _matchScore(match) 67 | ], 68 | ), 69 | ); 70 | } 71 | 72 | Widget _matchScore(Match match){ 73 | var putPointerHome = false; 74 | var putPointerAway = false; 75 | 76 | if(match.homeScore == match.awayScore){ 77 | putPointerHome = false; 78 | putPointerAway = false; 79 | } 80 | 81 | if(match.homeScore > match.awayScore){ 82 | putPointerHome = true; 83 | putPointerAway = false; 84 | } 85 | if(match.homeScore < match.awayScore){ 86 | putPointerHome = false; 87 | putPointerAway = true; 88 | } 89 | 90 | return Column( 91 | children: [ 92 | Row( 93 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 94 | children: [ 95 | Expanded(child: Padding( 96 | padding: const EdgeInsets.only(left: 10,top: 5, bottom: 5.0, right: 20), 97 | child: Text(match.homeTeam, style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis), 98 | )), 99 | Container( 100 | width: 35.0, 101 | child: Row( 102 | children: [ 103 | Text(match.homeScore.toString(), style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold)), 104 | putPointerHome ? Icon(MdiIcons.menuLeft) : SizedBox.shrink(), 105 | ], 106 | ), 107 | ), 108 | ], 109 | ), 110 | Row( 111 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 112 | children: [ 113 | Expanded(child: Padding( 114 | padding: const EdgeInsets.only(left: 10, right: 20), 115 | child: Text(match.awayTeam, maxLines: 2, style: TextStyle(fontWeight: FontWeight.w500, color: Colors.grey)), 116 | )), 117 | Container( 118 | width: 35.0, 119 | child: Row( 120 | children: [ 121 | Text(match.awayScore.toString(), style: TextStyle( fontSize: 14.0, color: Colors.grey, fontWeight: FontWeight.w500)), 122 | putPointerAway ? Icon(MdiIcons.menuLeft) : SizedBox.shrink(), 123 | ], 124 | ), 125 | ), 126 | ], 127 | ), 128 | ], 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/widgets/match_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:football/config/palette.dart'; 5 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 6 | import 'package:theme_mode_handler/theme_mode_handler.dart'; 7 | import 'package:football/model/match.dart'; 8 | 9 | class MatchItem extends StatelessWidget { 10 | 11 | final Match match; 12 | 13 | const MatchItem({Key key, this.match}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | decoration: BoxDecoration( 19 | borderRadius: BorderRadius.circular(10.0), 20 | color: Theme.of(context).cardColor, 21 | boxShadow: [ 22 | BoxShadow( 23 | color: Colors.grey.withOpacity(0.5), 24 | spreadRadius: 5, 25 | blurRadius: 7, 26 | offset: Offset(0, 0.5)) // changes position of shadow 27 | ]), 28 | height: 100.0, 29 | width: 200.0, 30 | padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 6), 31 | child: Row( 32 | mainAxisAlignment: MainAxisAlignment.end, 33 | children: [ 34 | _matchScore(match), 35 | // Expanded(child: SizedBox()), 36 | Container( 37 | margin: EdgeInsets.only(right: 12.0), 38 | child: Row( 39 | children: [ 40 | VerticalDivider(thickness: 1.0), 41 | Column( 42 | mainAxisAlignment: MainAxisAlignment.center, 43 | children: [ 44 | Text(match.playTime, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0)), 45 | SizedBox(height: 12.0), 46 | Text(match.playDate, style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 14.0)) 47 | ], 48 | ), 49 | ], 50 | ), 51 | ) 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _matchScore(Match match) { 58 | var putPointerHome = false; 59 | var putPointerAway = false; 60 | 61 | if(match.homeScore != null && match.awayScore != null){ 62 | 63 | if (match.homeScore > match.awayScore) { 64 | putPointerHome = true; 65 | putPointerAway = false; 66 | } 67 | if (match.homeScore < match.awayScore) { 68 | putPointerHome = false; 69 | putPointerAway = true; 70 | } 71 | } 72 | 73 | return Expanded( 74 | child: Container( 75 | child: Column( 76 | mainAxisAlignment: MainAxisAlignment.center, 77 | children: [ 78 | Row( 79 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 80 | children: [ 81 | Expanded( 82 | child: Row( 83 | children: [ 84 | SvgPicture.network(match.homeImageUrl, width: 16.0, height: 16.0), 85 | SizedBox(width: 8.0), 86 | Expanded(child: Text(match.homeTeam, style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis)), 87 | ], 88 | ), 89 | ), 90 | SizedBox( 91 | width: 40.0, 92 | child: Row( 93 | mainAxisAlignment: MainAxisAlignment.start, 94 | children: [ 95 | match.homeScore != null ? Text(match.homeScore.toString(), style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis) : SizedBox.shrink(), 96 | putPointerHome ? Icon(MdiIcons.menuLeft) : SizedBox.shrink() 97 | ], 98 | ), 99 | ), 100 | ], 101 | ), 102 | SizedBox(height: 8.0), 103 | Row( 104 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 105 | children: [ 106 | Expanded( 107 | child: Row( 108 | children: [ 109 | SvgPicture.network(match.awayImageUrl, width: 16.0, height: 16.0), 110 | SizedBox(width: 8.0), 111 | Expanded(child: Text(match.awayTeam, style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis)), 112 | ], 113 | ), 114 | ), 115 | SizedBox( 116 | width: 40.0, 117 | child: Row( 118 | mainAxisAlignment: MainAxisAlignment.start, 119 | children: [ 120 | match.awayScore != null ? Text(match.awayScore.toString(), style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis) : SizedBox.shrink(), 121 | putPointerAway ? Icon(MdiIcons.menuLeft) : SizedBox.shrink() 122 | ], 123 | ), 124 | ), 125 | ], 126 | ), 127 | ], 128 | ), 129 | ), 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/data/model/games_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/data/model/table_response.dart'; 2 | 3 | class GamesResponse { 4 | Competition competition; 5 | List matches; 6 | 7 | GamesResponse({this.competition, this.matches}); 8 | 9 | GamesResponse.fromJson(Map json) { 10 | competition = json['competition'] != null 11 | ? new Competition.fromJson(json['competition']) 12 | : null; 13 | if (json['matches'] != null) { 14 | matches = new List(); 15 | json['matches'].forEach((v) { 16 | matches.add(new Matches.fromJson(v)); 17 | }); 18 | } 19 | } 20 | 21 | Map toJson() { 22 | final Map data = new Map(); 23 | if (this.competition != null) { 24 | data['competition'] = this.competition.toJson(); 25 | } 26 | if (this.matches != null) { 27 | data['matches'] = this.matches.map((v) => v.toJson()).toList(); 28 | } 29 | return data; 30 | } 31 | } 32 | 33 | 34 | class Matches { 35 | int id; 36 | String utcDate; 37 | String status; 38 | int matchday; 39 | String lastUpdated; 40 | Score score; 41 | HomeTeam homeTeam; 42 | HomeTeam awayTeam; 43 | 44 | Matches( 45 | {this.id, 46 | this.utcDate, 47 | this.status, 48 | this.matchday, 49 | this.lastUpdated, 50 | this.score, 51 | this.homeTeam, 52 | this.awayTeam}); 53 | 54 | Matches.fromJson(Map json) { 55 | id = json['id']; 56 | utcDate = json['utcDate']; 57 | status = json['status']; 58 | matchday = json['matchday']; 59 | lastUpdated = json['lastUpdated']; 60 | score = json['score'] != null ? new Score.fromJson(json['score']) : null; 61 | homeTeam = json['homeTeam'] != null 62 | ? new HomeTeam.fromJson(json['homeTeam']) 63 | : null; 64 | awayTeam = json['awayTeam'] != null 65 | ? new HomeTeam.fromJson(json['awayTeam']) 66 | : null; 67 | } 68 | 69 | Map toJson() { 70 | final Map data = new Map(); 71 | data['id'] = this.id; 72 | data['utcDate'] = this.utcDate; 73 | data['status'] = this.status; 74 | data['matchday'] = this.matchday; 75 | data['lastUpdated'] = this.lastUpdated; 76 | if (this.score != null) { 77 | data['score'] = this.score.toJson(); 78 | } 79 | if (this.homeTeam != null) { 80 | data['homeTeam'] = this.homeTeam.toJson(); 81 | } 82 | if (this.awayTeam != null) { 83 | data['awayTeam'] = this.awayTeam.toJson(); 84 | } 85 | return data; 86 | } 87 | } 88 | 89 | class Score { 90 | String winner; 91 | String duration; 92 | FullTime fullTime; 93 | FullTime halfTime; 94 | ExtraTime extraTime; 95 | ExtraTime penalties; 96 | 97 | Score( 98 | {this.winner, 99 | this.duration, 100 | this.fullTime, 101 | this.halfTime, 102 | this.extraTime, 103 | this.penalties}); 104 | 105 | Score.fromJson(Map json) { 106 | winner = json['winner']; 107 | duration = json['duration']; 108 | fullTime = json['fullTime'] != null 109 | ? new FullTime.fromJson(json['fullTime']) 110 | : null; 111 | halfTime = json['halfTime'] != null 112 | ? new FullTime.fromJson(json['halfTime']) 113 | : null; 114 | extraTime = json['extraTime'] != null 115 | ? new ExtraTime.fromJson(json['extraTime']) 116 | : null; 117 | penalties = json['penalties'] != null 118 | ? new ExtraTime.fromJson(json['penalties']) 119 | : null; 120 | } 121 | 122 | Map toJson() { 123 | final Map data = new Map(); 124 | data['winner'] = this.winner; 125 | data['duration'] = this.duration; 126 | if (this.fullTime != null) { 127 | data['fullTime'] = this.fullTime.toJson(); 128 | } 129 | if (this.halfTime != null) { 130 | data['halfTime'] = this.halfTime.toJson(); 131 | } 132 | if (this.extraTime != null) { 133 | data['extraTime'] = this.extraTime.toJson(); 134 | } 135 | if (this.penalties != null) { 136 | data['penalties'] = this.penalties.toJson(); 137 | } 138 | return data; 139 | } 140 | } 141 | 142 | class FullTime { 143 | int homeTeam; 144 | int awayTeam; 145 | 146 | FullTime({this.homeTeam, this.awayTeam}); 147 | 148 | FullTime.fromJson(Map json) { 149 | homeTeam = json['homeTeam']; 150 | awayTeam = json['awayTeam']; 151 | } 152 | 153 | Map toJson() { 154 | final Map data = new Map(); 155 | data['homeTeam'] = this.homeTeam; 156 | data['awayTeam'] = this.awayTeam; 157 | return data; 158 | } 159 | } 160 | 161 | class ExtraTime { 162 | int homeTeam; 163 | int awayTeam; 164 | 165 | ExtraTime({this.homeTeam, this.awayTeam}); 166 | 167 | ExtraTime.fromJson(Map json) { 168 | homeTeam = json['homeTeam']; 169 | awayTeam = json['awayTeam']; 170 | } 171 | 172 | Map toJson() { 173 | final Map data = new Map(); 174 | data['homeTeam'] = this.homeTeam; 175 | data['awayTeam'] = this.awayTeam; 176 | return data; 177 | } 178 | } 179 | 180 | class HomeTeam { 181 | int id; 182 | String name; 183 | 184 | HomeTeam({this.id, this.name}); 185 | 186 | HomeTeam.fromJson(Map json) { 187 | id = json['id']; 188 | name = json['name']; 189 | } 190 | 191 | Map toJson() { 192 | final Map data = new Map(); 193 | data['id'] = this.id; 194 | data['name'] = this.name; 195 | return data; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/screens/onboarding.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:football/model/onboarding.dart'; 3 | import 'package:football/utils/storage_util.dart'; 4 | import 'package:lottie/lottie.dart'; 5 | 6 | class OnBoardingScreen extends StatelessWidget { 7 | final listOnBoarding = [ 8 | OnBoarding( 9 | "https://assets7.lottiefiles.com/packages/lf20_e4mqXr/ball_04.json", 10 | "League", 11 | "Everything from quoting to voice mail, " 12 | "to anything soccer goes into here and not is the time to do that" 13 | "to anything soccer goes into here and not is the time to do that"), 14 | OnBoarding( 15 | "https://assets7.lottiefiles.com/packages/lf20_bD8Yze.json", 16 | "Team", 17 | "Everything from quoting to voice mail, " 18 | "to anything soccer goes into here and not is the time to do that" 19 | "to anything soccer goes into here and not is the time to do that"), 20 | OnBoarding( 21 | "https://assets4.lottiefiles.com/datafiles/KbKSrbWahtNPvY6/data.json", 22 | "News", 23 | "Everything from quoting to voice mail, " 24 | "to anything soccer goes into here and not is the time to do that" 25 | "to anything soccer goes into here and not is the time to do that") 26 | ]; 27 | 28 | final pageIndexNotifier = ValueNotifier(0); 29 | final PageController _pageController = PageController(); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | body: Stack( 35 | children: [ 36 | PageView.builder( 37 | onPageChanged: (index) => pageIndexNotifier.value = index, 38 | controller: _pageController, 39 | scrollDirection: Axis.horizontal, 40 | itemCount: listOnBoarding.length, 41 | itemBuilder: (BuildContext context, int index) => OnBoardingItem( 42 | onBoarding: listOnBoarding[index], index: index)), 43 | Positioned( 44 | bottom: 20.0, 45 | left: 10.0, 46 | right: 10.0, 47 | child: 48 | ValueListenableBuilder( 49 | valueListenable: pageIndexNotifier, 50 | builder: (context, index, child){ 51 | return Row( 52 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 53 | children: [ 54 | FlatButton( 55 | onPressed: () {}, 56 | child: const Text( 57 | "Skip", 58 | style: TextStyle(color: Colors.grey), 59 | )), 60 | Row( 61 | children: [ 62 | Icon(Icons.arrow_drop_down_circle, 63 | size: 6.0, 64 | color: index == 0 ? Colors.blue : Colors.grey), 65 | Icon(Icons.arrow_drop_down_circle, 66 | size: 6.0, 67 | color: index == 1 ? Colors.blue : Colors.grey), 68 | Icon(Icons.arrow_drop_down_circle, 69 | size: 6.0, 70 | color: index == 2 ? Colors.blue : Colors.grey) 71 | ], 72 | ), 73 | FlatButton( 74 | onPressed: () { 75 | if(index != 2){ 76 | _pageController.nextPage(duration: const Duration(milliseconds: 400), curve: Curves.easeInOut); 77 | }else { 78 | navigateToHome(context); 79 | } 80 | }, 81 | child: Text(index == 2 ? "Get started" : "Next", 82 | style: TextStyle(color: Colors.blue), 83 | )) 84 | ], 85 | ); 86 | } 87 | )) 88 | ], 89 | ), 90 | ); 91 | } 92 | 93 | void navigateToHome(BuildContext context) async { 94 | await StorageUtil.setBool(StorageUtil.HAS_BOARDED, true); 95 | Navigator.of(context).pushReplacementNamed("/home"); 96 | } 97 | } 98 | 99 | class OnBoardingItem extends StatelessWidget { 100 | final OnBoarding onBoarding; 101 | final int index; 102 | 103 | const OnBoardingItem({Key key, this.onBoarding, this.index}) 104 | : super(key: key); 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | final height = MediaQuery.of(context).size.height; 109 | return Scaffold( 110 | body: Container( 111 | child: Column( 112 | mainAxisAlignment: MainAxisAlignment.center, 113 | children: [ 114 | Padding( 115 | padding: const EdgeInsets.all(10.0), 116 | child: LottieBuilder.network( 117 | onBoarding.imageAsset, 118 | repeat: false, 119 | height: height * 0.55, 120 | ), 121 | ), 122 | Text(onBoarding.headerTitle, 123 | style: const TextStyle( 124 | fontSize: 20.0, fontWeight: FontWeight.bold)), 125 | SizedBox(height: height * 0.03), 126 | Expanded( 127 | child: Padding( 128 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 129 | child: Text( 130 | onBoarding.subHeaderTitle, 131 | textAlign: TextAlign.center, 132 | overflow: TextOverflow.fade, 133 | style: TextStyle(fontWeight: FontWeight.w600), 134 | ), 135 | )), 136 | ], 137 | ), 138 | ), 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/data/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:football/model/league.dart'; 2 | import 'package:football/model/match.dart'; 3 | import 'package:football/model/news.dart'; 4 | import 'package:football/model/table_item.dart'; 5 | import 'package:football/model/team.dart'; 6 | import 'package:football/model/user.dart'; 7 | 8 | export 'package:football/data/model/news_response.dart'; 9 | export 'package:football/data/model/table_response.dart'; 10 | export 'package:football/data/datasource/footbal/news_remote_datasource.dart'; 11 | export 'package:football/data/datasource/footbal/football_remote_datasource.dart'; 12 | export 'package:football/data/model/articles_remote.dart'; 13 | export 'package:football/data/model/table_response.dart'; 14 | 15 | final User user = User(name: "Femi", nickName: "Efhem", avatar: "https://avatars0.githubusercontent.com/u/40208739?s=400&u=8b3a525a482244fccf8dca5ab04c77d482ca8aae&v=4"); 16 | 17 | final leagues = [ 18 | League(id: 2001, area: "Europe", name: "UEFA Champions League", emblemUrl: "https://upload.wikimedia.org/wikipedia/en/b/bf/UEFA_Champions_League_logo_2.svg", currentMatchday: 1), 19 | League(id: 2021, area: "England", name: "Premier League", emblemUrl: "https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg", currentMatchday: 5), 20 | League(id: 2019, area: "Italy", name: "Serie A", emblemUrl: "https://crests.football-data.org/SA.svg", currentMatchday: 4), 21 | League(id: 2014, area: "Spain", name: "La Liga", emblemUrl: "https://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg", currentMatchday: 7), 22 | League(id: 2015, area: "France", name: "Ligue 1", emblemUrl: "https://upload.wikimedia.org/wikipedia/en/c/c3/Flag_of_France.svg", currentMatchday: 8), 23 | League(id: 2002, area: "Germany", name: "Bundesliga", emblemUrl: "https://upload.wikimedia.org/wikipedia/commons/b/ba/Flag_of_Germany.svg", currentMatchday: 5), 24 | ]; 25 | 26 | // top teams can be filtered/deduced from each league by their position(first and/or second postion) on their league 27 | //or top teams could be as a result of their point divided by number of games played (kind of) 28 | final topTeams = [ 29 | Team(id: 258, leauge: "England", name: "Chelsea", emblemUrl: "https://crests.football-data.org/61.svg", position: 1), 30 | Team(id: 259, leauge: "England", name: "Man City", emblemUrl: "https://crests.football-data.org/65.svg", position: 2), 31 | Team(id: 259, leauge: "France", name: "Paris Saint-Germain", emblemUrl: "https://crests.football-data.org/524.svg", position: 1), 32 | Team(id: 200, leauge: "La Liga", name: "Real Madrid", emblemUrl: "https://crests.football-data.org/86.svg", position: 1), 33 | ]; 34 | 35 | final liveMatch = [ 36 | Match(id: 1, homeImageUrl: "https://crests.football-data.org/61.svg", 37 | awayImageUrl: "https://crests.football-data.org/65.svg", 38 | currentMinute: "15", isLive: true, 39 | league: "UEFA Champions League", homeTeam: "Chelsea", awayTeam: "Man City", homeScore: 2, awayScore: 3, playTime: "13:30", playDate: "18 JAN" ), 40 | Match(id: 2, homeImageUrl: "https://crests.football-data.org/524.svg", 41 | awayImageUrl: "https://crests.football-data.org/86.svg", 42 | currentMinute: "78", isLive: true, 43 | league: "UEFA Champions League", homeTeam: "Paris Saint-Germain", 44 | awayTeam: "Real Madrid", homeScore: 2, awayScore: 3, playTime: "13:30", playDate: "18 JAN" ), 45 | Match(id: 3, homeImageUrl: "https://crests.football-data.org/524.svg", 46 | awayImageUrl: "https://crests.football-data.org/86.svg", 47 | currentMinute: "110", isLive: true, 48 | league: "UEFA Champions League", homeTeam: "Paris Saint-Germain", 49 | awayTeam: "Real Madrid", homeScore: 2, awayScore: 3, playTime: "13:30", playDate: "18 JAN" ), 50 | ]; 51 | 52 | final scheduledMatch = [ 53 | Match(id: 1, homeImageUrl: "https://crests.football-data.org/61.svg", 54 | awayImageUrl: "https://crests.football-data.org/524.svg", 55 | currentMinute: null, isLive: false, 56 | league: "UEFA Champions League", homeTeam: "Chelsea", awayTeam: "Paris Saint-Germain", homeScore: 5, awayScore: 5, playTime: "20:30", playDate: "20 JAN" ), 57 | Match(id: 2, homeImageUrl: "https://crests.football-data.org/65.svg", 58 | awayImageUrl: "https://crests.football-data.org/86.svg", 59 | currentMinute: null, isLive: false, 60 | league: "UEFA Champions League", homeTeam: "Man City", 61 | awayTeam: "Real Madrid", homeScore: null, awayScore: null, playTime: "13:30", playDate: "18 JAN" ) 62 | ]; 63 | 64 | final table = [ 65 | TableItem(id: 1, avatar: "https:// crests.football-data.org/61.svg", name: "Chelsea", point: 97, gamePlayed: 36, goalDifference: 78), 66 | TableItem(id: 2, avatar: "https://crests.football-data.org/86.svg", name: "Real Madrid", point: 97, gamePlayed: 36, goalDifference: 78), 67 | TableItem(id: 3, avatar: "https://crests.football-data.org/65.svg", name: "Manchester city", point: 90, gamePlayed: 36, goalDifference: 70), 68 | TableItem(id: 4, avatar: "https://crests.football-data.org/61.svg", name: "Chelsea", point: 97, gamePlayed: 36, goalDifference: 78) 69 | ]; 70 | 71 | final newsList = [ 72 | 73 | News(1, "Cointelegraph By Guest Authors", "The evolution of crypto exchanges — What’s next for the industry", 74 | "Cryptocurrency exchanges have now become significant for global businesses, and digital asset trading hubs are carrying on substantial business activities on a daily basis", 75 | "https://cointelegraph.com/news/the-evolution-of-crypto-exchanges-what-s-next-for-the-industry", 76 | "https://s3.cointelegraph.com/uploads/2020-10/4eb37e5b-d6fe-49f9-bafd-4d06a58cc980.jpg", "2 hours ago"), 77 | News(2, "The Investor", "Weekend reading: Triage", 78 | "New experimental vaccine against Virus-related comments on Monevator, plus the week's good reads…", 79 | "https://monevator.com/weekend-reading-triage/", 80 | null, "9 hours ago"), 81 | News(3, "Cointelegraph By Guest Authors", "The evolution of crypto exchanges — What’s next for the industry", 82 | "Cryptocurrency exchanges have now become significant for global businesses, and digital asset trading hubs are carrying on substantial business activities on a daily basis", 83 | "https://cointelegraph.com/news/the-evolution-of-crypto-exchanges-what-s-next-for-the-industry", 84 | "https://s3.cointelegraph.com/uploads/2020-10/4eb37e5b-d6fe-49f9-bafd-4d06a58cc980.jpg", "2 hours ago"), 85 | ]; 86 | -------------------------------------------------------------------------------- /lib/data/model/table_response.dart: -------------------------------------------------------------------------------- 1 | /// competition : {"id":2021,"name":"Premier League","code":"PL"} 2 | /// standings : [{"stage":"REGULAR_SEASON","type":"TOTAL","group":null,"table":[{"position":1,"team":{"id":338,... 3 | 4 | class TableResponse { 5 | Competition _competition; 6 | List _standings; 7 | 8 | Competition get competition => _competition; 9 | List get standings => _standings; 10 | 11 | TableResponse({ 12 | Competition competition, 13 | List standings}){ 14 | _competition = competition; 15 | _standings = standings; 16 | } 17 | 18 | TableResponse.fromJson(dynamic json) { 19 | _competition = json["competition"] != null ? Competition.fromJson(json["competition"]) : null; 20 | if (json["standings"] != null) { 21 | _standings = []; 22 | json["standings"].forEach((v) { 23 | _standings.add(Standings.fromJson(v)); 24 | }); 25 | } 26 | } 27 | 28 | Map toJson() { 29 | var map = {}; 30 | if (_competition != null) { 31 | map["competition"] = _competition.toJson(); 32 | } 33 | if (_standings != null) { 34 | map["standings"] = _standings.map((v) => v.toJson()).toList(); 35 | } 36 | return map; 37 | } 38 | 39 | } 40 | 41 | /// stage : "REGULAR_SEASON" 42 | /// type : "TOTAL" 43 | /// group : null 44 | /// table : [{"position":1,"team":{"id":338,"name":"Leicester City FC"... 45 | 46 | class Standings { 47 | String _stage; 48 | String _type; 49 | dynamic _group; 50 | List _table; 51 | 52 | String get stage => _stage; 53 | String get type => _type; 54 | dynamic get group => _group; 55 | List
get table => _table; 56 | 57 | Standings({ 58 | String stage, 59 | String type, 60 | dynamic group, 61 | List
table}){ 62 | _stage = stage; 63 | _type = type; 64 | _group = group; 65 | _table = table; 66 | } 67 | 68 | Standings.fromJson(dynamic json) { 69 | _stage = json["stage"]; 70 | _type = json["type"]; 71 | _group = json["group"]; 72 | if (json["table"] != null) { 73 | _table = []; 74 | json["table"].forEach((v) { 75 | _table.add(Table.fromJson(v)); 76 | }); 77 | } 78 | } 79 | 80 | Map toJson() { 81 | var map = {}; 82 | map["stage"] = _stage; 83 | map["type"] = _type; 84 | map["group"] = _group; 85 | if (_table != null) { 86 | map["table"] = _table.map((v) => v.toJson()).toList(); 87 | } 88 | return map; 89 | } 90 | 91 | } 92 | 93 | /// position : 1 94 | /// team : {"id":338,"name":"Leicester City FC","crestUrl":"https://crests.football-data.org/338.svg"} 95 | /// playedGames : 8 96 | /// form : "W,W,W,L,L" 97 | /// won : 6 98 | /// draw : 0 99 | /// lost : 2 100 | /// points : 18 101 | /// goalsFor : 18 102 | /// goalsAgainst : 9 103 | /// goalDifference : 9 104 | 105 | class Table { 106 | int _position; 107 | TeamRemote _team; 108 | int _playedGames; 109 | String _form; 110 | int _won; 111 | int _draw; 112 | int _lost; 113 | int _points; 114 | int _goalsFor; 115 | int _goalsAgainst; 116 | int _goalDifference; 117 | 118 | int get position => _position; 119 | TeamRemote get team => _team; 120 | int get playedGames => _playedGames; 121 | String get form => _form; 122 | int get won => _won; 123 | int get draw => _draw; 124 | int get lost => _lost; 125 | int get points => _points; 126 | int get goalsFor => _goalsFor; 127 | int get goalsAgainst => _goalsAgainst; 128 | int get goalDifference => _goalDifference; 129 | 130 | Table({ 131 | int position, 132 | TeamRemote team, 133 | int playedGames, 134 | String form, 135 | int won, 136 | int draw, 137 | int lost, 138 | int points, 139 | int goalsFor, 140 | int goalsAgainst, 141 | int goalDifference}){ 142 | _position = position; 143 | _team = team; 144 | _playedGames = playedGames; 145 | _form = form; 146 | _won = won; 147 | _draw = draw; 148 | _lost = lost; 149 | _points = points; 150 | _goalsFor = goalsFor; 151 | _goalsAgainst = goalsAgainst; 152 | _goalDifference = goalDifference; 153 | } 154 | 155 | Table.fromJson(dynamic json) { 156 | _position = json["position"]; 157 | _team = json["team"] != null ? TeamRemote.fromJson(json["team"]) : null; 158 | _playedGames = json["playedGames"]; 159 | _form = json["form"]; 160 | _won = json["won"]; 161 | _draw = json["draw"]; 162 | _lost = json["lost"]; 163 | _points = json["points"]; 164 | _goalsFor = json["goalsFor"]; 165 | _goalsAgainst = json["goalsAgainst"]; 166 | _goalDifference = json["goalDifference"]; 167 | } 168 | 169 | Map toJson() { 170 | var map = {}; 171 | map["position"] = _position; 172 | if (_team != null) { 173 | map["team"] = _team.toJson(); 174 | } 175 | map["playedGames"] = _playedGames; 176 | map["form"] = _form; 177 | map["won"] = _won; 178 | map["draw"] = _draw; 179 | map["lost"] = _lost; 180 | map["points"] = _points; 181 | map["goalsFor"] = _goalsFor; 182 | map["goalsAgainst"] = _goalsAgainst; 183 | map["goalDifference"] = _goalDifference; 184 | return map; 185 | } 186 | 187 | } 188 | 189 | /// id : 338 190 | /// name : "Leicester City FC" 191 | /// crestUrl : "https://crests.football-data.org/338.svg" 192 | 193 | class TeamRemote { 194 | int _id; 195 | String _name; 196 | String _crestUrl; 197 | 198 | int get id => _id; 199 | String get name => _name; 200 | String get crestUrl => _crestUrl; 201 | 202 | TeamRemote({ 203 | int id, 204 | String name, 205 | String crestUrl}){ 206 | _id = id; 207 | _name = name; 208 | _crestUrl = crestUrl; 209 | } 210 | 211 | TeamRemote.fromJson(dynamic json) { 212 | _id = json["id"]; 213 | _name = json["name"]; 214 | _crestUrl = json["crestUrl"]; 215 | } 216 | 217 | Map toJson() { 218 | var map = {}; 219 | map["id"] = _id; 220 | map["name"] = _name; 221 | map["crestUrl"] = _crestUrl; 222 | return map; 223 | } 224 | 225 | } 226 | 227 | /// id : 2021 228 | /// name : "Premier League" 229 | /// code : "PL" 230 | 231 | class Competition { 232 | int _id; 233 | String _name; 234 | String _code; 235 | 236 | int get id => _id; 237 | String get name => _name; 238 | String get code => _code; 239 | 240 | Competition({ 241 | int id, 242 | String name, 243 | String code}){ 244 | _id = id; 245 | _name = name; 246 | _code = code; 247 | } 248 | 249 | Competition.fromJson(dynamic json) { 250 | _id = json["id"]; 251 | _name = json["name"]; 252 | _code = json["code"]; 253 | } 254 | 255 | Map toJson() { 256 | var map = {}; 257 | map["id"] = _id; 258 | map["name"] = _name; 259 | map["code"] = _code; 260 | return map; 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /lib/screens/home/football_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:football/model/league.dart'; 4 | import 'package:football/model/team.dart'; 5 | import 'package:football/utils/custom_route.dart'; 6 | import 'package:football/viewmodel/provider.dart'; 7 | import 'package:football/widgets/widget.dart'; 8 | import 'package:provider/provider.dart'; 9 | import '../screen.dart'; 10 | 11 | class FootballDetailsScreen extends StatefulWidget { 12 | final League league; 13 | 14 | FootballDetailsScreen(this.league); 15 | 16 | @override 17 | _FootballDetailsScreenState createState() => _FootballDetailsScreenState(); 18 | } 19 | 20 | class _FootballDetailsScreenState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | Future.delayed(Duration.zero, () { 25 | Provider.of(context, listen: false) 26 | .fetchTable(widget.league.id); 27 | Provider.of(context, listen: false) 28 | .fetchGames(widget.league.id); 29 | }); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Stack( 35 | children: [ 36 | Scaffold( 37 | body: Consumer( 38 | builder: (buildContext, provider, child) { 39 | final teams = provider.teams(widget.league.id); 40 | if (provider.failure != null) { 41 | Util.showSnackBar( 42 | context, provider.failure.message, () { 43 | provider.fetchTable(widget.league.id); 44 | provider.fetchGames(widget.league.id); 45 | }); 46 | } 47 | return CustomScrollView( 48 | slivers: [ 49 | SliverCustomAppBar( 50 | title: widget.league.name, 51 | iconLeading: Icons.arrow_back_ios, 52 | onLeadingTap: () { 53 | Navigator.pop(context); 54 | }), 55 | SliverToBoxAdapter( 56 | child: Padding( 57 | padding: const EdgeInsets.symmetric( 58 | vertical: 15, horizontal: 20), 59 | child: CustomSearchBar( 60 | onTapSearch: () => Navigator.of(context) 61 | .push(Util.slideUpRoute(SearchScreen())), 62 | ), 63 | ), 64 | ), 65 | SliverToBoxAdapter( 66 | child: Container( 67 | height: 90.0, 68 | child: _teamsWidget(teams), 69 | ) 70 | ), 71 | //LIVE HIGHLIGHTS TEXT 72 | SliverToBoxAdapter( 73 | child: provider.liveMatch.isEmpty ? SizedBox.shrink() : 74 | Padding( 75 | padding: const EdgeInsets.symmetric( 76 | vertical: 20, horizontal: 20.0), 77 | child: Row( 78 | children: [ 79 | Container( 80 | padding: const EdgeInsets.symmetric( 81 | vertical: 2.0, horizontal: 4.0), 82 | decoration: BoxDecoration( 83 | color: Colors.red, 84 | borderRadius: BorderRadius.circular(3.0)), 85 | child: const Text( 86 | "Live", 87 | style: TextStyle( 88 | fontWeight: FontWeight.w500, 89 | color: Colors.white), 90 | )), 91 | const SizedBox(width: 6.0), 92 | const ListHeader(headerTitle: "Live Highlights") 93 | ], 94 | ), 95 | ), 96 | ), 97 | SliverToBoxAdapter( 98 | child: provider.liveMatch.isEmpty 99 | ? SizedBox.shrink() 100 | : Container( 101 | height: 250, 102 | child: ListView.builder( 103 | physics: ClampingScrollPhysics(), 104 | padding: EdgeInsets.symmetric( 105 | vertical: 17.0, horizontal: 10.0), 106 | scrollDirection: Axis.horizontal, 107 | itemCount: provider.liveMatch.length, 108 | itemBuilder: (context, index) => Padding( 109 | padding: 110 | EdgeInsets.symmetric(horizontal: 12.0), 111 | child: LiveMatchItem( 112 | match: provider.liveMatch[index]))), 113 | ), 114 | ), 115 | SliverToBoxAdapter( 116 | child: Padding( 117 | padding: EdgeInsets.symmetric(vertical: 18, horizontal: 20), 118 | child: ListHeader( 119 | headerTitle: "All games", 120 | onTrailingTap: () { 121 | Navigator.of(context) 122 | .pushNamed("/table", arguments: widget.league); 123 | }, 124 | trailingTitle: teams.isNotEmpty ? "See Table" : "", 125 | ), 126 | ), 127 | ), 128 | SliverList( 129 | delegate: SliverChildBuilderDelegate( 130 | (context, index) => Padding( 131 | padding: EdgeInsets.symmetric( 132 | horizontal: 18.0, vertical: 12.0), 133 | child: MatchItem(match: provider.nonLiveMatch[index])), 134 | childCount: provider.nonLiveMatch.length, 135 | )) 136 | ], 137 | ); 138 | }), 139 | ), 140 | Consumer( 141 | builder: (buildContext, footballProvider, child) { 142 | return Visibility( 143 | visible: footballProvider.state == ViewState.loading, 144 | child: SpinKitSquareCircle( 145 | color: Theme.of(context).accentColor, 146 | size: 50.0, 147 | ), 148 | ); 149 | }) 150 | ], 151 | ); 152 | } 153 | 154 | Widget _teamsWidget(List teams) { 155 | return ListView.builder( 156 | physics: ClampingScrollPhysics(), 157 | padding: EdgeInsets.symmetric(horizontal: 16.0), 158 | scrollDirection: Axis.horizontal, 159 | itemCount: teams.length, 160 | itemBuilder: (BuildContext context, int index) => Padding( 161 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 162 | child: RoundImage( 163 | imageUrl: teams[index].emblemUrl, 164 | isElevated: true, 165 | onTap: () {}), 166 | )); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "7.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.39.17" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.13" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.6.0" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.6.1" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.0" 46 | build: 47 | dependency: transitive 48 | description: 49 | name: build 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.3.0" 53 | build_config: 54 | dependency: transitive 55 | description: 56 | name: build_config 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.4.2" 60 | build_daemon: 61 | dependency: transitive 62 | description: 63 | name: build_daemon 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.10" 67 | build_resolvers: 68 | dependency: transitive 69 | description: 70 | name: build_resolvers 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.3.11" 74 | build_runner: 75 | dependency: "direct dev" 76 | description: 77 | name: build_runner 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.10.2" 81 | build_runner_core: 82 | dependency: transitive 83 | description: 84 | name: build_runner_core 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "6.0.1" 88 | built_collection: 89 | dependency: transitive 90 | description: 91 | name: built_collection 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "4.3.2" 95 | built_value: 96 | dependency: transitive 97 | description: 98 | name: built_value 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "7.1.0" 102 | cached_network_image: 103 | dependency: "direct main" 104 | description: 105 | name: cached_network_image 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "2.4.1" 109 | characters: 110 | dependency: transitive 111 | description: 112 | name: characters 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.1.0" 116 | charcode: 117 | dependency: transitive 118 | description: 119 | name: charcode 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.2.0" 123 | checked_yaml: 124 | dependency: transitive 125 | description: 126 | name: checked_yaml 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.0.4" 130 | cli_util: 131 | dependency: transitive 132 | description: 133 | name: cli_util 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.2.0" 137 | clock: 138 | dependency: transitive 139 | description: 140 | name: clock 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.1.0" 144 | code_builder: 145 | dependency: transitive 146 | description: 147 | name: code_builder 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.7.0" 151 | collection: 152 | dependency: transitive 153 | description: 154 | name: collection 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.15.0" 158 | convert: 159 | dependency: transitive 160 | description: 161 | name: convert 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.1.1" 165 | crypto: 166 | dependency: transitive 167 | description: 168 | name: crypto 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.1.5" 172 | csslib: 173 | dependency: transitive 174 | description: 175 | name: csslib 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "0.16.2" 179 | cupertino_icons: 180 | dependency: "direct main" 181 | description: 182 | name: cupertino_icons 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "0.1.3" 186 | dart_style: 187 | dependency: transitive 188 | description: 189 | name: dart_style 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.3.6" 193 | dartz: 194 | dependency: "direct main" 195 | description: 196 | name: dartz 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "0.9.2" 200 | dio: 201 | dependency: "direct main" 202 | description: 203 | name: dio 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "3.0.10" 207 | fake_async: 208 | dependency: transitive 209 | description: 210 | name: fake_async 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.2.0" 214 | ffi: 215 | dependency: transitive 216 | description: 217 | name: ffi 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.1.2" 221 | file: 222 | dependency: transitive 223 | description: 224 | name: file 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "5.2.1" 228 | fixnum: 229 | dependency: transitive 230 | description: 231 | name: fixnum 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.10.11" 235 | flutter: 236 | dependency: "direct main" 237 | description: flutter 238 | source: sdk 239 | version: "0.0.0" 240 | flutter_blurhash: 241 | dependency: transitive 242 | description: 243 | name: flutter_blurhash 244 | url: "https://pub.dartlang.org" 245 | source: hosted 246 | version: "0.5.0" 247 | flutter_cache_manager: 248 | dependency: transitive 249 | description: 250 | name: flutter_cache_manager 251 | url: "https://pub.dartlang.org" 252 | source: hosted 253 | version: "2.0.0" 254 | flutter_spinkit: 255 | dependency: "direct main" 256 | description: 257 | name: flutter_spinkit 258 | url: "https://pub.dartlang.org" 259 | source: hosted 260 | version: "4.1.2+1" 261 | flutter_svg: 262 | dependency: "direct main" 263 | description: 264 | name: flutter_svg 265 | url: "https://pub.dartlang.org" 266 | source: hosted 267 | version: "0.20.0-nullsafety.3" 268 | flutter_test: 269 | dependency: "direct dev" 270 | description: flutter 271 | source: sdk 272 | version: "0.0.0" 273 | flutter_web_plugins: 274 | dependency: transitive 275 | description: flutter 276 | source: sdk 277 | version: "0.0.0" 278 | get_it: 279 | dependency: "direct main" 280 | description: 281 | name: get_it 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "3.1.0" 285 | glob: 286 | dependency: transitive 287 | description: 288 | name: glob 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.2.0" 292 | graphs: 293 | dependency: transitive 294 | description: 295 | name: graphs 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "0.2.0" 299 | hive: 300 | dependency: "direct main" 301 | description: 302 | name: hive 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.2.0" 306 | hive_flutter: 307 | dependency: "direct main" 308 | description: 309 | name: hive_flutter 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "0.3.1" 313 | hive_generator: 314 | dependency: "direct dev" 315 | description: 316 | name: hive_generator 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.5.2" 320 | html: 321 | dependency: transitive 322 | description: 323 | name: html 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "0.14.0+4" 327 | http: 328 | dependency: "direct main" 329 | description: 330 | name: http 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "0.12.2" 334 | http_multi_server: 335 | dependency: transitive 336 | description: 337 | name: http_multi_server 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.2.0" 341 | http_parser: 342 | dependency: transitive 343 | description: 344 | name: http_parser 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "3.1.4" 348 | intl: 349 | dependency: "direct main" 350 | description: 351 | name: intl 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "0.16.1" 355 | io: 356 | dependency: transitive 357 | description: 358 | name: io 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "0.3.5" 362 | js: 363 | dependency: transitive 364 | description: 365 | name: js 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "0.6.3" 369 | json_annotation: 370 | dependency: "direct main" 371 | description: 372 | name: json_annotation 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "3.1.1" 376 | json_serializable: 377 | dependency: "direct dev" 378 | description: 379 | name: json_serializable 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "3.5.1" 383 | logging: 384 | dependency: "direct main" 385 | description: 386 | name: logging 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "0.11.4" 390 | lottie: 391 | dependency: "direct main" 392 | description: 393 | name: lottie 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.6.0" 397 | matcher: 398 | dependency: transitive 399 | description: 400 | name: matcher 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "0.12.10" 404 | material_design_icons_flutter: 405 | dependency: "direct main" 406 | description: 407 | name: material_design_icons_flutter 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "4.0.5955" 411 | meta: 412 | dependency: transitive 413 | description: 414 | name: meta 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "1.3.0" 418 | mime: 419 | dependency: transitive 420 | description: 421 | name: mime 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "0.9.7" 425 | nested: 426 | dependency: transitive 427 | description: 428 | name: nested 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.0.0" 432 | node_interop: 433 | dependency: transitive 434 | description: 435 | name: node_interop 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.2.1" 439 | node_io: 440 | dependency: transitive 441 | description: 442 | name: node_io 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "1.2.0" 446 | octo_image: 447 | dependency: transitive 448 | description: 449 | name: octo_image 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "0.3.0" 453 | package_config: 454 | dependency: transitive 455 | description: 456 | name: package_config 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "1.9.3" 460 | path: 461 | dependency: "direct overridden" 462 | description: 463 | name: path 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "1.8.1" 467 | path_drawing: 468 | dependency: transitive 469 | description: 470 | name: path_drawing 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "0.5.1+1" 474 | path_parsing: 475 | dependency: transitive 476 | description: 477 | name: path_parsing 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "0.2.1" 481 | path_provider: 482 | dependency: "direct main" 483 | description: 484 | name: path_provider 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "1.6.28" 488 | path_provider_linux: 489 | dependency: transitive 490 | description: 491 | name: path_provider_linux 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "0.0.1+2" 495 | path_provider_macos: 496 | dependency: transitive 497 | description: 498 | name: path_provider_macos 499 | url: "https://pub.dartlang.org" 500 | source: hosted 501 | version: "0.0.4+8" 502 | path_provider_platform_interface: 503 | dependency: transitive 504 | description: 505 | name: path_provider_platform_interface 506 | url: "https://pub.dartlang.org" 507 | source: hosted 508 | version: "1.0.4" 509 | path_provider_windows: 510 | dependency: transitive 511 | description: 512 | name: path_provider_windows 513 | url: "https://pub.dartlang.org" 514 | source: hosted 515 | version: "0.0.5" 516 | pedantic: 517 | dependency: transitive 518 | description: 519 | name: pedantic 520 | url: "https://pub.dartlang.org" 521 | source: hosted 522 | version: "1.11.1" 523 | petitparser: 524 | dependency: transitive 525 | description: 526 | name: petitparser 527 | url: "https://pub.dartlang.org" 528 | source: hosted 529 | version: "4.1.0" 530 | platform: 531 | dependency: transitive 532 | description: 533 | name: platform 534 | url: "https://pub.dartlang.org" 535 | source: hosted 536 | version: "3.1.0" 537 | plugin_platform_interface: 538 | dependency: transitive 539 | description: 540 | name: plugin_platform_interface 541 | url: "https://pub.dartlang.org" 542 | source: hosted 543 | version: "1.0.3" 544 | pointycastle: 545 | dependency: transitive 546 | description: 547 | name: pointycastle 548 | url: "https://pub.dartlang.org" 549 | source: hosted 550 | version: "1.0.2" 551 | pool: 552 | dependency: transitive 553 | description: 554 | name: pool 555 | url: "https://pub.dartlang.org" 556 | source: hosted 557 | version: "1.5.0" 558 | pretty_dio_logger: 559 | dependency: "direct main" 560 | description: 561 | name: pretty_dio_logger 562 | url: "https://pub.dartlang.org" 563 | source: hosted 564 | version: "1.1.1" 565 | process: 566 | dependency: transitive 567 | description: 568 | name: process 569 | url: "https://pub.dartlang.org" 570 | source: hosted 571 | version: "3.0.13" 572 | provider: 573 | dependency: "direct main" 574 | description: 575 | name: provider 576 | url: "https://pub.dartlang.org" 577 | source: hosted 578 | version: "6.0.1" 579 | pub_semver: 580 | dependency: transitive 581 | description: 582 | name: pub_semver 583 | url: "https://pub.dartlang.org" 584 | source: hosted 585 | version: "1.4.4" 586 | pubspec_parse: 587 | dependency: transitive 588 | description: 589 | name: pubspec_parse 590 | url: "https://pub.dartlang.org" 591 | source: hosted 592 | version: "0.1.8" 593 | quiver: 594 | dependency: transitive 595 | description: 596 | name: quiver 597 | url: "https://pub.dartlang.org" 598 | source: hosted 599 | version: "2.1.5" 600 | retrofit: 601 | dependency: "direct main" 602 | description: 603 | name: retrofit 604 | url: "https://pub.dartlang.org" 605 | source: hosted 606 | version: "1.3.4+1" 607 | retrofit_generator: 608 | dependency: "direct dev" 609 | description: 610 | name: retrofit_generator 611 | url: "https://pub.dartlang.org" 612 | source: hosted 613 | version: "1.4.1+3" 614 | rxdart: 615 | dependency: transitive 616 | description: 617 | name: rxdart 618 | url: "https://pub.dartlang.org" 619 | source: hosted 620 | version: "0.24.1" 621 | shared_preferences: 622 | dependency: "direct main" 623 | description: 624 | name: shared_preferences 625 | url: "https://pub.dartlang.org" 626 | source: hosted 627 | version: "0.5.12+4" 628 | shared_preferences_linux: 629 | dependency: transitive 630 | description: 631 | name: shared_preferences_linux 632 | url: "https://pub.dartlang.org" 633 | source: hosted 634 | version: "0.0.2+4" 635 | shared_preferences_macos: 636 | dependency: transitive 637 | description: 638 | name: shared_preferences_macos 639 | url: "https://pub.dartlang.org" 640 | source: hosted 641 | version: "0.0.1+11" 642 | shared_preferences_platform_interface: 643 | dependency: transitive 644 | description: 645 | name: shared_preferences_platform_interface 646 | url: "https://pub.dartlang.org" 647 | source: hosted 648 | version: "1.0.4" 649 | shared_preferences_web: 650 | dependency: transitive 651 | description: 652 | name: shared_preferences_web 653 | url: "https://pub.dartlang.org" 654 | source: hosted 655 | version: "0.1.2+7" 656 | shared_preferences_windows: 657 | dependency: transitive 658 | description: 659 | name: shared_preferences_windows 660 | url: "https://pub.dartlang.org" 661 | source: hosted 662 | version: "0.0.2+3" 663 | shelf: 664 | dependency: transitive 665 | description: 666 | name: shelf 667 | url: "https://pub.dartlang.org" 668 | source: hosted 669 | version: "0.7.9" 670 | shelf_web_socket: 671 | dependency: transitive 672 | description: 673 | name: shelf_web_socket 674 | url: "https://pub.dartlang.org" 675 | source: hosted 676 | version: "0.2.4+1" 677 | sky_engine: 678 | dependency: transitive 679 | description: flutter 680 | source: sdk 681 | version: "0.0.99" 682 | source_gen: 683 | dependency: transitive 684 | description: 685 | name: source_gen 686 | url: "https://pub.dartlang.org" 687 | source: hosted 688 | version: "0.9.7+1" 689 | source_span: 690 | dependency: transitive 691 | description: 692 | name: source_span 693 | url: "https://pub.dartlang.org" 694 | source: hosted 695 | version: "1.8.1" 696 | sqflite: 697 | dependency: transitive 698 | description: 699 | name: sqflite 700 | url: "https://pub.dartlang.org" 701 | source: hosted 702 | version: "1.3.2+4" 703 | sqflite_common: 704 | dependency: transitive 705 | description: 706 | name: sqflite_common 707 | url: "https://pub.dartlang.org" 708 | source: hosted 709 | version: "1.0.3+3" 710 | stack_trace: 711 | dependency: transitive 712 | description: 713 | name: stack_trace 714 | url: "https://pub.dartlang.org" 715 | source: hosted 716 | version: "1.10.0" 717 | stream_channel: 718 | dependency: transitive 719 | description: 720 | name: stream_channel 721 | url: "https://pub.dartlang.org" 722 | source: hosted 723 | version: "2.1.0" 724 | stream_transform: 725 | dependency: transitive 726 | description: 727 | name: stream_transform 728 | url: "https://pub.dartlang.org" 729 | source: hosted 730 | version: "1.2.0" 731 | string_scanner: 732 | dependency: transitive 733 | description: 734 | name: string_scanner 735 | url: "https://pub.dartlang.org" 736 | source: hosted 737 | version: "1.1.0" 738 | synchronized: 739 | dependency: transitive 740 | description: 741 | name: synchronized 742 | url: "https://pub.dartlang.org" 743 | source: hosted 744 | version: "2.2.0+2" 745 | term_glyph: 746 | dependency: transitive 747 | description: 748 | name: term_glyph 749 | url: "https://pub.dartlang.org" 750 | source: hosted 751 | version: "1.2.0" 752 | test_api: 753 | dependency: transitive 754 | description: 755 | name: test_api 756 | url: "https://pub.dartlang.org" 757 | source: hosted 758 | version: "0.3.0" 759 | theme_mode_handler: 760 | dependency: "direct main" 761 | description: 762 | name: theme_mode_handler 763 | url: "https://pub.dartlang.org" 764 | source: hosted 765 | version: "2.0.0" 766 | timing: 767 | dependency: transitive 768 | description: 769 | name: timing 770 | url: "https://pub.dartlang.org" 771 | source: hosted 772 | version: "0.1.1+3" 773 | tuple: 774 | dependency: transitive 775 | description: 776 | name: tuple 777 | url: "https://pub.dartlang.org" 778 | source: hosted 779 | version: "1.0.3" 780 | typed_data: 781 | dependency: transitive 782 | description: 783 | name: typed_data 784 | url: "https://pub.dartlang.org" 785 | source: hosted 786 | version: "1.3.0" 787 | uuid: 788 | dependency: transitive 789 | description: 790 | name: uuid 791 | url: "https://pub.dartlang.org" 792 | source: hosted 793 | version: "2.2.2" 794 | vector_math: 795 | dependency: transitive 796 | description: 797 | name: vector_math 798 | url: "https://pub.dartlang.org" 799 | source: hosted 800 | version: "2.1.0" 801 | watcher: 802 | dependency: transitive 803 | description: 804 | name: watcher 805 | url: "https://pub.dartlang.org" 806 | source: hosted 807 | version: "0.9.7+15" 808 | web_socket_channel: 809 | dependency: transitive 810 | description: 811 | name: web_socket_channel 812 | url: "https://pub.dartlang.org" 813 | source: hosted 814 | version: "1.2.0" 815 | webview_flutter: 816 | dependency: "direct main" 817 | description: 818 | name: webview_flutter 819 | url: "https://pub.dartlang.org" 820 | source: hosted 821 | version: "0.3.24" 822 | win32: 823 | dependency: transitive 824 | description: 825 | name: win32 826 | url: "https://pub.dartlang.org" 827 | source: hosted 828 | version: "2.2.10" 829 | xdg_directories: 830 | dependency: transitive 831 | description: 832 | name: xdg_directories 833 | url: "https://pub.dartlang.org" 834 | source: hosted 835 | version: "0.1.2" 836 | xml: 837 | dependency: transitive 838 | description: 839 | name: xml 840 | url: "https://pub.dartlang.org" 841 | source: hosted 842 | version: "5.1.2" 843 | yaml: 844 | dependency: transitive 845 | description: 846 | name: yaml 847 | url: "https://pub.dartlang.org" 848 | source: hosted 849 | version: "2.2.1" 850 | sdks: 851 | dart: ">=2.13.0 <3.0.0" 852 | flutter: ">=1.24.0-7.0" 853 | --------------------------------------------------------------------------------