From 214d27942ad2be27cb15eb388c1638d93f2c41aa Mon Sep 17 00:00:00 2001 From: Yudi Setiawan Date: Sun, 12 Apr 2020 17:31:01 +0700 Subject: [PATCH] Implement build flavor for Android platform --- analysis_options.yaml | 1 + android/app/build.gradle | 12 + android/app/src/main/AndroidManifest.xml | 2 +- ios/Runner.xcodeproj/project.pbxproj | 27 +- lib/{src/ui => }/app.dart | 5 +- lib/config/base_url_config.dart | 4 + lib/config/device_utils.dart | 33 ++ lib/config/flavor_config.dart | 68 +++ .../presentation/page/home/home_page.dart | 12 + lib/main.dart | 8 - lib/main_development.dart | 14 + lib/main_production.dart | 12 + lib/src/api/api_provider.dart | 82 --- lib/src/api/api_repository.dart | 29 - lib/src/bloc/home/home_bloc.dart | 99 ---- lib/src/model/category/category.dart | 20 - lib/src/model/category/category.g.dart | 19 - .../response_top_headlinews_news.dart | 69 --- .../response_top_headlinews_news.g.dart | 63 -- lib/src/ui/home/home_screen.dart | 556 ------------------ pubspec.lock | 9 +- pubspec.yaml | 4 + 22 files changed, 186 insertions(+), 962 deletions(-) create mode 100644 analysis_options.yaml rename lib/{src/ui => }/app.dart (55%) create mode 100644 lib/config/base_url_config.dart create mode 100644 lib/config/device_utils.dart create mode 100644 lib/config/flavor_config.dart create mode 100644 lib/feature/presentation/page/home/home_page.dart delete mode 100644 lib/main.dart create mode 100644 lib/main_development.dart create mode 100644 lib/main_production.dart delete mode 100644 lib/src/api/api_provider.dart delete mode 100644 lib/src/api/api_repository.dart delete mode 100644 lib/src/bloc/home/home_bloc.dart delete mode 100644 lib/src/model/category/category.dart delete mode 100644 lib/src/model/category/category.g.dart delete mode 100644 lib/src/model/topheadlinesnews/response_top_headlinews_news.dart delete mode 100644 lib/src/model/topheadlinesnews/response_top_headlinews_news.g.dart delete mode 100644 lib/src/ui/home/home_screen.dart diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..d4fcc1a --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:pedantic/analysis_options.yaml \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 01d37ab..d51cff7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -53,6 +53,18 @@ android { signingConfig signingConfigs.debug } } + + flavorDimensions "production, development" + productFlavors { + production { + resValue "string", "app_name", "News App" + } + development { + resValue "string", "app_name", "dev-News App" + applicationIdSuffix ".development" + versionNameSuffix "-dev" + } + } } flutter { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3faeab3..44c1dd3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ FlutterApplication and put your custom class here. --> androidDeviceInfo() async { + var deviceInfoPlugin = DeviceInfoPlugin(); + return deviceInfoPlugin.androidInfo; + } + + static Future iosDeviceInfo() async { + var deviceInfoPlugin = DeviceInfoPlugin(); + return deviceInfoPlugin.iosInfo; + } + +} \ No newline at end of file diff --git a/lib/config/flavor_config.dart b/lib/config/flavor_config.dart new file mode 100644 index 0000000..467e7ba --- /dev/null +++ b/lib/config/flavor_config.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; + +enum Flavor { + DEVELOPMENT, + PRODUCTION, +} + +class FlavorValues { + final String baseUrl; + + FlavorValues({this.baseUrl}); +} + +class FlavorConfig { + final Flavor flavor; + final String name; + final Color colorPrimary; + final Color colorPrimaryDark; + final Color colorPrimaryLight; + final Color colorAccent; + final FlavorValues values; + + static FlavorConfig _instance; + + factory FlavorConfig({ + @required Flavor flavor, + Color colorPrimary = Colors.blue, + Color colorPrimaryDark = Colors.blue, + Color colorPrimaryLight = Colors.blue, + Color colorAccent = Colors.blueAccent, + @required FlavorValues values, + }) { + _instance ??= FlavorConfig._internal( + flavor, + enumName(flavor.toString()), + colorPrimary, + colorPrimaryDark, + colorPrimaryLight, + colorAccent, + values, + ); + return _instance; + } + + FlavorConfig._internal( + this.flavor, + this.name, + this.colorPrimary, + this.colorPrimaryDark, + this.colorPrimaryLight, + this.colorAccent, + this.values, + ); + + static FlavorConfig get instance { + return _instance; + } + + static String enumName(String enumToString) { + var paths = enumToString.split('.'); + return paths[paths.length - 1]; + } + + static bool isProduction() => _instance.flavor == Flavor.PRODUCTION; + + static bool isDevelopment() => _instance.flavor == Flavor.DEVELOPMENT; +} diff --git a/lib/feature/presentation/page/home/home_page.dart b/lib/feature/presentation/page/home/home_page.dart new file mode 100644 index 0000000..5b2fd14 --- /dev/null +++ b/lib/feature/presentation/page/home/home_page.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class HomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text('Hello World'), + ) + ); + } +} diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index 819faf8..0000000 --- a/lib/main.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_news_app/src/ui/app.dart'; -import 'package:flutter_stetho/flutter_stetho.dart'; - -void main() { - Stetho.initialize(); - runApp(App()); -} diff --git a/lib/main_development.dart b/lib/main_development.dart new file mode 100644 index 0000000..d70b46d --- /dev/null +++ b/lib/main_development.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_news_app/app.dart'; +import 'package:flutter_news_app/config/base_url_config.dart'; +import 'package:flutter_news_app/config/flavor_config.dart'; +import 'package:flutter_stetho/flutter_stetho.dart'; + +void main() async { + FlavorConfig( + flavor: Flavor.DEVELOPMENT, + values: FlavorValues(baseUrl: BaseUrlConfig().baseUrlDevelopment), + ); + await Stetho.initialize(); + runApp(App()); +} \ No newline at end of file diff --git a/lib/main_production.dart b/lib/main_production.dart new file mode 100644 index 0000000..7106b72 --- /dev/null +++ b/lib/main_production.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_news_app/app.dart'; +import 'package:flutter_news_app/config/base_url_config.dart'; +import 'package:flutter_news_app/config/flavor_config.dart'; + +void main() async { + FlavorConfig( + flavor: Flavor.PRODUCTION, + values: FlavorValues(baseUrl: BaseUrlConfig().baseUrlProduction), + ); + runApp(App()); +} \ No newline at end of file diff --git a/lib/src/api/api_provider.dart b/lib/src/api/api_provider.dart deleted file mode 100644 index 2040709..0000000 --- a/lib/src/api/api_provider.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:flutter_news_app/src/model/topheadlinesnews/response_top_headlinews_news.dart'; - -class ApiProvider { - final Dio _dio = Dio(); - final String _baseUrl = - 'https://newsapi.org/v2/top-headlines?country=id&apiKey=YOUR_API_KEY'; - - void printOutError(error, StackTrace stacktrace) { - print('Exception occured: $error with stacktrace: $stacktrace'); - } - - Future getTopHeadlinesNews() async { - try { - final response = await _dio.get(_baseUrl); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopBusinessHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=business'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopEntertainmentHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=entertainment'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopHealthHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=health'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopScienceHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=science'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopSportHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=sport'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } - - Future getTopTechnologyHeadlinesNews() async { - try { - final response = await _dio.get('$_baseUrl&category=technology'); - return ResponseTopHeadlinesNews.fromJson(response.data); - } catch (error, stacktrace) { - printOutError(error, stacktrace); - return ResponseTopHeadlinesNews.withError('$error'); - } - } -} diff --git a/lib/src/api/api_repository.dart b/lib/src/api/api_repository.dart deleted file mode 100644 index de56cea..0000000 --- a/lib/src/api/api_repository.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_news_app/src/api/api_provider.dart'; -import 'package:flutter_news_app/src/model/topheadlinesnews/response_top_headlinews_news.dart'; - -class ApiRepository { - final _apiProvider = ApiProvider(); - - Future fetchTopHeadlinesNews() => - _apiProvider.getTopHeadlinesNews(); - - Future fetchTopBusinessHeadlinesNews() => - _apiProvider.getTopBusinessHeadlinesNews(); - - Future fetchTopEntertainmentHeadlinesNews() => - _apiProvider.getTopEntertainmentHeadlinesNews(); - - Future fetchTopHealthHeadlinesNews() => - _apiProvider.getTopHealthHeadlinesNews(); - - Future fetchTopScienceHeadlinesNews() => - _apiProvider.getTopScienceHeadlinesNews(); - - Future fetchTopSportHeadlinesNews() => - _apiProvider.getTopSportHeadlinesNews(); - - Future fetchTopTechnologyHeadlinesNews() => - _apiProvider.getTopTechnologyHeadlinesNews(); -} diff --git a/lib/src/bloc/home/home_bloc.dart b/lib/src/bloc/home/home_bloc.dart deleted file mode 100644 index ccf3244..0000000 --- a/lib/src/bloc/home/home_bloc.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:flutter_news_app/src/api/api_repository.dart'; -import 'package:flutter_news_app/src/model/topheadlinesnews/response_top_headlinews_news.dart'; - -abstract class DataState {} - -class DataInitial extends DataState {} - -class DataLoading extends DataState {} - -class DataSuccess extends DataState { - final ResponseTopHeadlinesNews data; - - DataSuccess(this.data); -} - -class DataFailed extends DataState { - final String errorMessage; - - DataFailed(this.errorMessage); -} - -class DataEvent { - final String category; - - DataEvent(this.category); -} - -class HomeBloc extends Bloc { - @override - DataState get initialState => DataInitial(); - - @override - Stream mapEventToState(DataEvent event) async* { - yield DataLoading(); - final apiRepository = ApiRepository(); - final categoryLowerCase = event.category.toLowerCase(); - switch (categoryLowerCase) { - case 'all': - final data = await apiRepository.fetchTopHeadlinesNews(); - if (data.error == null) { - yield DataSuccess(data); - } else { - yield DataFailed('Failed to fetch data'); - } - break; - case 'business': - final data = await apiRepository.fetchTopBusinessHeadlinesNews(); - if (data.error == null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - case 'entertainment': - final data = await apiRepository.fetchTopEntertainmentHeadlinesNews(); - if (data.error == null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - case 'health': - final data = await apiRepository.fetchTopHealthHeadlinesNews(); - if (data.error == null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - case 'science': - final data = await apiRepository.fetchTopScienceHeadlinesNews(); - if (data.error == null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - case 'sport': - final data = await apiRepository.fetchTopSportHeadlinesNews(); - if (data != null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - case 'technology': - final data = await apiRepository.fetchTopTechnologyHeadlinesNews(); - if (data != null) { - yield DataSuccess(data); - } else { - yield DataFailed(data.error); - } - break; - default: - yield DataFailed('Unknown category'); - } - } -} diff --git a/lib/src/model/category/category.dart b/lib/src/model/category/category.dart deleted file mode 100644 index 8c94444..0000000 --- a/lib/src/model/category/category.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'category.g.dart'; - -@JsonSerializable() -class Category { - String image; - String title; - - Category(this.image, this.title); - - factory Category.fromJson(Map json) => _$CategoryFromJson(json); - - Map toJson() => _$CategoryToJson(this); - - @override - String toString() { - return 'Category{image: $image, title: $title}'; - } - -} \ No newline at end of file diff --git a/lib/src/model/category/category.g.dart b/lib/src/model/category/category.g.dart deleted file mode 100644 index 50ea5e6..0000000 --- a/lib/src/model/category/category.g.dart +++ /dev/null @@ -1,19 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'category.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Category _$CategoryFromJson(Map json) { - return Category( - json['image'] as String, - json['title'] as String, - ); -} - -Map _$CategoryToJson(Category instance) => { - 'image': instance.image, - 'title': instance.title, - }; diff --git a/lib/src/model/topheadlinesnews/response_top_headlinews_news.dart b/lib/src/model/topheadlinesnews/response_top_headlinews_news.dart deleted file mode 100644 index df08cc8..0000000 --- a/lib/src/model/topheadlinesnews/response_top_headlinews_news.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'response_top_headlinews_news.g.dart'; - -@JsonSerializable() -class ResponseTopHeadlinesNews { - String status; - int totalResults; - List
articles; - @JsonKey(ignore: true) - String error; - - ResponseTopHeadlinesNews(this.status, this.totalResults, this.articles); - - factory ResponseTopHeadlinesNews.fromJson(Map json) => - _$ResponseTopHeadlinesNewsFromJson(json); - - ResponseTopHeadlinesNews.withError(this.error); - - Map toJson() => _$ResponseTopHeadlinesNewsToJson(this); - - @override - String toString() { - return 'ResponseTopHeadlinesNews{status: $status, totalResults: $totalResults, articles: $articles, error: $error}'; - } - - -} - -@JsonSerializable() -class Article { - Source source; - String author; - String title; - String description; - String url; - String urlToImage; - String publishedAt; - String content; - - Article(this.source, this.author, this.title, this.description, this.url, - this.urlToImage, this.publishedAt, this.content); - - factory Article.fromJson(Map json) => - _$ArticleFromJson(json); - - Map toJson() => _$ArticleToJson(this); - - @override - String toString() { - return 'Article{source: $source, author: $author, title: $title, description: $description, url: $url, urlToImage: $urlToImage, publishedAt: $publishedAt, content: $content}'; - } -} - -@JsonSerializable() -class Source { - String name; - - Source(this.name); - - factory Source.fromJson(Map json) => _$SourceFromJson(json); - - Map toJson() => _$SourceToJson(this); - - @override - String toString() { - return 'Source{name: $name}'; - } - -} diff --git a/lib/src/model/topheadlinesnews/response_top_headlinews_news.g.dart b/lib/src/model/topheadlinesnews/response_top_headlinews_news.g.dart deleted file mode 100644 index eab5574..0000000 --- a/lib/src/model/topheadlinesnews/response_top_headlinews_news.g.dart +++ /dev/null @@ -1,63 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'response_top_headlinews_news.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ResponseTopHeadlinesNews _$ResponseTopHeadlinesNewsFromJson( - Map json) { - return ResponseTopHeadlinesNews( - json['status'] as String, - json['totalResults'] as int, - (json['articles'] as List) - ?.map((e) => - e == null ? null : Article.fromJson(e as Map)) - ?.toList(), - ); -} - -Map _$ResponseTopHeadlinesNewsToJson( - ResponseTopHeadlinesNews instance) => - { - 'status': instance.status, - 'totalResults': instance.totalResults, - 'articles': instance.articles, - }; - -Article _$ArticleFromJson(Map json) { - return Article( - json['source'] == null - ? null - : Source.fromJson(json['source'] as Map), - json['author'] as String, - json['title'] as String, - json['description'] as String, - json['url'] as String, - json['urlToImage'] as String, - json['publishedAt'] as String, - json['content'] as String, - ); -} - -Map _$ArticleToJson(Article instance) => { - 'source': instance.source, - 'author': instance.author, - 'title': instance.title, - 'description': instance.description, - 'url': instance.url, - 'urlToImage': instance.urlToImage, - 'publishedAt': instance.publishedAt, - 'content': instance.content, - }; - -Source _$SourceFromJson(Map json) { - return Source( - json['name'] as String, - ); -} - -Map _$SourceToJson(Source instance) => { - 'name': instance.name, - }; diff --git a/lib/src/ui/home/home_screen.dart b/lib/src/ui/home/home_screen.dart deleted file mode 100644 index 0162207..0000000 --- a/lib/src/ui/home/home_screen.dart +++ /dev/null @@ -1,556 +0,0 @@ -import 'dart:io'; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_news_app/src/bloc/home/home_bloc.dart'; -import 'package:flutter_news_app/src/model/category/category.dart'; -import 'package:flutter_news_app/src/model/topheadlinesnews/response_top_headlinews_news.dart'; -import 'package:intl/intl.dart'; -import 'package:url_launcher/url_launcher.dart'; - -final GlobalKey scaffoldState = GlobalKey(); - -class HomeScreen extends StatelessWidget { - @override - Widget build(BuildContext context) { - var strToday = getStrToday(); - var mediaQuery = MediaQuery.of(context); - - return Scaffold( - key: scaffoldState, - body: BlocProvider( - builder: (context) => HomeBloc(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - color: Color(0xFFF1F5F9), - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - ), - ), - padding: EdgeInsets.only( - top: mediaQuery.padding.top + 16.0, - bottom: 16.0, - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - WidgetTitle(strToday), - ], - ), - /*SizedBox(height: 8.0), - buildWidgetSearch(),*/ - SizedBox(height: 12.0), - WidgetCategory(), - ], - ), - ), - SizedBox(height: 16.0), - _buildWidgetLabelLatestNews(context), - _buildWidgetSubtitleLatestNews(context), - Expanded( - child: WidgetLatestNews(), - ), - ], - ), - ), - ); - } - - Widget _buildWidgetSubtitleLatestNews(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - 'Top stories at the moment', - style: Theme.of(context).textTheme.caption.merge( - TextStyle( - color: Color(0xFF325384).withOpacity(0.5), - ), - ), - ), - ); - } - - Widget _buildWidgetLabelLatestNews(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - 'Latest News', - style: Theme.of(context).textTheme.subtitle.merge( - TextStyle( - fontSize: 18.0, - color: Color(0xFF325384).withOpacity(0.8), - ), - ), - ), - ); - } - - Widget buildWidgetSearch() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Container( - padding: EdgeInsets.only( - left: 12.0, - top: 8.0, - right: 12.0, - bottom: 8.0, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(20), - ), - color: Colors.white, - ), - child: Row( - children: [ - Expanded( - child: Text( - 'What are you looking for?', - style: TextStyle( - color: Colors.black26, - ), - ), - ), - Icon( - Icons.search, - size: 16.0, - color: Colors.black26, - ), - ], - ), - ), - ); - } - - String getStrToday() { - var today = DateFormat().add_yMMMMd().format(DateTime.now()); - var strDay = today.split(" ")[1].replaceFirst(',', ''); - if (strDay == '1') { - strDay = strDay + "st"; - } else if (strDay == '2') { - strDay = strDay + "nd"; - } else if (strDay == '3') { - strDay = strDay + "rd"; - } else { - strDay = strDay + "th"; - } - var strMonth = today.split(" ")[0]; - var strYear = today.split(" ")[2]; - return "$strDay $strMonth $strYear"; - } -} - -class WidgetTitle extends StatelessWidget { - final String strToday; - - WidgetTitle(this.strToday); - - @override - Widget build(BuildContext context) { - return Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'News Today\n', - style: Theme.of(context).textTheme.title.merge( - TextStyle( - color: Color(0xFF325384), - ), - ), - ), - TextSpan( - text: strToday, - style: Theme.of(context).textTheme.caption.merge( - TextStyle( - color: Color(0xFF325384).withOpacity(0.8), - fontSize: 10.0, - ), - ), - ), - ], - ), - ), - ), - ); - } -} - -class WidgetCategory extends StatefulWidget { - @override - _WidgetCategoryState createState() => _WidgetCategoryState(); -} - -class _WidgetCategoryState extends State { - final listCategories = [ - Category('', 'All'), - Category('assets/images/img_business.png', 'Business'), - Category('assets/images/img_entertainment.png', 'Entertainment'), - Category('assets/images/img_health.png', 'Health'), - Category('assets/images/img_science.png', 'Science'), - Category('assets/images/img_sport.png', 'Sport'), - Category('assets/images/img_technology.png', 'Technology'), - ]; - int indexSelectedCategory = 0; - - @override - void initState() { - final homeBloc = BlocProvider.of(context); - homeBloc.dispatch(DataEvent(listCategories[indexSelectedCategory].title)); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final homeBloc = BlocProvider.of(context); - return Container( - height: 74, - child: ListView.builder( - shrinkWrap: false, - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - Category itemCategory = listCategories[index]; - return Padding( - padding: EdgeInsets.only( - left: 16.0, - right: index == listCategories.length - 1 ? 16.0 : 0.0, - ), - child: Column( - children: [ - GestureDetector( - onTap: () { - setState(() { - indexSelectedCategory = index; - homeBloc.dispatch(DataEvent( - listCategories[indexSelectedCategory].title)); - }); - }, - child: index == 0 - ? Container( - width: 48.0, - height: 48.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color(0xFFBDCDDE), - border: indexSelectedCategory == index - ? Border.all( - color: Colors.white, - width: 5.0, - ) - : null, - ), - child: Icon( - Icons.apps, - color: Colors.white, - ), - ) - : Container( - width: 48.0, - height: 48.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - image: AssetImage(itemCategory.image), - fit: BoxFit.cover, - ), - border: indexSelectedCategory == index - ? Border.all( - color: Colors.white, - width: 5.0, - ) - : null, - ), - ), - ), - SizedBox(height: 8.0), - Text( - itemCategory.title, - style: TextStyle( - fontSize: 14, - color: Color(0xFF325384), - fontWeight: indexSelectedCategory == index - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ], - ), - ); - }, - itemCount: listCategories.length, - ), - ); - } -} - -class WidgetLatestNews extends StatefulWidget { - WidgetLatestNews(); - - @override - _WidgetLatestNewsState createState() => _WidgetLatestNewsState(); -} - -class _WidgetLatestNewsState extends State { - @override - Widget build(BuildContext context) { - MediaQueryData mediaQuery = MediaQuery.of(context); - final HomeBloc homeBloc = BlocProvider.of(context); - return Padding( - padding: EdgeInsets.only( - left: 16.0, - top: 8.0, - right: 16.0, - bottom: mediaQuery.padding.bottom + 16.0, - ), - child: BlocListener( - listener: (context, state) { - if (state is DataFailed) { - Scaffold.of(context).showSnackBar( - SnackBar(content: Text(state.errorMessage)), - ); - } - }, - child: BlocBuilder( - bloc: homeBloc, - builder: (BuildContext context, DataState state) { - return _buildWidgetContentLatestNews(state, mediaQuery); - }, - ), - ), - ); - } - - Widget _buildWidgetContentLatestNews( - DataState state, MediaQueryData mediaQuery) { - if (state is DataLoading) { - return Center( - child: Platform.isAndroid - ? CircularProgressIndicator() - : CupertinoActivityIndicator(), - ); - } else if (state is DataSuccess) { - ResponseTopHeadlinesNews data = state.data; - return ListView.separated( - padding: EdgeInsets.zero, - itemCount: data.articles.length, - separatorBuilder: (context, index) { - return Divider(); - }, - itemBuilder: (context, index) { - Article itemArticle = data.articles[index]; - if (index == 0) { - return Stack( - children: [ - ClipRRect( - child: CachedNetworkImage( - imageUrl: itemArticle.urlToImage, - height: 192.0, - width: mediaQuery.size.width, - fit: BoxFit.cover, - placeholder: (context, url) => Platform.isAndroid - ? CircularProgressIndicator() - : CupertinoActivityIndicator(), - errorWidget: (context, url, error) => Image.asset( - 'assets/images/img_not_found.jpg', - fit: BoxFit.cover, - ), - ), - borderRadius: BorderRadius.all( - Radius.circular(8.0), - ), - ), - GestureDetector( - onTap: () async { - if (await canLaunch(itemArticle.url)) { - await launch(itemArticle.url); - } else { - scaffoldState.currentState.showSnackBar(SnackBar( - content: Text('Could not launch news'), - )); - } - }, - child: Container( - width: mediaQuery.size.width, - height: 192.0, - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(8.0), - ), - gradient: LinearGradient( - colors: [ - Colors.black.withOpacity(0.8), - Colors.black.withOpacity(0.0), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [ - 0.0, - 0.7, - ], - ), - ), - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 12.0, - right: 12.0, - ), - child: Text( - itemArticle.title, - style: TextStyle( - color: Colors.white, - ), - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - right: 12.0, - ), - child: Wrap( - children: [ - Icon( - Icons.launch, - color: Colors.white.withOpacity(0.8), - size: 12.0, - ), - SizedBox(width: 4.0), - Text( - '${itemArticle.source.name}', - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 11.0, - ), - ), - ], - ), - ), - ], - ), - ], - ); - } else { - return GestureDetector( - onTap: () async { - if (await canLaunch(itemArticle.url)) { - await launch(itemArticle.url); - } - }, - child: Container( - width: mediaQuery.size.width, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: SizedBox( - height: 72.0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - itemArticle.title, - overflow: TextOverflow.ellipsis, - maxLines: 3, - style: TextStyle( - fontSize: 16.0, - color: Color(0xFF325384), - fontWeight: FontWeight.w400, - ), - ), - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Icon( - Icons.launch, - size: 12.0, - color: Color(0xFF325384).withOpacity(0.5), - ), - SizedBox(width: 4.0), - Text( - itemArticle.source.name, - style: TextStyle( - color: Color(0xFF325384).withOpacity(0.5), - fontSize: 12.0, - ), - ), - ], - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: ClipRRect( - /*child: Image.network( - itemArticle.urlToImage ?? - 'http://api.bengkelrobot.net:8001/assets/images/img_not_found.jpg', - width: 72.0, - height: 72.0, - fit: BoxFit.cover, - ),*/ - child: CachedNetworkImage( - imageUrl: itemArticle.urlToImage, - imageBuilder: (context, imageProvider) { - return Container( - width: 72.0, - height: 72.0, - decoration: BoxDecoration( - image: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, - ), - ), - ); - }, - placeholder: (context, url) => Container( - width: 72.0, - height: 72.0, - child: Center( - child: Platform.isAndroid - ? CircularProgressIndicator() - : CupertinoActivityIndicator(), - ), - ), - errorWidget: (context, url, error) => Image.asset( - 'assets/images/img_not_found.jpg', - fit: BoxFit.cover, - width: 72.0, - height: 72.0, - ), - ), - borderRadius: BorderRadius.all( - Radius.circular(4.0), - ), - ), - ), - ], - ), - ), - ); - } - }, - ); - } else { - return Container(); - } - } -} diff --git a/pubspec.lock b/pubspec.lock index 34359f5..2b2d971 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -211,6 +211,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.4" + device_info: + dependency: "direct main" + description: + name: device_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2+1" dio: dependency: "direct main" description: @@ -786,4 +793,4 @@ packages: version: "2.2.0" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index b389e9d..59b9b01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,10 @@ dependencies: # How to get the most value from Dart static analysis. pedantic: ^1.9.0 + # Flutter plugin providing detailed information about the device (make, model, etc), and + # Android or OS version the app is running on. + device_info: ^0.4.2+1 + dev_dependencies: flutter_test: sdk: flutter