Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
artembark committed Aug 5, 2022
1 parent d33d961 commit 2a1d91d
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 252 deletions.
23 changes: 6 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
# Weather App for Friflex
Тестовое приложение для полуения текущей погоды и прогноза погоды на 3 дня.
Код приложения покрыт тестами.
Тестовое приложение для полуения текущей погоды и прогноза погоды на 3 дня.

Код приложения покрыт юнит-тестами.

В случае проблем после клонирования выполнить
flutter pub run build_runner build --delete-conflicting-outputs

В приложении реализованы:
- работа с двумя запросами API погоды:
- запрос текущей погоды
- запрос прогноза погоды на 3 дня с интревалом 3 часа
- отображение экрана текущей погоды с:
- иконкой соответствующего состояния погоды
- текстового описания состояния погоды
- температуры
- влажности
- сокрости ветра
- отображение экрана списка прогнозов погоды на 3 дня с интервалом 3 часа с сортировкой прогнозов погоды по температуре, начиная с самого холодного прогноза и отображающих:
- иконку соответствующего состояния погоды
- текстовое описания состояния погоды
- температуру
- влажность
- сокрость ветра
- работа с двумя запросами API погоды: запрос текущей погоды, запрос прогноза погоды на 3 дня с интревалом 3 часа
- отображение экрана текущей погоды с: иконкой соответствующего состояния погоды, текстового описания состояния погоды, температуры, влажности, скорости ветра
- отображение экрана списка прогнозов погоды на 3 дня с интервалом 3 часа с сортировкой прогнозов погоды по температуре, начиная с самого холодного прогноза и отображающих иконку соответствующего состояния погоды, текстовое описания состояния погоды, температуру, влажность, сокрость ветра
- поле ввода валидирует вводимый текст по длине символов и при подтверждении пустого поля ввода отображается текст ошибки
- использована библиотека для получения состояния интернет-соединения connectivity_plus и при всех интернет запросах проверяется состояние сети
- последнее введенное значение города сохраняется в системных настройках и при следующем входе открвается сразу экран текущей погоды
Expand Down
26 changes: 26 additions & 0 deletions lib/app/app_strings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//строковые константы приложения
abstract class AppStrings {
AppStrings._();

static const errorConnection = 'Ошибка. Проверьте доступ к сети интернет.';
static const errorNoCityName = 'Необходимо ввести название города';
static const errorShortName = 'Слишком короткое название';
static const errorUnexpected = 'Неожиданная ошибка';

static const backButtonText = 'Назад';
static const refreshButtonText = 'Обновить';

static const labelTemperature = 'Температура:';
static const labelHumidity = 'Влажность:';
static const labelWindSpeed = 'Скорость ветра:';
static const labelActualForecast = 'Прогноз актуален на:';
static const labelGetWeather = 'Узнать погоду в городе:';
static const labelConfirmButton = 'Подтвердить';
static const labelChooseAnother = 'Выбрать другой';
static const labelCurrentWeather = 'Текущая погода';
static const labelCity = 'Город';
static const labelForecastFull = 'Прогноз погоды';
static const labelForecastShort = 'Прогноз';

static const labelWindUnits = 'м/с';
}
16 changes: 16 additions & 0 deletions lib/app/app_theme.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';

//класс для управления темой приложения
abstract class AppTheme {
AppTheme._();

static final lightTheme = ThemeData(
primarySwatch: Colors.purple,
snackBarTheme: const SnackBarThemeData(
shape: StadiumBorder(),
backgroundColor: Colors.purple,
behavior: SnackBarBehavior.floating,
actionTextColor: Colors.white,
),
);
}
10 changes: 10 additions & 0 deletions lib/app/app_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:intl/intl.dart';

//утилиты для использования на всех страницах
String formatDateTime(DateTime? dateTime) {
if (dateTime != null) {
return DateFormat("dd-MM-yyyy HH:mm").format(dateTime);
} else {
return '-';
}
}
9 changes: 8 additions & 1 deletion lib/data/dto/current_weather/current_weather_dto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ class CurrentWeatherDTO with _$CurrentWeatherDTO {
_$CurrentWeatherDTOFromJson(json);
}

//маппер данных
//преобразование форматов времени
extension CurrentWeatherMapper on CurrentWeatherDTO {
CurrentWeatherEntity toEntity() {
final time = dt?.toInt();
DateTime? convertedTime;
if (time != null) {
convertedTime = DateTime.fromMillisecondsSinceEpoch(time * 1000);
}
return CurrentWeatherEntity(
coord: coord?.toEntity(),
weather: weather?.map((e) => e.toEntity()).toList(),
Expand All @@ -44,7 +51,7 @@ extension CurrentWeatherMapper on CurrentWeatherDTO {
visibility: visibility,
wind: wind?.toEntity(),
clouds: clouds?.toEntity(),
dt: DateTime.fromMillisecondsSinceEpoch(dt?.toInt() ?? 0),
dt: convertedTime,
sys: sys?.toEntity(),
timezone: timezone,
id: id,
Expand Down
9 changes: 8 additions & 1 deletion lib/data/dto/forecast_weather/forecast_part_dto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ class ForecastPartDTO with _$ForecastPartDTO {
_$ForecastPartDTOFromJson(json);
}

//маппер данных
//реализация преобразования даты
extension ForecastPartMapper on ForecastPartDTO {
ForecastPartEntity toEntity() {
final time = dt?.toInt();
DateTime? convertedTime;
if (time != null) {
convertedTime = DateTime.fromMillisecondsSinceEpoch(time * 1000);
}
return ForecastPartEntity(
clouds: clouds?.toEntity(),
dt: DateTime.fromMillisecondsSinceEpoch(dt?.toInt() ?? 0),
dt: convertedTime,
dtTxt: dtTxt,
main: main?.toEntity(),
pop: pop,
Expand Down
19 changes: 9 additions & 10 deletions lib/domain/bloc/current_weather/current_weather_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ class CurrentWeatherCubit extends Cubit<CurrentWeatherState> {
//и формирование соответсвующего текста ошибки
//если ошибка какая-то другая - текст ошибки устанавливается другим
//оба случая вырасывают новое состояние с параметром текста ошибки
await currentWeatherRepository.getCurrentWeather(cityName: cityName).then(
(weatherEntity) =>
emit(CurrentWeatherState(weatherEntity: weatherEntity)))
// .catchError((error) {
// emit(CurrentWeatherState(errorMessage: 'Такой город не найден'));
// }, test: (error) => error is CityNotFoundException).catchError((error) {
// print(error.toString());
// emit(CurrentWeatherState(errorMessage: 'Ошибка получения данных'));
// })
;
await currentWeatherRepository
.getCurrentWeather(cityName: cityName)
.then((weatherEntity) =>
emit(CurrentWeatherState(weatherEntity: weatherEntity)))
.catchError((error) {
emit(CurrentWeatherState(errorMessage: 'Такой город не найден'));
}, test: (error) => error is CityNotFoundException).catchError((error) {
emit(CurrentWeatherState(errorMessage: 'Ошибка получения данных'));
});
}
}
2 changes: 0 additions & 2 deletions lib/domain/bloc/forecast_weather/forecast_weather_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:io';

import 'package:bloc/bloc.dart';
import 'package:friflex_weather_app/app/app_exception.dart';
import 'package:friflex_weather_app/domain/entities/forecast_weather/forecast_part_entity.dart';
Expand Down
6 changes: 3 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_weather_app/app/app_bloc_observer.dart';
import 'package:friflex_weather_app/app/app_const.dart';
import 'package:friflex_weather_app/app/app_theme.dart';
import 'package:friflex_weather_app/di.dart';
import 'package:friflex_weather_app/domain/bloc/app_settings/app_settings_cubit.dart';
import 'package:friflex_weather_app/domain/bloc/current_weather/current_weather_cubit.dart';
Expand Down Expand Up @@ -53,6 +54,7 @@ class FriflexWeatherApp extends StatelessWidget {
const FriflexWeatherApp({Key? key, required this.initialRoute})
: super(key: key);

//поле для передачи значения стартового маршрута
final String initialRoute;

@override
Expand All @@ -68,9 +70,7 @@ class FriflexWeatherApp extends StatelessWidget {
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: AppConst.appName,
theme: ThemeData(
primarySwatch: Colors.purple,
),
theme: AppTheme.lightTheme,
//установка initialRoute, полученного в начале
initialRoute: initialRoute,
//именованные маршруты для навигации
Expand Down
15 changes: 9 additions & 6 deletions lib/presentation/screens/city_input_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_weather_app/app/app_const.dart';
import 'package:friflex_weather_app/app/app_strings.dart';
import 'package:friflex_weather_app/app/app_text_styles.dart';
import 'package:friflex_weather_app/domain/bloc/app_settings/app_settings_cubit.dart';
import 'package:friflex_weather_app/domain/bloc/internet_connection/connected_bloc.dart';
Expand Down Expand Up @@ -34,6 +35,7 @@ class CityInputTextField extends StatefulWidget {
class _CityInputTextFieldState extends State<CityInputTextField> {
//контроллер для доступа к текстовому полю
final TextEditingController cityNameController = TextEditingController();

//текст ошибки поля для ввода
String errorText = '';

Expand All @@ -51,9 +53,9 @@ class _CityInputTextFieldState extends State<CityInputTextField> {
if (value.length > 1) {
errorText = '';
} else if (value.length == 1) {
errorText = 'Слишком короткое название';
errorText = AppStrings.errorShortName;
} else {
errorText = 'Необходимо ввести название города';
errorText = AppStrings.errorNoCityName;
}
//обновление состояния
setState(() {});
Expand All @@ -72,11 +74,12 @@ class _CityInputTextFieldState extends State<CityInputTextField> {
} else {
//демонстрация уведомления в случае отсутствия интернета
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Ошибка. Проверьте доступ к сети интернет.')));
content: Text(AppStrings.errorConnection),
));
}
} else {
//установка текста ошибки
errorText = 'Необходимо ввести название города';
errorText = AppStrings.errorNoCityName;
//обновление состояния
setState(() {});
}
Expand All @@ -91,7 +94,7 @@ class _CityInputTextFieldState extends State<CityInputTextField> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Узнать погоду в городе:',
AppStrings.labelGetWeather,
style: AppTextStyle.titleTextTextStyle,
),
//отступ со всех сторон
Expand Down Expand Up @@ -128,7 +131,7 @@ class _CityInputTextFieldState extends State<CityInputTextField> {
//кнопка для перехода на следующий экран
OutlinedButton(
onPressed: () => navigateToWeatherScreen(context),
child: const Text('Подтвердить'),
child: const Text(AppStrings.labelConfirmButton),
)
],
));
Expand Down
Loading

0 comments on commit 2a1d91d

Please sign in to comment.