Skip to content

Commit

Permalink
FEATURE: add news page #28
Browse files Browse the repository at this point in the history
FEATURE: add news page
  • Loading branch information
0niel authored Aug 21, 2021
2 parents 3d6d376 + 37f8c83 commit fe00b90
Show file tree
Hide file tree
Showing 25 changed files with 894 additions and 11 deletions.
9 changes: 9 additions & 0 deletions assets/icons/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions assets/icons/tag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions lib/data/datasources/news_remote.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:dio/dio.dart';
import 'package:rtu_mirea_app/common/errors/exceptions.dart';
import 'package:rtu_mirea_app/data/models/news_item_model.dart';
import 'dart:convert';

abstract class NewsRemoteData {
Future<List<NewsItemModel>> getNews(int offset, int limit, [String? tag]);
Future<List<String>> getTags();
}

class NewsRemoteDataImpl extends NewsRemoteData {
static const _API_BASE_URL = "http://schedule.mirea.ninja:5050";

final Dio httpClient;

NewsRemoteDataImpl({required this.httpClient});

@override
Future<List<NewsItemModel>> getNews(int offset, int limit,
[String? tag]) async {
try {
final response = await httpClient.get(
_API_BASE_URL + '/news' + '?tag=$tag&limit=$limit&offset=$offset');

if (response.statusCode == 200) {
Map responseBody = response.data;
return responseBody["news"]
.map<NewsItemModel>((newsItem) => NewsItemModel.fromJson(newsItem))
.toList();
} else {
throw ServerException('Response status code is $response.statusCode');
}
} catch (e) {
throw ServerException(e.toString());
}
}

@override
Future<List<String>> getTags() async {
try {
final response = await httpClient.get(_API_BASE_URL + '/tags');

if (response.statusCode == 200) {
Map responseBody = response.data;

List<String> tags = [];
tags = List<String>.from(responseBody["tags"].map((x) => x['name']));
return tags;
} else {
throw ServerException('Response status code is $response.statusCode');
}
} catch (e) {
throw ServerException(e.toString());
}
}
}
30 changes: 30 additions & 0 deletions lib/data/models/news_item_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:rtu_mirea_app/domain/entities/news_item.dart';

class NewsItemModel extends NewsItem {
NewsItemModel({
required title,
required text,
required date,
required images,
required tags,
}) : super(
title: title,
text: text,
date: date,
images: images,
tags: tags,
);

factory NewsItemModel.fromJson(Map<String, dynamic> json) {
return NewsItemModel(
title: json['title'],
text: json['text'],
date: DateTime.parse(json['date']),
images: List<String>.from(json["images"].map((x) => x['name'])),
tags: List<String>.from(json["tags"].map((x) => x['name'])),
);
}

@override
List<Object?> get props => [title, text, date, images, tags];
}
46 changes: 46 additions & 0 deletions lib/data/repositories/news_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:dartz/dartz.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:rtu_mirea_app/common/errors/exceptions.dart';
import 'package:rtu_mirea_app/common/errors/failures.dart';
import 'package:rtu_mirea_app/data/datasources/news_remote.dart';
import 'package:rtu_mirea_app/domain/entities/news_item.dart';
import 'package:rtu_mirea_app/domain/repositories/news_repository.dart';

class NewsRepositoryImpl implements NewsRepository {
final NewsRemoteData remoteDataSource;
final InternetConnectionChecker connectionChecker;

NewsRepositoryImpl({
required this.remoteDataSource,
required this.connectionChecker,
});

@override
Future<Either<Failure, List<NewsItem>>> getNews(int offset, int limit,
[String? tag]) async {
if (await connectionChecker.hasConnection) {
try {
final newsList = await remoteDataSource.getNews(offset, limit, tag);
return Right(newsList);
} on ServerException {
return Left(ServerFailure());
}
} else {
return Left(ServerFailure());
}
}

@override
Future<Either<Failure, List<String>>> getTags() async {
if (await connectionChecker.hasConnection) {
try {
final tagsList = await remoteDataSource.getTags();
return Right(tagsList);
} on ServerException {
return Left(ServerFailure());
}
} else {
return Left(ServerFailure());
}
}
}
20 changes: 20 additions & 0 deletions lib/domain/entities/news_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:equatable/equatable.dart';

class NewsItem extends Equatable {
final String title;
final String text;
final DateTime date;
final List<String> images;
final List<String> tags;

NewsItem({
required this.title,
required this.text,
required this.date,
required this.images,
required this.tags,
});

@override
List<Object?> get props => [title, text, date, images, tags];
}
9 changes: 9 additions & 0 deletions lib/domain/repositories/news_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:dartz/dartz.dart';
import 'package:rtu_mirea_app/common/errors/failures.dart';
import 'package:rtu_mirea_app/domain/entities/news_item.dart';

abstract class NewsRepository {
Future<Either<Failure, List<NewsItem>>> getNews(
int offset, int limit, String tag);
Future<Either<Failure, List<String>>> getTags();
}
29 changes: 29 additions & 0 deletions lib/domain/usecases/get_news.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:rtu_mirea_app/common/errors/failures.dart';
import 'package:rtu_mirea_app/domain/entities/news_item.dart';
import 'package:rtu_mirea_app/domain/repositories/news_repository.dart';
import 'package:rtu_mirea_app/domain/usecases/usecase.dart';

class GetNews extends UseCase<List<NewsItem>, GetNewsParams> {
final NewsRepository newsRepository;

GetNews(this.newsRepository);

@override
Future<Either<Failure, List<NewsItem>>> call(GetNewsParams params) async {
return await newsRepository.getNews(
params.offset, params.limit, params.tag ?? 'все');
}
}

class GetNewsParams extends Equatable {
final int offset;
final int limit;
final String? tag;

GetNewsParams({required this.offset, required this.limit, this.tag});

@override
List<Object?> get props => [offset, limit, tag];
}
15 changes: 15 additions & 0 deletions lib/domain/usecases/get_news_tags.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:dartz/dartz.dart';
import 'package:rtu_mirea_app/common/errors/failures.dart';
import 'package:rtu_mirea_app/domain/repositories/news_repository.dart';
import 'package:rtu_mirea_app/domain/usecases/usecase.dart';

class GetNewsTags extends UseCase<List<String>, void> {
final NewsRepository newsRepository;

GetNewsTags(this.newsRepository);

@override
Future<Either<Failure, List<String>>> call([_]) async {
return await newsRepository.getTags();
}
}
6 changes: 5 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:rtu_mirea_app/presentation/bloc/home_navigator_bloc/home_navigator_bloc.dart';
import 'package:rtu_mirea_app/presentation/bloc/map_cubit/map_cubit.dart';
import 'package:rtu_mirea_app/presentation/bloc/news_bloc/news_bloc.dart';
import 'package:rtu_mirea_app/presentation/bloc/onboarding_cubit/onboarding_cubit.dart';
import 'package:rtu_mirea_app/presentation/bloc/schedule_bloc/schedule_bloc.dart';
import 'package:rtu_mirea_app/presentation/pages/home/home_navigator_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/map/map_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/news/news_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/onboarding/onboarding_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/schedule/schedule_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/profile/profile_screen.dart';
Expand Down Expand Up @@ -61,6 +63,7 @@ class App extends StatelessWidget {
BlocProvider<OnboardingCubit>(
create: (context) => getIt<OnboardingCubit>()),
BlocProvider<MapCubit>(create: (context) => getIt<MapCubit>()),
BlocProvider<NewsBloc>(create: (context) => getIt<NewsBloc>()),
],
child: AdaptiveTheme(
light: lightTheme,
Expand All @@ -75,7 +78,8 @@ class App extends StatelessWidget {
ScheduleScreen.routeName: (context) => ScheduleScreen(),
MapScreen.routeName: (context) => MapScreen(),
ProfileScreen.routeName: (context) => ProfileScreen(),
OnBoardingScreen.routeName: (context) => OnBoardingScreen()
OnBoardingScreen.routeName: (context) => OnBoardingScreen(),
NewsScreen.routeName: (context) => NewsScreen()
},
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:rtu_mirea_app/presentation/pages/map/map_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/news/news_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/profile/profile_screen.dart';
import 'package:rtu_mirea_app/presentation/pages/schedule/schedule_screen.dart';

Expand Down Expand Up @@ -32,6 +33,9 @@ class HomeNavigatorBloc extends Bloc<HomeNavigatorEvent, HomeNavigatorState> {
case MapScreen.routeName:
yield MapPage();
break;
case NewsScreen.routeName:
yield NewsPage();
break;
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class SchedulePage extends HomeNavigatorState {
List<Object> get props => [_screen];
}


class MapPage extends HomeNavigatorState {
final _screen = MapScreen();

Expand All @@ -39,3 +38,13 @@ class ProfilePage extends HomeNavigatorState {
@override
List<Object> get props => [_screen];
}

class NewsPage extends HomeNavigatorState {
final _screen = NewsScreen();

@override
Widget get screen => _screen;

@override
List<Object> get props => [_screen];
}
64 changes: 64 additions & 0 deletions lib/presentation/bloc/news_bloc/news_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:rtu_mirea_app/domain/entities/news_item.dart';
import 'package:rtu_mirea_app/domain/usecases/get_news.dart';
import 'package:rtu_mirea_app/domain/usecases/get_news_tags.dart';

part 'news_event.dart';
part 'news_state.dart';

class NewsBloc extends Bloc<NewsEvent, NewsState> {
NewsBloc({
required this.getNews,
required this.getNewsTags,
}) : super(NewsInitial());

final GetNews getNews;
final GetNewsTags getNewsTags;

bool _isFirstFetch = true;
int _offset = 0;

@override
Stream<NewsState> mapEventToState(
NewsEvent event,
) async* {
if (event is NewsLoadEvent) {
List<String> tagsList = [];
List<NewsItem> oldNews = [];

bool hasFetchError = false;

if (_isFirstFetch) {
yield NewsLoading(oldNews: oldNews, isFirstFetch: true);
_isFirstFetch = false;
final tags = await getNewsTags();
tags.fold((failure) {
hasFetchError = true;
}, (r) {
tagsList = r;
});
} else {
if (state is NewsLoaded) {
tagsList = (state as NewsLoaded).tags;
oldNews = (state as NewsLoaded).news;
}
yield NewsLoading(oldNews: oldNews, isFirstFetch: false);
}

if (hasFetchError) {
yield NewsLoadError();
return;
}

final news = await getNews(GetNewsParams(offset: _offset, limit: 10));
yield news.fold((failure) => NewsLoadError(), (r) {
_offset += r.length - 1;
List<NewsItem> newNews = List.from(oldNews)..addAll(r);
return NewsLoaded(news: newNews, tags: tagsList);
});
}
}
}
12 changes: 12 additions & 0 deletions lib/presentation/bloc/news_bloc/news_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
part of 'news_bloc.dart';

abstract class NewsEvent extends Equatable {
const NewsEvent();

@override
List<Object> get props => [];
}

class NewsLoadTagsEvent extends NewsEvent {}

class NewsLoadEvent extends NewsEvent {}
Loading

0 comments on commit fe00b90

Please sign in to comment.