mirror of
https://github.com/darkmoonight/Rain.git
synced 2025-06-28 12:09:57 +00:00
Refactor code
This commit is contained in:
parent
33be8dcdc6
commit
9ba80c3609
69 changed files with 4478 additions and 4113 deletions
0
lib/app/api/api.dart
Normal file → Executable file
0
lib/app/api/api.dart
Normal file → Executable file
0
lib/app/api/city_api.dart
Normal file → Executable file
0
lib/app/api/city_api.dart
Normal file → Executable file
0
lib/app/api/weather_api.dart
Normal file → Executable file
0
lib/app/api/weather_api.dart
Normal file → Executable file
0
lib/app/api/weather_api.freezed.dart
Normal file → Executable file
0
lib/app/api/weather_api.freezed.dart
Normal file → Executable file
0
lib/app/api/weather_api.g.dart
Normal file → Executable file
0
lib/app/api/weather_api.g.dart
Normal file → Executable file
93
lib/app/controller/controller.dart
Normal file → Executable file
93
lib/app/controller/controller.dart
Normal file → Executable file
|
@ -134,7 +134,7 @@ class WeatherController extends GetxController {
|
|||
await readCache();
|
||||
}
|
||||
|
||||
Future<Map> getCurrentLocationSearch() async {
|
||||
Future<Map<String, dynamic>> getCurrentLocationSearch() async {
|
||||
if (!(await isOnline.value)) {
|
||||
showSnackBar(content: 'no_inter'.tr);
|
||||
}
|
||||
|
@ -313,8 +313,7 @@ class WeatherController extends GetxController {
|
|||
}
|
||||
|
||||
Future<void> updateCacheCard(bool refresh) async {
|
||||
final weatherCard =
|
||||
refresh
|
||||
final weatherCard = refresh
|
||||
? isar.weatherCards.where().sortByIndex().findAllSync()
|
||||
: isar.weatherCards
|
||||
.filter()
|
||||
|
@ -335,68 +334,14 @@ class WeatherController extends GetxController {
|
|||
oldCard.timezone!,
|
||||
);
|
||||
isar.writeTxnSync(() {
|
||||
oldCard
|
||||
..time = updatedCard.time
|
||||
..weathercode = updatedCard.weathercode
|
||||
..temperature2M = updatedCard.temperature2M
|
||||
..apparentTemperature = updatedCard.apparentTemperature
|
||||
..relativehumidity2M = updatedCard.relativehumidity2M
|
||||
..precipitation = updatedCard.precipitation
|
||||
..rain = updatedCard.rain
|
||||
..surfacePressure = updatedCard.surfacePressure
|
||||
..visibility = updatedCard.visibility
|
||||
..evapotranspiration = updatedCard.evapotranspiration
|
||||
..windspeed10M = updatedCard.windspeed10M
|
||||
..winddirection10M = updatedCard.winddirection10M
|
||||
..windgusts10M = updatedCard.windgusts10M
|
||||
..cloudcover = updatedCard.cloudcover
|
||||
..uvIndex = updatedCard.uvIndex
|
||||
..dewpoint2M = updatedCard.dewpoint2M
|
||||
..precipitationProbability = updatedCard.precipitationProbability
|
||||
..shortwaveRadiation = updatedCard.shortwaveRadiation
|
||||
..timeDaily = updatedCard.timeDaily
|
||||
..weathercodeDaily = updatedCard.weathercodeDaily
|
||||
..temperature2MMax = updatedCard.temperature2MMax
|
||||
..temperature2MMin = updatedCard.temperature2MMin
|
||||
..apparentTemperatureMax = updatedCard.apparentTemperatureMax
|
||||
..apparentTemperatureMin = updatedCard.apparentTemperatureMin
|
||||
..sunrise = updatedCard.sunrise
|
||||
..sunset = updatedCard.sunset
|
||||
..precipitationSum = updatedCard.precipitationSum
|
||||
..precipitationProbabilityMax =
|
||||
updatedCard.precipitationProbabilityMax
|
||||
..windspeed10MMax = updatedCard.windspeed10MMax
|
||||
..windgusts10MMax = updatedCard.windgusts10MMax
|
||||
..uvIndexMax = updatedCard.uvIndexMax
|
||||
..rainSum = updatedCard.rainSum
|
||||
..winddirection10MDominant = updatedCard.winddirection10MDominant
|
||||
..timestamp = DateTime.now();
|
||||
|
||||
isar.weatherCards.putSync(oldCard);
|
||||
|
||||
final newCard = oldCard;
|
||||
final oldIdx = weatherCard.indexOf(oldCard);
|
||||
weatherCards[oldIdx] = newCard;
|
||||
_updateWeatherCard(oldCard, updatedCard);
|
||||
weatherCards.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateCard(WeatherCard weatherCard) async {
|
||||
if (!(await isOnline.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final updatedCard = await WeatherAPI().getWeatherCard(
|
||||
weatherCard.lat!,
|
||||
weatherCard.lon!,
|
||||
weatherCard.city!,
|
||||
weatherCard.district!,
|
||||
weatherCard.timezone!,
|
||||
);
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
weatherCard
|
||||
void _updateWeatherCard(WeatherCard oldCard, WeatherCard updatedCard) {
|
||||
oldCard
|
||||
..time = updatedCard.time
|
||||
..weathercode = updatedCard.weathercode
|
||||
..temperature2M = updatedCard.temperature2M
|
||||
|
@ -432,7 +377,24 @@ class WeatherController extends GetxController {
|
|||
..winddirection10MDominant = updatedCard.winddirection10MDominant
|
||||
..timestamp = DateTime.now();
|
||||
|
||||
isar.weatherCards.putSync(weatherCard);
|
||||
isar.weatherCards.putSync(oldCard);
|
||||
}
|
||||
|
||||
Future<void> updateCard(WeatherCard weatherCard) async {
|
||||
if (!(await isOnline.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final updatedCard = await WeatherAPI().getWeatherCard(
|
||||
weatherCard.lat!,
|
||||
weatherCard.lon!,
|
||||
weatherCard.city!,
|
||||
weatherCard.district!,
|
||||
weatherCard.timezone!,
|
||||
);
|
||||
|
||||
isar.writeTxnSync(() {
|
||||
_updateWeatherCard(weatherCard, updatedCard);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -512,8 +474,8 @@ class WeatherController extends GetxController {
|
|||
|
||||
void notificationCheck() async {
|
||||
if (settings.notifications) {
|
||||
final pendingNotificationRequests =
|
||||
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
|
||||
final pendingNotificationRequests = await flutterLocalNotificationsPlugin
|
||||
.pendingNotificationRequests();
|
||||
if (pendingNotificationRequests.isEmpty) {
|
||||
notification(_mainWeather.value);
|
||||
}
|
||||
|
@ -572,8 +534,9 @@ class WeatherController extends GetxController {
|
|||
WeatherCardSchema,
|
||||
], directory: (await getApplicationSupportDirectory()).path);
|
||||
|
||||
final mainWeatherCache =
|
||||
isarWidget.mainWeatherCaches.where().findFirstSync();
|
||||
final mainWeatherCache = isarWidget.mainWeatherCaches
|
||||
.where()
|
||||
.findFirstSync();
|
||||
if (mainWeatherCache == null) return false;
|
||||
|
||||
final hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
|
||||
|
|
0
lib/app/data/db.dart
Normal file → Executable file
0
lib/app/data/db.dart
Normal file → Executable file
0
lib/app/data/db.g.dart
Normal file → Executable file
0
lib/app/data/db.g.dart
Normal file → Executable file
732
lib/app/ui/geolocation.dart
Normal file → Executable file
732
lib/app/ui/geolocation.dart
Normal file → Executable file
|
@ -33,6 +33,8 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
final _controllerCity = TextEditingController();
|
||||
final _controllerDistrict = TextEditingController();
|
||||
|
||||
static const kTextFieldElevation = 4.0;
|
||||
|
||||
static const colorFilter = ColorFilter.matrix(<double>[
|
||||
-0.2, -0.7, -0.08, 0, 255, // Red channel
|
||||
-0.2, -0.7, -0.08, 0, 255, // Green channel
|
||||
|
@ -41,17 +43,16 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
]);
|
||||
|
||||
final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
|
||||
|
||||
final mapController = MapController();
|
||||
|
||||
textTrim(value) {
|
||||
void textTrim(TextEditingController value) {
|
||||
value.text = value.text.trim();
|
||||
while (value.text.contains(' ')) {
|
||||
value.text = value.text.replaceAll(' ', ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void fillController(selection) {
|
||||
void fillController(Result selection) {
|
||||
_controllerLat.text = '${selection.latitude}';
|
||||
_controllerLon.text = '${selection.longitude}';
|
||||
_controllerCity.text = selection.name;
|
||||
|
@ -61,7 +62,7 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
void fillControllerGeo(location) {
|
||||
void fillControllerGeo(Map<String, dynamic> location) {
|
||||
_controllerLat.text = '${location['lat']}';
|
||||
_controllerLon.text = '${location['lon']}';
|
||||
_controllerCity.text = location['district'];
|
||||
|
@ -82,35 +83,310 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildMap() {
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
child: SizedBox(
|
||||
height: Get.size.height * 0.3,
|
||||
child: FlutterMap(
|
||||
mapController: mapController,
|
||||
options: MapOptions(
|
||||
backgroundColor: context.theme.colorScheme.surface,
|
||||
initialCenter: const LatLng(55.7522, 37.6156),
|
||||
initialZoom: 3,
|
||||
interactionOptions: const InteractionOptions(
|
||||
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
|
||||
),
|
||||
cameraConstraint: CameraConstraint.contain(
|
||||
bounds: LatLngBounds(
|
||||
const LatLng(-90, -180),
|
||||
const LatLng(90, 180),
|
||||
),
|
||||
),
|
||||
onLongPress: (tapPosition, point) =>
|
||||
fillMap(point.latitude, point.longitude),
|
||||
),
|
||||
children: [
|
||||
if (_isDarkMode)
|
||||
ColorFiltered(
|
||||
colorFilter: colorFilter,
|
||||
child: _buildMapTileLayer(),
|
||||
)
|
||||
else
|
||||
_buildMapTileLayer(),
|
||||
RichAttributionWidget(
|
||||
animationConfig: const ScaleRAWA(),
|
||||
attributions: [
|
||||
TextSourceAttribution(
|
||||
'OpenStreetMap contributors',
|
||||
onTap: () => weatherController.urlLauncher(
|
||||
'https://openstreetmap.org/copyright',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchField() {
|
||||
return RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controller,
|
||||
fieldViewBuilder:
|
||||
(
|
||||
BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
labelText: 'search'.tr,
|
||||
type: TextInputType.text,
|
||||
icon: const Icon(IconsaxPlusLinear.global_search),
|
||||
controller: _controller,
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
focusNode: _focusNode,
|
||||
);
|
||||
},
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<Result>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||
},
|
||||
onSelected: (Result selection) => fillController(selection),
|
||||
displayStringForOption: (Result option) =>
|
||||
'${option.name}, ${option.admin1}',
|
||||
optionsViewBuilder:
|
||||
(
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return _buildOptionsView(context, onSelected, options);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionsView(
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
elevation: 4.0,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Result option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'${option.name}, ${option.admin1}',
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationButton() {
|
||||
return Card(
|
||||
elevation: kTextFieldElevation,
|
||||
margin: const EdgeInsets.only(top: 10, right: 10),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(2.5),
|
||||
child: IconButton(
|
||||
onPressed: _handleLocationButtonPress,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleLocationButtonPress() async {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
if (!context.mounted) return;
|
||||
await _showLocationDialog();
|
||||
return;
|
||||
}
|
||||
setState(() => isLoading = true);
|
||||
final location = await weatherController.getCurrentLocationSearch();
|
||||
fillControllerGeo(location);
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
|
||||
Future<void> _showLocationDialog() async {
|
||||
await showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog.adaptive(
|
||||
title: Text('location'.tr, style: context.textTheme.titleLarge),
|
||||
content: Text('no_location'.tr, style: context.textTheme.titleMedium),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: false),
|
||||
child: Text(
|
||||
'cancel'.tr,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Geolocator.openLocationSettings();
|
||||
Get.back(result: true);
|
||||
},
|
||||
child: Text(
|
||||
'settings'.tr,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLatitudeField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLat,
|
||||
labelText: 'lat'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateLatitude(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLongitudeField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLon,
|
||||
labelText: 'lon'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateLongitude(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCityField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerCity,
|
||||
labelText: 'city'.tr,
|
||||
type: TextInputType.name,
|
||||
icon: const Icon(IconsaxPlusLinear.building_3),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateCity(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDistrictField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerDistrict,
|
||||
labelText: 'district'.tr,
|
||||
type: TextInputType.streetAddress,
|
||||
icon: const Icon(IconsaxPlusLinear.global),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubmitButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
|
||||
);
|
||||
}
|
||||
|
||||
String? _validateLatitude(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
double? numericValue = double.tryParse(value);
|
||||
if (numericValue == null) {
|
||||
return 'validateNumber'.tr;
|
||||
}
|
||||
if (numericValue < -90 || numericValue > 90) {
|
||||
return 'validate90'.tr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validateLongitude(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
double? numericValue = double.tryParse(value);
|
||||
if (numericValue == null) {
|
||||
return 'validateNumber'.tr;
|
||||
}
|
||||
if (numericValue < -180 || numericValue > 180) {
|
||||
return 'validate180'.tr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _validateCity(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateName'.tr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _handleSubmit() async {
|
||||
if (formKeySearch.currentState!.validate()) {
|
||||
textTrim(_controllerLat);
|
||||
textTrim(_controllerLon);
|
||||
textTrim(_controllerCity);
|
||||
textTrim(_controllerDistrict);
|
||||
try {
|
||||
await weatherController.deleteAll(true);
|
||||
await weatherController.getLocation(
|
||||
double.parse(_controllerLat.text),
|
||||
double.parse(_controllerLon.text),
|
||||
_controllerDistrict.text,
|
||||
_controllerCity.text,
|
||||
);
|
||||
widget.isStart
|
||||
? Get.off(() => const HomePage(), transition: Transition.downToUp)
|
||||
: Get.back();
|
||||
} catch (error) {
|
||||
Future.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const kTextFieldElevation = 4.0;
|
||||
return Form(
|
||||
key: formKeySearch,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
leading:
|
||||
widget.isStart
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(
|
||||
'searchCity'.tr,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
appBar: _buildAppBar(),
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
|
@ -128,66 +404,7 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: Get.size.height * 0.3,
|
||||
child: FlutterMap(
|
||||
mapController: mapController,
|
||||
options: MapOptions(
|
||||
backgroundColor:
|
||||
context.theme.colorScheme.surface,
|
||||
initialCenter: const LatLng(
|
||||
55.7522,
|
||||
37.6156,
|
||||
),
|
||||
initialZoom: 3,
|
||||
interactionOptions:
|
||||
const InteractionOptions(
|
||||
flags:
|
||||
InteractiveFlag.all &
|
||||
~InteractiveFlag.rotate,
|
||||
),
|
||||
cameraConstraint:
|
||||
CameraConstraint.contain(
|
||||
bounds: LatLngBounds(
|
||||
const LatLng(-90, -180),
|
||||
const LatLng(90, 180),
|
||||
),
|
||||
),
|
||||
onLongPress:
|
||||
(tapPosition, point) => fillMap(
|
||||
point.latitude,
|
||||
point.longitude,
|
||||
),
|
||||
),
|
||||
children: [
|
||||
if (_isDarkMode)
|
||||
ColorFiltered(
|
||||
colorFilter: colorFilter,
|
||||
child: _buildMapTileLayer(),
|
||||
)
|
||||
else
|
||||
_buildMapTileLayer(),
|
||||
RichAttributionWidget(
|
||||
animationConfig: const ScaleRAWA(),
|
||||
attributions: [
|
||||
TextSourceAttribution(
|
||||
'OpenStreetMap contributors',
|
||||
onTap:
|
||||
() => weatherController
|
||||
.urlLauncher(
|
||||
'https://openstreetmap.org/copyright',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _buildMap(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
|
@ -205,284 +422,14 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controller,
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController
|
||||
fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
labelText: 'search'.tr,
|
||||
type: TextInputType.text,
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.global_search,
|
||||
),
|
||||
controller: _controller,
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
focusNode: _focusNode,
|
||||
);
|
||||
},
|
||||
optionsBuilder: (
|
||||
TextEditingValue textEditingValue,
|
||||
) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<
|
||||
Result
|
||||
>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(
|
||||
textEditingValue.text,
|
||||
locale,
|
||||
);
|
||||
},
|
||||
onSelected:
|
||||
(Result selection) =>
|
||||
fillController(selection),
|
||||
displayStringForOption:
|
||||
(Result option) =>
|
||||
'${option.name}, ${option.admin1}',
|
||||
optionsViewBuilder: (
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result>
|
||||
onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(20),
|
||||
elevation: 4.0,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (
|
||||
BuildContext context,
|
||||
int index,
|
||||
) {
|
||||
final Result option =
|
||||
options.elementAt(
|
||||
index,
|
||||
);
|
||||
return InkWell(
|
||||
onTap:
|
||||
() => onSelected(
|
||||
option,
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'${option.name}, ${option.admin1}',
|
||||
style:
|
||||
context
|
||||
.textTheme
|
||||
.labelLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Card(
|
||||
elevation: kTextFieldElevation,
|
||||
margin: const EdgeInsets.only(
|
||||
top: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(2.5),
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
bool serviceEnabled =
|
||||
await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
if (!context.mounted) return;
|
||||
await showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
) {
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(
|
||||
'location'.tr,
|
||||
style:
|
||||
context
|
||||
.textTheme
|
||||
.titleLarge,
|
||||
),
|
||||
content: Text(
|
||||
'no_location'.tr,
|
||||
style:
|
||||
context
|
||||
.textTheme
|
||||
.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
() => Get.back(
|
||||
result: false,
|
||||
),
|
||||
child: Text(
|
||||
'cancel'.tr,
|
||||
style: context
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
color:
|
||||
Colors
|
||||
.blueAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Geolocator.openLocationSettings();
|
||||
Get.back(
|
||||
result: true,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'settings'.tr,
|
||||
style: context
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
color:
|
||||
Colors
|
||||
.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
setState(() => isLoading = true);
|
||||
final location =
|
||||
await weatherController
|
||||
.getCurrentLocationSearch();
|
||||
fillControllerGeo(location);
|
||||
setState(() => isLoading = false);
|
||||
},
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.location,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(child: _buildSearchField()),
|
||||
_buildLocationButton(),
|
||||
],
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLat,
|
||||
labelText: 'lat'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
double? numericValue = double.tryParse(
|
||||
value,
|
||||
);
|
||||
if (numericValue == null) {
|
||||
return 'validateNumber'.tr;
|
||||
}
|
||||
if (numericValue < -90 ||
|
||||
numericValue > 90) {
|
||||
return 'validate90'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLon,
|
||||
labelText: 'lon'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
double? numericValue = double.tryParse(
|
||||
value,
|
||||
);
|
||||
if (numericValue == null) {
|
||||
return 'validateNumber'.tr;
|
||||
}
|
||||
if (numericValue < -180 ||
|
||||
numericValue > 180) {
|
||||
return 'validate180'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerCity,
|
||||
labelText: 'city'.tr,
|
||||
type: TextInputType.name,
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.building_3,
|
||||
),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateName'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerDistrict,
|
||||
labelText: 'district'.tr,
|
||||
type: TextInputType.streetAddress,
|
||||
icon: const Icon(IconsaxPlusLinear.global),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
),
|
||||
_buildLatitudeField(),
|
||||
_buildLongitudeField(),
|
||||
_buildCityField(),
|
||||
_buildDistrictField(),
|
||||
const Gap(20),
|
||||
],
|
||||
),
|
||||
|
@ -491,40 +438,7 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
child: MyTextButton(
|
||||
buttonName: 'done'.tr,
|
||||
onPressed: () async {
|
||||
if (formKeySearch.currentState!.validate()) {
|
||||
textTrim(_controllerLat);
|
||||
textTrim(_controllerLon);
|
||||
textTrim(_controllerCity);
|
||||
textTrim(_controllerDistrict);
|
||||
try {
|
||||
await weatherController.deleteAll(true);
|
||||
await weatherController.getLocation(
|
||||
double.parse(_controllerLat.text),
|
||||
double.parse(_controllerLon.text),
|
||||
_controllerDistrict.text,
|
||||
_controllerCity.text,
|
||||
);
|
||||
widget.isStart
|
||||
? Get.off(
|
||||
() => const HomePage(),
|
||||
transition: Transition.downToUp,
|
||||
)
|
||||
: Get.back();
|
||||
} catch (error) {
|
||||
Future.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildSubmitButton(),
|
||||
],
|
||||
),
|
||||
if (isLoading)
|
||||
|
@ -538,4 +452,28 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar _buildAppBar() {
|
||||
return AppBar(
|
||||
centerTitle: true,
|
||||
leading: widget.isStart
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(
|
||||
'searchCity'.tr,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
175
lib/app/ui/home.dart
Normal file → Executable file
175
lib/app/ui/home.dart
Normal file → Executable file
|
@ -82,40 +82,51 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
tabController.animateTo(tabIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = context.textTheme;
|
||||
final labelLarge = textTheme.labelLarge;
|
||||
|
||||
final textStyle = textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
Widget _buildAppBarTitle(
|
||||
int tabIndex,
|
||||
TextStyle? textStyle,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
switch (tabIndex) {
|
||||
case 0:
|
||||
return visible
|
||||
? _buildSearchField(labelLarge)
|
||||
: Obx(() {
|
||||
final location = weatherController.location;
|
||||
final city = location.city;
|
||||
final district = location.district;
|
||||
return Text(
|
||||
weatherController.isLoading.isFalse
|
||||
? district!.isEmpty
|
||||
? '$city'
|
||||
: city!.isEmpty
|
||||
? district
|
||||
: city == district
|
||||
? city
|
||||
: '$city, $district'
|
||||
: settings.location
|
||||
? 'search'.tr
|
||||
: (isar.locationCaches.where().findAllSync()).isNotEmpty
|
||||
? 'loading'.tr
|
||||
: 'searchCity'.tr,
|
||||
style: textStyle,
|
||||
);
|
||||
});
|
||||
case 1:
|
||||
return Text('cities'.tr, style: textStyle);
|
||||
case 2:
|
||||
return settings.hideMap
|
||||
? Text('settings_full'.tr, style: textStyle)
|
||||
: Text('map'.tr, style: textStyle);
|
||||
case 3:
|
||||
return Text('settings_full'.tr, style: textStyle);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultTabController(
|
||||
length: pages.length,
|
||||
child: ScaffoldMessenger(
|
||||
key: globalKey,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: switch (tabIndex) {
|
||||
0 => IconButton(
|
||||
onPressed: () {
|
||||
Get.to(
|
||||
() => const SelectGeolocation(isStart: false),
|
||||
transition: Transition.downToUp,
|
||||
);
|
||||
},
|
||||
icon: const Icon(IconsaxPlusLinear.global_search, size: 18),
|
||||
),
|
||||
int() => null,
|
||||
},
|
||||
title: switch (tabIndex) {
|
||||
0 =>
|
||||
visible
|
||||
? RawAutocomplete<Result>(
|
||||
Widget _buildSearchField(TextStyle? labelLarge) {
|
||||
return RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controller,
|
||||
fieldViewBuilder: (_, __, ___, ____) {
|
||||
|
@ -130,10 +141,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<Result>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(
|
||||
textEditingValue.text,
|
||||
locale,
|
||||
);
|
||||
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||
},
|
||||
onSelected: (Result selection) async {
|
||||
await weatherController.deleteAll(true);
|
||||
|
@ -154,6 +162,17 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return _buildOptionsView(context, onSelected, options, labelLarge);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionsView(
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
|
@ -167,9 +186,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Result option = options.elementAt(
|
||||
index,
|
||||
);
|
||||
final Result option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: ListTile(
|
||||
|
@ -184,42 +201,10 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Obx(() {
|
||||
final location = weatherController.location;
|
||||
final city = location.city;
|
||||
final district = location.district;
|
||||
return Text(
|
||||
weatherController.isLoading.isFalse
|
||||
? district!.isEmpty
|
||||
? '$city'
|
||||
: city!.isEmpty
|
||||
? district
|
||||
: city == district
|
||||
? city
|
||||
: '$city'
|
||||
', $district'
|
||||
: settings.location
|
||||
? 'search'.tr
|
||||
: (isar.locationCaches.where().findAllSync())
|
||||
.isNotEmpty
|
||||
? 'loading'.tr
|
||||
: 'searchCity'.tr,
|
||||
style: textStyle,
|
||||
);
|
||||
}),
|
||||
1 => Text('cities'.tr, style: textStyle),
|
||||
2 =>
|
||||
settings.hideMap
|
||||
? Text('settings_full'.tr, style: textStyle)
|
||||
: Text('map'.tr, style: textStyle),
|
||||
3 => Text('settings_full'.tr, style: textStyle),
|
||||
int() => null,
|
||||
},
|
||||
actions: switch (tabIndex) {
|
||||
0 => [
|
||||
IconButton(
|
||||
}
|
||||
|
||||
Widget _buildSearchIconButton() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (visible) {
|
||||
_controller.clear();
|
||||
|
@ -236,10 +221,44 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||
: IconsaxPlusLinear.search_normal_1,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
int() => null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = context.textTheme;
|
||||
final labelLarge = textTheme.labelLarge;
|
||||
|
||||
final textStyle = textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
);
|
||||
|
||||
return DefaultTabController(
|
||||
length: pages.length,
|
||||
child: ScaffoldMessenger(
|
||||
key: globalKey,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading:
|
||||
tabIndex == 0
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
Get.to(
|
||||
() => const SelectGeolocation(isStart: false),
|
||||
transition: Transition.downToUp,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.global_search,
|
||||
size: 18,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: _buildAppBarTitle(tabIndex, textStyle, labelLarge),
|
||||
actions: tabIndex == 0 ? [_buildSearchIconButton()] : null,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: TabBarView(controller: tabController, children: pages),
|
||||
|
|
196
lib/app/ui/main/view/main.dart
Normal file → Executable file
196
lib/app/ui/main/view/main.dart
Normal file → Executable file
|
@ -24,36 +24,12 @@ class _MainPageState extends State<MainPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await weatherController.deleteAll(false);
|
||||
await weatherController.setLocation();
|
||||
setState(() {});
|
||||
},
|
||||
onRefresh: _handleRefresh,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Obx(() {
|
||||
if (weatherController.isLoading.isTrue) {
|
||||
return ListView(
|
||||
children: const [
|
||||
MyShimmer(hight: 200),
|
||||
MyShimmer(
|
||||
hight: 130,
|
||||
edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15),
|
||||
),
|
||||
MyShimmer(
|
||||
hight: 90,
|
||||
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
|
||||
),
|
||||
MyShimmer(
|
||||
hight: 400,
|
||||
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
|
||||
),
|
||||
MyShimmer(
|
||||
hight: 450,
|
||||
edgeInsetsMargin: EdgeInsets.only(bottom: 15),
|
||||
),
|
||||
],
|
||||
);
|
||||
return _buildLoadingView();
|
||||
}
|
||||
|
||||
final mainWeather = weatherController.mainWeather;
|
||||
|
@ -65,25 +41,101 @@ class _MainPageState extends State<MainPage> {
|
|||
final tempMax = mainWeather.temperature2MMax![dayOfNow];
|
||||
final tempMin = mainWeather.temperature2MMin![dayOfNow];
|
||||
|
||||
return _buildMainView(
|
||||
context,
|
||||
mainWeather,
|
||||
weatherCard,
|
||||
hourOfDay,
|
||||
dayOfNow,
|
||||
sunrise,
|
||||
sunset,
|
||||
tempMax!,
|
||||
tempMin!,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRefresh() async {
|
||||
await weatherController.deleteAll(false);
|
||||
await weatherController.setLocation();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Widget _buildLoadingView() {
|
||||
return ListView(
|
||||
children: const [
|
||||
MyShimmer(height: 200),
|
||||
MyShimmer(height: 130, margin: EdgeInsets.symmetric(vertical: 15)),
|
||||
MyShimmer(height: 90, margin: EdgeInsets.only(bottom: 15)),
|
||||
MyShimmer(height: 400, margin: EdgeInsets.only(bottom: 15)),
|
||||
MyShimmer(height: 450, margin: EdgeInsets.only(bottom: 15)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMainView(
|
||||
BuildContext context,
|
||||
MainWeatherCache mainWeather,
|
||||
WeatherCard weatherCard,
|
||||
int hourOfDay,
|
||||
int dayOfNow,
|
||||
String sunrise,
|
||||
String sunset,
|
||||
double tempMax,
|
||||
double tempMin,
|
||||
) {
|
||||
return ListView(
|
||||
children: [
|
||||
Now(
|
||||
_buildNowWidget(
|
||||
mainWeather,
|
||||
hourOfDay,
|
||||
dayOfNow,
|
||||
sunrise,
|
||||
sunset,
|
||||
tempMax,
|
||||
tempMin,
|
||||
),
|
||||
_buildHourlyList(context, mainWeather, hourOfDay, dayOfNow),
|
||||
_buildSunsetSunriseWidget(sunrise, sunset),
|
||||
_buildHourlyDescContainer(mainWeather, hourOfDay),
|
||||
_buildDailyContainer(weatherCard),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNowWidget(
|
||||
MainWeatherCache mainWeather,
|
||||
int hourOfDay,
|
||||
int dayOfNow,
|
||||
String sunrise,
|
||||
String sunset,
|
||||
double tempMax,
|
||||
double tempMin,
|
||||
) {
|
||||
return Now(
|
||||
time: mainWeather.time![hourOfDay],
|
||||
weather: mainWeather.weathercode![hourOfDay],
|
||||
degree: mainWeather.temperature2M![hourOfDay],
|
||||
feels: mainWeather.apparentTemperature![hourOfDay]!,
|
||||
timeDay: sunrise,
|
||||
timeNight: sunset,
|
||||
tempMax: tempMax!,
|
||||
tempMin: tempMin!,
|
||||
),
|
||||
Card(
|
||||
tempMax: tempMax,
|
||||
tempMin: tempMin,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyList(
|
||||
BuildContext context,
|
||||
MainWeatherCache mainWeather,
|
||||
int hourOfDay,
|
||||
int dayOfNow,
|
||||
) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: SizedBox(
|
||||
height: 135,
|
||||
child: ScrollablePositionedList.separated(
|
||||
|
@ -96,10 +148,30 @@ class _MainPageState extends State<MainPage> {
|
|||
);
|
||||
},
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemScrollController:
|
||||
weatherController.itemScrollController,
|
||||
itemScrollController: weatherController.itemScrollController,
|
||||
itemCount: mainWeather.time!.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
return _buildHourlyItem(
|
||||
context,
|
||||
mainWeather,
|
||||
i,
|
||||
hourOfDay,
|
||||
dayOfNow,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyItem(
|
||||
BuildContext context,
|
||||
MainWeatherCache mainWeather,
|
||||
int i,
|
||||
int hourOfDay,
|
||||
int dayOfNow,
|
||||
) {
|
||||
final i24 = (i / 24).floor();
|
||||
|
||||
return GestureDetector(
|
||||
|
@ -110,21 +182,12 @@ class _MainPageState extends State<MainPage> {
|
|||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
i == hourOfDay
|
||||
? context
|
||||
.theme
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
color: i == hourOfDay
|
||||
? context.theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Hourly(
|
||||
time: mainWeather.time![i],
|
||||
|
@ -135,13 +198,17 @@ class _MainPageState extends State<MainPage> {
|
|||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset),
|
||||
DescContainer(
|
||||
}
|
||||
|
||||
Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
|
||||
return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
|
||||
}
|
||||
|
||||
Widget _buildHourlyDescContainer(
|
||||
MainWeatherCache mainWeather,
|
||||
int hourOfDay,
|
||||
) {
|
||||
return DescContainer(
|
||||
humidity: mainWeather.relativehumidity2M?[hourOfDay],
|
||||
wind: mainWeather.windspeed10M?[hourOfDay],
|
||||
visibility: mainWeather.visibility?[hourOfDay],
|
||||
|
@ -160,19 +227,16 @@ class _MainPageState extends State<MainPage> {
|
|||
shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
|
||||
initiallyExpanded: false,
|
||||
title: 'hourlyVariables'.tr,
|
||||
),
|
||||
DailyContainer(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyContainer(WeatherCard weatherCard) {
|
||||
return DailyContainer(
|
||||
weatherData: weatherCard,
|
||||
onTap:
|
||||
() => Get.to(
|
||||
onTap: () => Get.to(
|
||||
() => DailyCardList(weatherData: weatherCard),
|
||||
transition: Transition.downToUp,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
211
lib/app/ui/map/view/map.dart
Normal file → Executable file
211
lib/app/ui/map/view/map.dart
Normal file → Executable file
|
@ -113,7 +113,7 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
|||
_focusNode.unfocus();
|
||||
}
|
||||
|
||||
Widget _buidStyleMarkers(
|
||||
Widget _buildStyleMarkers(
|
||||
int weathercode,
|
||||
String time,
|
||||
String sunrise,
|
||||
|
@ -154,7 +154,7 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
|||
point: LatLng(weatherCard.lat!, weatherCard.lon!),
|
||||
child: GestureDetector(
|
||||
onTap: () => _onMarkerTap(weatherCard),
|
||||
child: _buidStyleMarkers(
|
||||
child: _buildStyleMarkers(
|
||||
weatherCard.weathercode![hourOfDay],
|
||||
weatherCard.time![hourOfDay],
|
||||
weatherCard.sunrise![dayOfNow],
|
||||
|
@ -166,33 +166,27 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
|||
}
|
||||
|
||||
Marker _buildCardMarker(WeatherCard weatherCardList) {
|
||||
final hourOfDay = weatherController.getTime(
|
||||
weatherCardList.time!,
|
||||
weatherCardList.timezone!,
|
||||
);
|
||||
final dayOfNow = weatherController.getDay(
|
||||
weatherCardList.timeDaily!,
|
||||
weatherCardList.timezone!,
|
||||
);
|
||||
|
||||
return Marker(
|
||||
height: 50,
|
||||
width: 100,
|
||||
point: LatLng(weatherCardList.lat!, weatherCardList.lon!),
|
||||
child: GestureDetector(
|
||||
onTap: () => _onMarkerTap(weatherCardList),
|
||||
child: _buidStyleMarkers(
|
||||
weatherCardList.weathercode![weatherController.getTime(
|
||||
weatherCardList.time!,
|
||||
weatherCardList.timezone!,
|
||||
)],
|
||||
weatherCardList.time![weatherController.getTime(
|
||||
weatherCardList.time!,
|
||||
weatherCardList.timezone!,
|
||||
)],
|
||||
weatherCardList.sunrise![weatherController.getDay(
|
||||
weatherCardList.timeDaily!,
|
||||
weatherCardList.timezone!,
|
||||
)],
|
||||
weatherCardList.sunset![weatherController.getDay(
|
||||
weatherCardList.timeDaily!,
|
||||
weatherCardList.timezone!,
|
||||
)],
|
||||
weatherCardList.temperature2M![weatherController.getTime(
|
||||
weatherCardList.time!,
|
||||
weatherCardList.timezone!,
|
||||
)],
|
||||
child: _buildStyleMarkers(
|
||||
weatherCardList.weathercode![hourOfDay],
|
||||
weatherCardList.time![hourOfDay],
|
||||
weatherCardList.sunrise![dayOfNow],
|
||||
weatherCardList.sunset![dayOfNow],
|
||||
weatherCardList.temperature2M![hourOfDay],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -235,6 +229,91 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
|||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _buildSearchField() {
|
||||
return RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controllerSearch,
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return MyTextForm(
|
||||
labelText: 'search'.tr,
|
||||
type: TextInputType.text,
|
||||
icon: const Icon(IconsaxPlusLinear.global_search),
|
||||
controller: _controllerSearch,
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
focusNode: _focusNode,
|
||||
onChanged: (value) => setState(() {}),
|
||||
iconButton:
|
||||
_controllerSearch.text.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_controllerSearch.clear();
|
||||
},
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.close_circle,
|
||||
color: Colors.grey,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<Result>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||
},
|
||||
onSelected: (Result selection) {
|
||||
_animatedMapController.mapController.move(
|
||||
LatLng(selection.latitude, selection.longitude),
|
||||
14,
|
||||
);
|
||||
_controllerSearch.clear();
|
||||
_focusNode.unfocus();
|
||||
},
|
||||
displayStringForOption:
|
||||
(Result option) => '${option.name}, ${option.admin1}',
|
||||
optionsViewBuilder: (
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
elevation: 4.0,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Result option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'${option.name}, ${option.admin1}',
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mainLocation = weatherController.location;
|
||||
|
@ -387,91 +466,7 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
|||
),
|
||||
],
|
||||
),
|
||||
RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controllerSearch,
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return MyTextForm(
|
||||
labelText: 'search'.tr,
|
||||
type: TextInputType.text,
|
||||
icon: const Icon(IconsaxPlusLinear.global_search),
|
||||
controller: _controllerSearch,
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
focusNode: _focusNode,
|
||||
onChanged: (value) => setState(() {}),
|
||||
iconButton:
|
||||
_controllerSearch.text.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_controllerSearch.clear();
|
||||
},
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.close_circle,
|
||||
color: Colors.grey,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<Result>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||
},
|
||||
onSelected: (Result selection) {
|
||||
_animatedMapController.mapController.move(
|
||||
LatLng(selection.latitude, selection.longitude),
|
||||
14,
|
||||
);
|
||||
_controllerSearch.clear();
|
||||
_focusNode.unfocus();
|
||||
},
|
||||
displayStringForOption:
|
||||
(Result option) => '${option.name}, ${option.admin1}',
|
||||
optionsViewBuilder: (
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
elevation: 4.0,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Result option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'${option.name}, ${option.admin1}',
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildSearchField(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
56
lib/app/ui/onboarding.dart
Normal file → Executable file
56
lib/app/ui/onboarding.dart
Normal file → Executable file
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:rain/app/data/db.dart';
|
||||
import 'package:rain/app/ui/geolocation.dart';
|
||||
import 'package:rain/app/ui/widgets/button.dart';
|
||||
import 'package:rain/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class OnBording extends StatefulWidget {
|
||||
|
@ -19,8 +19,8 @@ class _OnBordingState extends State<OnBording> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
pageController = PageController(initialPage: 0);
|
||||
super.initState();
|
||||
pageController = PageController(initialPage: 0);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -45,7 +45,17 @@ class _OnBordingState extends State<OnBording> {
|
|||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
_buildPageView(),
|
||||
_buildDotIndicators(),
|
||||
_buildActionButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageView() {
|
||||
return Expanded(
|
||||
child: PageView.builder(
|
||||
controller: pageController,
|
||||
itemCount: data.length,
|
||||
|
@ -54,44 +64,44 @@ class _OnBordingState extends State<OnBording> {
|
|||
pageIndex = index;
|
||||
});
|
||||
},
|
||||
itemBuilder:
|
||||
(context, index) => OnboardContent(
|
||||
itemBuilder: (context, index) => OnboardContent(
|
||||
image: data[index].image,
|
||||
title: data[index].title,
|
||||
description: data[index].description,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDotIndicators() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...List.generate(
|
||||
children: List.generate(
|
||||
data.length,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: DotIndicator(isActive: index == pageIndex),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: MyTextButton(
|
||||
buttonName:
|
||||
pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
|
||||
buttonName: pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
|
||||
onPressed: () {
|
||||
pageIndex == data.length - 1
|
||||
? onBoardHome()
|
||||
: pageController.nextPage(
|
||||
if (pageIndex == data.length - 1) {
|
||||
onBoardHome();
|
||||
} else {
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -108,8 +118,7 @@ class DotIndicator extends StatelessWidget {
|
|||
height: 8,
|
||||
width: 8,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive
|
||||
color: isActive
|
||||
? context.theme.colorScheme.secondary
|
||||
: context.theme.colorScheme.secondaryContainer,
|
||||
shape: BoxShape.circle,
|
||||
|
@ -153,6 +162,7 @@ class OnboardContent extends StatelessWidget {
|
|||
required this.title,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String image, title, description;
|
||||
|
||||
@override
|
||||
|
|
124
lib/app/ui/places/view/place_info.dart
Normal file → Executable file
124
lib/app/ui/places/view/place_info.dart
Normal file → Executable file
|
@ -56,13 +56,35 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
final weatherCard = widget.weatherCard;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await weatherController.updateCard(weatherCard);
|
||||
onRefresh: _handleRefresh,
|
||||
child: Scaffold(
|
||||
appBar: _buildAppBar(context, weatherCard),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ListView(
|
||||
children: [
|
||||
_buildNowWidget(weatherCard),
|
||||
_buildHourlyList(weatherCard),
|
||||
_buildSunsetSunriseWidget(weatherCard),
|
||||
_buildHourlyDescContainer(weatherCard),
|
||||
_buildDailyContainer(weatherCard),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRefresh() async {
|
||||
await weatherController.updateCard(widget.weatherCard);
|
||||
getTime();
|
||||
setState(() {});
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
}
|
||||
|
||||
AppBar _buildAppBar(BuildContext context, WeatherCard weatherCard) {
|
||||
return AppBar(
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: IconButton(
|
||||
|
@ -71,21 +93,18 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
),
|
||||
title: Text(
|
||||
weatherCard.district!.isNotEmpty
|
||||
? '${weatherCard.city}'
|
||||
', ${weatherCard.district}'
|
||||
? '${weatherCard.city}, ${weatherCard.district}'
|
||||
: '${weatherCard.city}',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ListView(
|
||||
children: [
|
||||
Now(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNowWidget(WeatherCard weatherCard) {
|
||||
return Now(
|
||||
time: weatherCard.time![timeNow],
|
||||
weather: weatherCard.weathercode![timeNow],
|
||||
degree: weatherCard.temperature2M![timeNow],
|
||||
|
@ -94,14 +113,14 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
timeNight: weatherCard.sunset![dayNow],
|
||||
tempMax: weatherCard.temperature2MMax![dayNow]!,
|
||||
tempMin: weatherCard.temperature2MMin![dayNow]!,
|
||||
),
|
||||
Card(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyList(WeatherCard weatherCard) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: SizedBox(
|
||||
height: 135,
|
||||
child: ScrollablePositionedList.separated(
|
||||
|
@ -116,8 +135,15 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
scrollDirection: Axis.horizontal,
|
||||
itemScrollController: itemScrollController,
|
||||
itemCount: weatherCard.time!.length,
|
||||
itemBuilder:
|
||||
(ctx, i) => GestureDetector(
|
||||
itemBuilder: (ctx, i) => _buildHourlyItem(weatherCard, i),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyItem(WeatherCard weatherCard, int i) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
timeNow = i;
|
||||
dayNow = (i / 24).floor();
|
||||
|
@ -125,42 +151,34 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
i == timeNow
|
||||
? context
|
||||
.theme
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
? context.theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Hourly(
|
||||
time: weatherCard.time![i],
|
||||
weather: weatherCard.weathercode![i],
|
||||
degree: weatherCard.temperature2M![i],
|
||||
timeDay:
|
||||
weatherCard.sunrise![(i / 24).floor()],
|
||||
timeNight:
|
||||
weatherCard.sunset![(i / 24).floor()],
|
||||
timeDay: weatherCard.sunrise![(i / 24).floor()],
|
||||
timeNight: weatherCard.sunset![(i / 24).floor()],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SunsetSunrise(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSunsetSunriseWidget(WeatherCard weatherCard) {
|
||||
return SunsetSunrise(
|
||||
timeSunrise: weatherCard.sunrise![dayNow],
|
||||
timeSunset: weatherCard.sunset![dayNow],
|
||||
),
|
||||
DescContainer(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyDescContainer(WeatherCard weatherCard) {
|
||||
return DescContainer(
|
||||
humidity: weatherCard.relativehumidity2M?[timeNow],
|
||||
wind: weatherCard.windspeed10M?[timeNow],
|
||||
visibility: weatherCard.visibility?[timeNow],
|
||||
|
@ -174,25 +192,21 @@ class _PlaceInfoState extends State<PlaceInfo> {
|
|||
windgusts: weatherCard.windgusts10M?[timeNow],
|
||||
uvIndex: weatherCard.uvIndex?[timeNow],
|
||||
dewpoint2M: weatherCard.dewpoint2M?[timeNow],
|
||||
precipitationProbability:
|
||||
weatherCard.precipitationProbability?[timeNow],
|
||||
precipitationProbability: weatherCard.precipitationProbability?[timeNow],
|
||||
shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
|
||||
initiallyExpanded: false,
|
||||
title: 'hourlyVariables'.tr,
|
||||
),
|
||||
DailyContainer(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyContainer(WeatherCard weatherCard) {
|
||||
return DailyContainer(
|
||||
weatherData: weatherCard,
|
||||
onTap:
|
||||
() => Get.to(
|
||||
() => DailyCardList(weatherData: weatherCard),
|
||||
transition: Transition.downToUp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
86
lib/app/ui/places/view/place_list.dart
Normal file → Executable file
86
lib/app/ui/places/view/place_list.dart
Normal file → Executable file
|
@ -14,28 +14,43 @@ class PlaceList extends StatefulWidget {
|
|||
|
||||
class _PlaceListState extends State<PlaceList> {
|
||||
final weatherController = Get.put(WeatherController());
|
||||
TextEditingController searchTasks = TextEditingController();
|
||||
final TextEditingController searchTasks = TextEditingController();
|
||||
String filter = '';
|
||||
|
||||
applyFilter(String value) async {
|
||||
filter = value.toLowerCase();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
applyFilter('');
|
||||
}
|
||||
|
||||
void applyFilter(String value) {
|
||||
filter = value.toLowerCase();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void clearSearch() {
|
||||
searchTasks.clear();
|
||||
applyFilter('');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = context.textTheme;
|
||||
final titleMedium = textTheme.titleMedium;
|
||||
return Obx(
|
||||
() =>
|
||||
weatherController.weatherCards.isEmpty
|
||||
? Center(
|
||||
|
||||
return Obx(() => _buildContent(context, titleMedium));
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, TextStyle? titleMedium) {
|
||||
if (weatherController.weatherCards.isEmpty) {
|
||||
return _buildEmptyState(context, titleMedium);
|
||||
} else {
|
||||
return _buildListView(context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context, TextStyle? titleMedium) {
|
||||
return Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
|
@ -54,32 +69,35 @@ class _PlaceListState extends State<PlaceList> {
|
|||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: NestedScrollView(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListView(BuildContext context) {
|
||||
return NestedScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverToBoxAdapter(
|
||||
return [_buildSearchField(context)];
|
||||
},
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _handleRefresh,
|
||||
child: PlaceCardList(searchCity: filter),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchField(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: MyTextForm(
|
||||
labelText: 'search'.tr,
|
||||
type: TextInputType.text,
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.search_normal_1,
|
||||
size: 20,
|
||||
),
|
||||
icon: const Icon(IconsaxPlusLinear.search_normal_1, size: 20),
|
||||
controller: searchTasks,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
onChanged: applyFilter,
|
||||
iconButton:
|
||||
searchTasks.text.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
searchTasks.clear();
|
||||
applyFilter('');
|
||||
},
|
||||
onPressed: clearSearch,
|
||||
icon: const Icon(
|
||||
IconsaxPlusLinear.close_circle,
|
||||
color: Colors.grey,
|
||||
|
@ -88,17 +106,11 @@ class _PlaceListState extends State<PlaceList> {
|
|||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await weatherController.updateCacheCard(true);
|
||||
setState(() {});
|
||||
},
|
||||
child: PlaceCardList(searchCity: filter),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleRefresh() async {
|
||||
await weatherController.updateCacheCard(true);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
|
241
lib/app/ui/places/widgets/create_place.dart
Normal file → Executable file
241
lib/app/ui/places/widgets/create_place.dart
Normal file → Executable file
|
@ -23,12 +23,14 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
final formKey = GlobalKey<FormState>();
|
||||
final _focusNode = FocusNode();
|
||||
final weatherController = Get.put(WeatherController());
|
||||
late final TextEditingController _controller = TextEditingController();
|
||||
late TextEditingController _controllerLat = TextEditingController();
|
||||
late TextEditingController _controllerLon = TextEditingController();
|
||||
late final TextEditingController _controllerCity = TextEditingController();
|
||||
late final TextEditingController _controllerDistrict =
|
||||
TextEditingController();
|
||||
|
||||
static const kTextFieldElevation = 4.0;
|
||||
|
||||
late TextEditingController _controller;
|
||||
late TextEditingController _controllerLat;
|
||||
late TextEditingController _controllerLon;
|
||||
late TextEditingController _controllerCity;
|
||||
late TextEditingController _controllerDistrict;
|
||||
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _animation;
|
||||
|
@ -36,10 +38,12 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.latitude != null && widget.longitude != null) {
|
||||
_controller = TextEditingController();
|
||||
_controllerLat = TextEditingController(text: widget.latitude);
|
||||
_controllerLon = TextEditingController(text: widget.longitude);
|
||||
}
|
||||
_controllerCity = TextEditingController();
|
||||
_controllerDistrict = TextEditingController();
|
||||
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
|
@ -78,20 +82,24 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const kTextFieldElevation = 4.0;
|
||||
bool showButton =
|
||||
_controllerLon.text.isNotEmpty &&
|
||||
bool get showButton {
|
||||
return _controllerLon.text.isNotEmpty &&
|
||||
_controllerLat.text.isNotEmpty &&
|
||||
_controllerCity.text.isNotEmpty &&
|
||||
_controllerDistrict.text.isNotEmpty;
|
||||
}
|
||||
|
||||
void updateButtonVisibility() {
|
||||
if (showButton) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
updateButtonVisibility();
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||
|
@ -108,7 +116,26 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
_buildTitleText(),
|
||||
_buildSearchField(),
|
||||
_buildLatitudeField(),
|
||||
_buildLongitudeField(),
|
||||
_buildCityField(),
|
||||
_buildDistrictField(),
|
||||
_buildSubmitButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isLoading) const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitleText() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 14, bottom: 7),
|
||||
child: Text(
|
||||
'create'.tr,
|
||||
|
@ -117,8 +144,11 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
RawAutocomplete<Result>(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchField() {
|
||||
return RawAutocomplete<Result>(
|
||||
focusNode: _focusNode,
|
||||
textEditingController: _controller,
|
||||
fieldViewBuilder: (
|
||||
|
@ -133,11 +163,7 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
type: TextInputType.text,
|
||||
icon: const Icon(IconsaxPlusLinear.global_search),
|
||||
controller: _controller,
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
focusNode: _focusNode,
|
||||
);
|
||||
},
|
||||
|
@ -145,19 +171,25 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<Result>.empty();
|
||||
}
|
||||
return WeatherAPI().getCity(
|
||||
textEditingValue.text,
|
||||
locale,
|
||||
);
|
||||
return WeatherAPI().getCity(textEditingValue.text, locale);
|
||||
},
|
||||
onSelected:
|
||||
(Result selection) => fillController(selection),
|
||||
onSelected: (Result selection) => fillController(selection),
|
||||
displayStringForOption:
|
||||
(Result option) => '${option.name}, ${option.admin1}',
|
||||
optionsViewBuilder: (
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return _buildOptionsView(context, onSelected, options);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionsView(
|
||||
BuildContext context,
|
||||
AutocompleteOnSelected<Result> onSelected,
|
||||
Iterable<Result> options,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
|
@ -171,9 +203,7 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final Result option = options.elementAt(
|
||||
index,
|
||||
);
|
||||
final Result option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: ListTile(
|
||||
|
@ -188,21 +218,72 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
}
|
||||
|
||||
Widget _buildLatitudeField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLat,
|
||||
labelText: 'lat'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateLatitude(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLongitudeField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLon,
|
||||
labelText: 'lon'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateLongitude(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCityField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerCity,
|
||||
labelText: 'city'.tr,
|
||||
type: TextInputType.name,
|
||||
icon: const Icon(IconsaxPlusLinear.building_3),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateCity(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDistrictField() {
|
||||
return MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerDistrict,
|
||||
labelText: 'district'.tr,
|
||||
type: TextInputType.streetAddress,
|
||||
icon: const Icon(IconsaxPlusLinear.global),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
|
||||
validator: (value) => _validateDistrict(value),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubmitButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
child: SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axisAlignment: -1.0,
|
||||
child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
|
||||
),
|
||||
validator: (value) {
|
||||
);
|
||||
}
|
||||
|
||||
String? _validateLatitude(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
|
@ -214,21 +295,9 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
return 'validate90'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerLon,
|
||||
labelText: 'lon'.tr,
|
||||
type: TextInputType.number,
|
||||
icon: const Icon(IconsaxPlusLinear.location),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
}
|
||||
|
||||
String? _validateLongitude(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateValue'.tr;
|
||||
}
|
||||
|
@ -240,62 +309,29 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
return 'validate180'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerCity,
|
||||
labelText: 'city'.tr,
|
||||
type: TextInputType.name,
|
||||
icon: const Icon(IconsaxPlusLinear.building_3),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
}
|
||||
|
||||
String? _validateCity(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateName'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
MyTextForm(
|
||||
elevation: kTextFieldElevation,
|
||||
controller: _controllerDistrict,
|
||||
labelText: 'district'.tr,
|
||||
type: TextInputType.streetAddress,
|
||||
icon: const Icon(IconsaxPlusLinear.global),
|
||||
onChanged: (value) => setState(() {}),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
),
|
||||
validator: (value) {
|
||||
}
|
||||
|
||||
String? _validateDistrict(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'validateName'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 10,
|
||||
),
|
||||
child: SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axisAlignment: -1.0,
|
||||
child: MyTextButton(
|
||||
buttonName: 'done'.tr,
|
||||
onPressed: () async {
|
||||
}
|
||||
|
||||
Future<void> _handleSubmit() async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
textTrim(_controllerLat);
|
||||
textTrim(_controllerLon);
|
||||
textTrim(_controllerCity);
|
||||
textTrim(_controllerDistrict);
|
||||
|
||||
setState(() => isLoading = true);
|
||||
await weatherController.addCardWeather(
|
||||
double.parse(_controllerLat.text),
|
||||
|
@ -306,18 +342,5 @@ class _CreatePlaceState extends State<CreatePlace>
|
|||
setState(() => isLoading = false);
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isLoading) const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
116
lib/app/ui/places/widgets/place_card.dart
Normal file → Executable file
116
lib/app/ui/places/widgets/place_card.dart
Normal file → Executable file
|
@ -19,6 +19,7 @@ class PlaceCard extends StatefulWidget {
|
|||
required this.timeNight,
|
||||
required this.timeDaily,
|
||||
});
|
||||
|
||||
final List<String> time;
|
||||
final List<String> timeDay;
|
||||
final List<String> timeNight;
|
||||
|
@ -40,13 +41,36 @@ class _PlaceCardState extends State<PlaceCard> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentTimeIndex = weatherController.getTime(
|
||||
widget.time,
|
||||
widget.timezone,
|
||||
);
|
||||
final currentDayIndex = weatherController.getDay(
|
||||
widget.timeDaily,
|
||||
widget.timezone,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
_buildWeatherInfo(context, currentTimeIndex, currentDayIndex),
|
||||
const Gap(5),
|
||||
_buildWeatherImage(currentTimeIndex, currentDayIndex),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherInfo(
|
||||
BuildContext context,
|
||||
int currentTimeIndex,
|
||||
int currentDayIndex,
|
||||
) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -55,13 +79,7 @@ class _PlaceCardState extends State<PlaceCard> {
|
|||
children: [
|
||||
Text(
|
||||
statusData.getDegree(
|
||||
widget
|
||||
.degree[weatherController.getTime(
|
||||
widget.time,
|
||||
widget.timezone,
|
||||
)]
|
||||
.round()
|
||||
.toInt(),
|
||||
widget.degree[currentTimeIndex].round().toInt(),
|
||||
),
|
||||
style: context.textTheme.titleLarge?.copyWith(
|
||||
fontSize: 22,
|
||||
|
@ -70,12 +88,7 @@ class _PlaceCardState extends State<PlaceCard> {
|
|||
),
|
||||
const Gap(7),
|
||||
Text(
|
||||
statusWeather.getText(
|
||||
widget.weather[weatherController.getTime(
|
||||
widget.time,
|
||||
widget.timezone,
|
||||
)],
|
||||
),
|
||||
statusWeather.getText(widget.weather[currentTimeIndex]),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w400,
|
||||
|
@ -84,21 +97,37 @@ class _PlaceCardState extends State<PlaceCard> {
|
|||
],
|
||||
),
|
||||
const Gap(10),
|
||||
Text(
|
||||
widget.district.isEmpty
|
||||
? widget.city
|
||||
: widget.city.isEmpty
|
||||
? widget.district
|
||||
: widget.city == widget.district
|
||||
? widget.city
|
||||
: '${widget.city}'
|
||||
', ${widget.district}',
|
||||
_buildLocationText(),
|
||||
const Gap(5),
|
||||
_buildCurrentTimeText(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationText() {
|
||||
String locationText;
|
||||
|
||||
if (widget.district.isEmpty) {
|
||||
locationText = widget.city;
|
||||
} else if (widget.city.isEmpty) {
|
||||
locationText = widget.district;
|
||||
} else if (widget.city == widget.district) {
|
||||
locationText = widget.city;
|
||||
} else {
|
||||
locationText = '${widget.city}, ${widget.district}';
|
||||
}
|
||||
|
||||
return Text(
|
||||
locationText,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
StreamBuilder(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCurrentTimeText(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: Stream.periodic(const Duration(seconds: 1)),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
|
@ -109,35 +138,18 @@ class _PlaceCardState extends State<PlaceCard> {
|
|||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Image.asset(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherImage(int currentTimeIndex, int currentDayIndex) {
|
||||
return Image.asset(
|
||||
statusWeather.getImageNow(
|
||||
widget.weather[weatherController.getTime(
|
||||
widget.time,
|
||||
widget.timezone,
|
||||
)],
|
||||
widget.time[weatherController.getTime(
|
||||
widget.time,
|
||||
widget.timezone,
|
||||
)],
|
||||
widget.timeDay[weatherController.getDay(
|
||||
widget.timeDaily,
|
||||
widget.timezone,
|
||||
)],
|
||||
widget.timeNight[weatherController.getDay(
|
||||
widget.timeDaily,
|
||||
widget.timezone,
|
||||
)],
|
||||
widget.weather[currentTimeIndex],
|
||||
widget.time[currentTimeIndex],
|
||||
widget.timeDay[currentDayIndex],
|
||||
widget.timeNight[currentDayIndex],
|
||||
),
|
||||
scale: 6.5,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
115
lib/app/ui/places/widgets/place_card_list.dart
Normal file → Executable file
115
lib/app/ui/places/widgets/place_card_list.dart
Normal file → Executable file
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
import 'package:rain/app/controller/controller.dart';
|
||||
import 'package:rain/app/data/db.dart';
|
||||
import 'package:rain/app/ui/places/view/place_info.dart';
|
||||
import 'package:rain/app/ui/places/widgets/place_card.dart';
|
||||
|
||||
|
@ -21,54 +22,101 @@ class _PlaceCardListState extends State<PlaceCardList> {
|
|||
final textTheme = context.textTheme;
|
||||
final titleMedium = textTheme.titleMedium;
|
||||
|
||||
var weatherCards =
|
||||
weatherController.weatherCards
|
||||
.where(
|
||||
(weatherCard) =>
|
||||
(widget.searchCity.isEmpty ||
|
||||
weatherCard.city!.toLowerCase().contains(
|
||||
final weatherCards = _filterWeatherCards(
|
||||
weatherController.weatherCards,
|
||||
widget.searchCity,
|
||||
)),
|
||||
)
|
||||
.toList()
|
||||
.obs;
|
||||
);
|
||||
|
||||
return ReorderableListView(
|
||||
onReorder:
|
||||
(oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex),
|
||||
children: [
|
||||
...weatherCards.map(
|
||||
(weatherCardList) => Dismissible(
|
||||
children: _buildWeatherCardList(
|
||||
weatherCards,
|
||||
context,
|
||||
textTheme,
|
||||
titleMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<WeatherCard> _filterWeatherCards(
|
||||
List<WeatherCard> weatherCards,
|
||||
String searchCity,
|
||||
) {
|
||||
return weatherCards
|
||||
.where(
|
||||
(weatherCard) =>
|
||||
(searchCity.isEmpty ||
|
||||
weatherCard.city!.toLowerCase().contains(searchCity)),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<Widget> _buildWeatherCardList(
|
||||
List<WeatherCard> weatherCards,
|
||||
BuildContext context,
|
||||
TextTheme textTheme,
|
||||
TextStyle? titleMedium,
|
||||
) {
|
||||
return weatherCards
|
||||
.map(
|
||||
(weatherCardList) => _buildDismissibleCard(
|
||||
context,
|
||||
weatherCardList,
|
||||
textTheme,
|
||||
titleMedium,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Widget _buildDismissibleCard(
|
||||
BuildContext context,
|
||||
WeatherCard weatherCardList,
|
||||
TextTheme textTheme,
|
||||
TextStyle? titleMedium,
|
||||
) {
|
||||
return Dismissible(
|
||||
key: ValueKey(weatherCardList),
|
||||
direction: DismissDirection.endToStart,
|
||||
background: Container(
|
||||
background: _buildDismissibleBackground(),
|
||||
confirmDismiss:
|
||||
(DismissDirection direction) =>
|
||||
_showDeleteConfirmationDialog(context, textTheme, titleMedium),
|
||||
onDismissed: (DismissDirection direction) async {
|
||||
await weatherController.deleteCardWeather(weatherCardList);
|
||||
},
|
||||
child: _buildCardGestureDetector(weatherCardList),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDismissibleBackground() {
|
||||
return Container(
|
||||
alignment: Alignment.centerRight,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: Icon(IconsaxPlusLinear.trash_square, color: Colors.red),
|
||||
),
|
||||
),
|
||||
confirmDismiss: (DismissDirection direction) async {
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _showDeleteConfirmationDialog(
|
||||
BuildContext context,
|
||||
TextTheme textTheme,
|
||||
TextStyle? titleMedium,
|
||||
) async {
|
||||
return await showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog.adaptive(
|
||||
title: Text(
|
||||
'deletedCardWeather'.tr,
|
||||
style: textTheme.titleLarge,
|
||||
),
|
||||
content: Text(
|
||||
'deletedCardWeatherQuery'.tr,
|
||||
style: titleMedium,
|
||||
),
|
||||
title: Text('deletedCardWeather'.tr, style: textTheme.titleLarge),
|
||||
content: Text('deletedCardWeatherQuery'.tr, style: titleMedium),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: false),
|
||||
child: Text(
|
||||
'cancel'.tr,
|
||||
style: titleMedium?.copyWith(
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
style: titleMedium?.copyWith(color: Colors.blueAccent),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
|
@ -82,11 +130,10 @@ class _PlaceCardListState extends State<PlaceCardList> {
|
|||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onDismissed: (DismissDirection direction) async {
|
||||
await weatherController.deleteCardWeather(weatherCardList);
|
||||
},
|
||||
child: GestureDetector(
|
||||
}
|
||||
|
||||
Widget _buildCardGestureDetector(WeatherCard weatherCardList) {
|
||||
return GestureDetector(
|
||||
onTap:
|
||||
() => Get.to(
|
||||
() => PlaceInfo(weatherCard: weatherCardList),
|
||||
|
@ -103,10 +150,6 @@ class _PlaceCardListState extends State<PlaceCardList> {
|
|||
city: weatherCardList.city!,
|
||||
timezone: weatherCardList.timezone!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
1384
lib/app/ui/settings/view/settings.dart
Normal file → Executable file
1384
lib/app/ui/settings/view/settings.dart
Normal file → Executable file
File diff suppressed because it is too large
Load diff
68
lib/app/ui/settings/widgets/setting_card.dart
Normal file → Executable file
68
lib/app/ui/settings/widgets/setting_card.dart
Normal file → Executable file
|
@ -14,12 +14,13 @@ class SettingCard extends StatelessWidget {
|
|||
this.elevation,
|
||||
this.dropdownName,
|
||||
this.dropdownList,
|
||||
this.dropdownCange,
|
||||
this.dropdownChange,
|
||||
this.value,
|
||||
this.onPressed,
|
||||
this.onChange,
|
||||
this.infoWidget,
|
||||
});
|
||||
|
||||
final Widget icon;
|
||||
final String text;
|
||||
final bool switcher;
|
||||
|
@ -29,10 +30,10 @@ class SettingCard extends StatelessWidget {
|
|||
final Widget? infoWidget;
|
||||
final String? dropdownName;
|
||||
final List<String>? dropdownList;
|
||||
final Function(String?)? dropdownCange;
|
||||
final ValueChanged<String?>? dropdownChange;
|
||||
final bool? value;
|
||||
final Function()? onPressed;
|
||||
final Function(bool)? onChange;
|
||||
final VoidCallback? onPressed;
|
||||
final ValueChanged<bool>? onChange;
|
||||
final double? elevation;
|
||||
|
||||
@override
|
||||
|
@ -49,14 +50,32 @@ class SettingCard extends StatelessWidget {
|
|||
style: context.textTheme.titleMedium,
|
||||
overflow: TextOverflow.visible,
|
||||
),
|
||||
trailing:
|
||||
switcher
|
||||
? Transform.scale(
|
||||
trailing: _buildTrailingWidget(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTrailingWidget(BuildContext context) {
|
||||
if (switcher) {
|
||||
return _buildSwitchWidget();
|
||||
} else if (dropdown) {
|
||||
return _buildDropdownWidget();
|
||||
} else if (info) {
|
||||
return _buildInfoWidget();
|
||||
} else {
|
||||
return const Icon(IconsaxPlusLinear.arrow_right_3, size: 18);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSwitchWidget() {
|
||||
return Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(value: value!, onChanged: onChange),
|
||||
)
|
||||
: dropdown
|
||||
? DropdownButton<String>(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownWidget() {
|
||||
return DropdownButton<String>(
|
||||
icon: const Padding(
|
||||
padding: EdgeInsets.only(left: 7),
|
||||
child: Icon(IconsaxPlusLinear.arrow_down),
|
||||
|
@ -66,28 +85,23 @@ class SettingCard extends StatelessWidget {
|
|||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
underline: Container(),
|
||||
value: dropdownName,
|
||||
items:
|
||||
dropdownList!.map<DropdownMenuItem<String>>((
|
||||
String value,
|
||||
) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
items: dropdownList!.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(value: value, child: Text(value));
|
||||
}).toList(),
|
||||
onChanged: dropdownCange,
|
||||
)
|
||||
: info
|
||||
? infoSettings
|
||||
? Wrap(
|
||||
onChanged: dropdownChange,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoWidget() {
|
||||
if (infoSettings) {
|
||||
return Wrap(
|
||||
children: [
|
||||
infoWidget!,
|
||||
const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
|
||||
],
|
||||
)
|
||||
: infoWidget!
|
||||
: const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return infoWidget!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
21
lib/app/ui/widgets/button.dart
Normal file → Executable file
21
lib/app/ui/widgets/button.dart
Normal file → Executable file
|
@ -6,25 +6,32 @@ class MyTextButton extends StatelessWidget {
|
|||
super.key,
|
||||
required this.buttonName,
|
||||
required this.onPressed,
|
||||
this.height = 50.0,
|
||||
});
|
||||
|
||||
final String buttonName;
|
||||
final VoidCallback? onPressed;
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
height: height,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
shadowColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
context.theme.colorScheme.secondaryContainer.withAlpha(80),
|
||||
),
|
||||
),
|
||||
style: _buildButtonStyle(context),
|
||||
onPressed: onPressed,
|
||||
child: Text(buttonName, style: context.textTheme.titleMedium),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle _buildButtonStyle(BuildContext context) {
|
||||
return ButtonStyle(
|
||||
shadowColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
context.theme.colorScheme.secondaryContainer.withAlpha(80),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
13
lib/app/ui/widgets/shimmer.dart
Normal file → Executable file
13
lib/app/ui/widgets/shimmer.dart
Normal file → Executable file
|
@ -3,16 +3,21 @@ import 'package:get/get.dart';
|
|||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class MyShimmer extends StatelessWidget {
|
||||
const MyShimmer({super.key, required this.hight, this.edgeInsetsMargin});
|
||||
final double hight;
|
||||
final EdgeInsets? edgeInsetsMargin;
|
||||
const MyShimmer({super.key, required this.height, this.margin});
|
||||
|
||||
final double height;
|
||||
final EdgeInsets? margin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: context.theme.cardColor,
|
||||
highlightColor: context.theme.primaryColor,
|
||||
child: Card(margin: edgeInsetsMargin, child: SizedBox(height: hight)),
|
||||
child: _buildShimmerCard(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmerCard() {
|
||||
return Card(margin: margin, child: SizedBox(height: height));
|
||||
}
|
||||
}
|
||||
|
|
26
lib/app/ui/widgets/text_form.dart
Normal file → Executable file
26
lib/app/ui/widgets/text_form.dart
Normal file → Executable file
|
@ -15,6 +15,7 @@ class MyTextForm extends StatelessWidget {
|
|||
this.focusNode,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final String labelText;
|
||||
final TextInputType type;
|
||||
final Icon icon;
|
||||
|
@ -31,23 +32,28 @@ class MyTextForm extends StatelessWidget {
|
|||
return Card(
|
||||
elevation: elevation,
|
||||
margin: margin,
|
||||
child: TextFormField(
|
||||
child: _buildTextFormField(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextFormField(BuildContext context) {
|
||||
return TextFormField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
keyboardType: type,
|
||||
style: context.textTheme.labelLarge,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.5,
|
||||
vertical: 0,
|
||||
),
|
||||
decoration: _buildInputDecoration(),
|
||||
validator: validator,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _buildInputDecoration() {
|
||||
return InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.5, vertical: 0),
|
||||
prefixIcon: icon,
|
||||
suffixIcon: iconButton,
|
||||
labelText: labelText,
|
||||
),
|
||||
validator: validator,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
59
lib/app/ui/widgets/weather/daily/daily_card.dart
Normal file → Executable file
59
lib/app/ui/widgets/weather/daily/daily_card.dart
Normal file → Executable file
|
@ -14,6 +14,7 @@ class DailyCard extends StatefulWidget {
|
|||
required this.temperature2MMax,
|
||||
required this.temperature2MMin,
|
||||
});
|
||||
|
||||
final DateTime timeDaily;
|
||||
final int? weathercodeDaily;
|
||||
final double? temperature2MMax;
|
||||
|
@ -29,15 +30,27 @@ class _DailyCardState extends State<DailyCard> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.weathercodeDaily == null
|
||||
? Container()
|
||||
: Card(
|
||||
if (widget.weathercodeDaily == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
_buildTemperatureInfo(context),
|
||||
const Gap(5),
|
||||
_buildWeatherImage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemperatureInfo(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -49,34 +62,38 @@ class _DailyCardState extends State<DailyCard> {
|
|||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Text(
|
||||
DateFormat.MMMMEEEEd(
|
||||
locale.languageCode,
|
||||
).format(widget.timeDaily),
|
||||
_buildDateText(context),
|
||||
const Gap(5),
|
||||
_buildWeatherDescription(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateText(BuildContext context) {
|
||||
return Text(
|
||||
DateFormat.MMMMEEEEd(locale.languageCode).format(widget.timeDaily),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Text(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherDescription(BuildContext context) {
|
||||
return Text(
|
||||
statusWeather.getText(widget.weathercodeDaily),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Image.asset(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherImage() {
|
||||
return Image.asset(
|
||||
statusWeather.getImageNowDaily(widget.weathercodeDaily),
|
||||
scale: 6.5,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
362
lib/app/ui/widgets/weather/daily/daily_card_info.dart
Normal file → Executable file
362
lib/app/ui/widgets/weather/daily/daily_card_info.dart
Normal file → Executable file
|
@ -52,32 +52,11 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
|
|||
Widget build(BuildContext context) {
|
||||
final weatherData = widget.weatherData;
|
||||
final timeDaily = weatherData.timeDaily ?? [];
|
||||
final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
|
||||
|
||||
final textTheme = context.textTheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
title: Text(
|
||||
DateFormat.MMMMEEEEd(
|
||||
locale.languageCode,
|
||||
).format(timeDaily[pageIndex]),
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
appBar: _buildAppBar(context, textTheme, timeDaily),
|
||||
body: SafeArea(
|
||||
child: PageView.builder(
|
||||
controller: pageController,
|
||||
|
@ -89,13 +68,52 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
|
|||
},
|
||||
itemCount: timeDaily.length,
|
||||
itemBuilder: (context, index) {
|
||||
final indexedWeatherCodeDaily = weatherCodeDaily[index];
|
||||
return _buildPageContent(context, weatherData, index);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar _buildAppBar(
|
||||
BuildContext context,
|
||||
TextTheme textTheme,
|
||||
List<DateTime> timeDaily,
|
||||
) {
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
title: Text(
|
||||
DateFormat.MMMMEEEEd(locale.languageCode).format(timeDaily[pageIndex]),
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageContent(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
int index,
|
||||
) {
|
||||
final weatherCodeDaily = weatherData.weathercodeDaily?[index];
|
||||
if (weatherCodeDaily == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final startIndex = index * 24;
|
||||
final temperature2MMin = weatherData.temperature2MMin?[index];
|
||||
final temperature2MMax = weatherData.temperature2MMax?[index];
|
||||
final apparentTemperatureMin =
|
||||
weatherData.apparentTemperatureMin?[index];
|
||||
final apparentTemperatureMax =
|
||||
weatherData.apparentTemperatureMax?[index];
|
||||
final apparentTemperatureMin = weatherData.apparentTemperatureMin?[index];
|
||||
final apparentTemperatureMax = weatherData.apparentTemperatureMax?[index];
|
||||
final uvIndexMax = weatherData.uvIndexMax?[index];
|
||||
final windDirection10MDominant =
|
||||
weatherData.winddirection10MDominant?[index];
|
||||
|
@ -105,45 +123,98 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
|
|||
weatherData.precipitationProbabilityMax?[index];
|
||||
final rainSum = weatherData.rainSum?[index];
|
||||
final precipitationSum = weatherData.precipitationSum?[index];
|
||||
final sunrise = weatherData.sunrise![index];
|
||||
final sunset = weatherData.sunset![index];
|
||||
final sunrise = weatherData.sunrise?[index];
|
||||
final sunset = weatherData.sunset?[index];
|
||||
|
||||
final startIndex = index * 24;
|
||||
if (sunrise == null || sunset == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return indexedWeatherCodeDaily == null
|
||||
? null
|
||||
: Container(
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: ListView(
|
||||
children: [
|
||||
Now(
|
||||
weather:
|
||||
weatherData.weathercode![startIndex + hourOfDay],
|
||||
degree:
|
||||
weatherData.temperature2M![startIndex + hourOfDay],
|
||||
feels:
|
||||
weatherData.apparentTemperature![startIndex +
|
||||
hourOfDay]!,
|
||||
time: weatherData.time![startIndex + hourOfDay],
|
||||
_buildNowWidget(
|
||||
weatherData,
|
||||
index,
|
||||
startIndex,
|
||||
hourOfDay,
|
||||
sunrise,
|
||||
sunset,
|
||||
),
|
||||
_buildHourlyList(context, weatherData, startIndex, sunrise, sunset),
|
||||
_buildSunsetSunriseWidget(sunrise, sunset),
|
||||
_buildHourlyDescContainer(weatherData, startIndex, hourOfDay),
|
||||
_buildDailyDescContainer(
|
||||
weatherData,
|
||||
temperature2MMin,
|
||||
temperature2MMax,
|
||||
apparentTemperatureMin,
|
||||
apparentTemperatureMax,
|
||||
uvIndexMax,
|
||||
windDirection10MDominant,
|
||||
windSpeed10MMax,
|
||||
windGusts10MMax,
|
||||
precipitationProbabilityMax,
|
||||
rainSum,
|
||||
precipitationSum,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNowWidget(
|
||||
WeatherCard weatherData,
|
||||
int index,
|
||||
int startIndex,
|
||||
int hourOfDay,
|
||||
String sunrise,
|
||||
String sunset,
|
||||
) {
|
||||
final weatherCode = weatherData.weathercode?[startIndex + hourOfDay];
|
||||
final temperature = weatherData.temperature2M?[startIndex + hourOfDay];
|
||||
final feels = weatherData.apparentTemperature?[startIndex + hourOfDay];
|
||||
final time = weatherData.time?[startIndex + hourOfDay];
|
||||
final tempMax = weatherData.temperature2MMax?[index];
|
||||
final tempMin = weatherData.temperature2MMin?[index];
|
||||
|
||||
if (weatherCode == null ||
|
||||
temperature == null ||
|
||||
feels == null ||
|
||||
time == null ||
|
||||
tempMax == null ||
|
||||
tempMin == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Now(
|
||||
weather: weatherCode,
|
||||
degree: temperature,
|
||||
feels: feels,
|
||||
time: time,
|
||||
timeDay: sunrise,
|
||||
timeNight: sunset,
|
||||
tempMax: temperature2MMax!,
|
||||
tempMin: temperature2MMin!,
|
||||
),
|
||||
Card(
|
||||
tempMax: tempMax,
|
||||
tempMin: tempMin,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyList(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
int startIndex,
|
||||
String sunrise,
|
||||
String sunset,
|
||||
) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: SizedBox(
|
||||
height: 135,
|
||||
child: ScrollablePositionedList.separated(
|
||||
separatorBuilder: (
|
||||
BuildContext context,
|
||||
int index,
|
||||
) {
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const VerticalDivider(
|
||||
width: 10,
|
||||
indent: 40,
|
||||
|
@ -153,108 +224,125 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
|
|||
scrollDirection: Axis.horizontal,
|
||||
itemCount: 24,
|
||||
itemBuilder: (ctx, i) {
|
||||
int hourlyIndex = startIndex + i;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
hourOfDay = i;
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
i == hourOfDay
|
||||
? context
|
||||
.theme
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
: Colors.transparent,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Hourly(
|
||||
time: weatherData.time![hourlyIndex],
|
||||
weather:
|
||||
weatherData.weathercode![hourlyIndex],
|
||||
degree:
|
||||
weatherData
|
||||
.temperature2M![hourlyIndex],
|
||||
timeDay: sunrise,
|
||||
timeNight: sunset,
|
||||
),
|
||||
),
|
||||
return _buildHourlyItem(
|
||||
context,
|
||||
weatherData,
|
||||
startIndex,
|
||||
i,
|
||||
sunrise,
|
||||
sunset,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHourlyItem(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
int startIndex,
|
||||
int i,
|
||||
String sunrise,
|
||||
String sunset,
|
||||
) {
|
||||
int hourlyIndex = startIndex + i;
|
||||
bool isSelected = i == hourOfDay;
|
||||
|
||||
final time = weatherData.time?[hourlyIndex];
|
||||
final weatherCode = weatherData.weathercode?[hourlyIndex];
|
||||
final temperature = weatherData.temperature2M?[hourlyIndex];
|
||||
|
||||
if (time == null || weatherCode == null || temperature == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
hourOfDay = i;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset),
|
||||
DescContainer(
|
||||
humidity:
|
||||
weatherData.relativehumidity2M?[startIndex +
|
||||
hourOfDay],
|
||||
wind: weatherData.windspeed10M?[startIndex + hourOfDay],
|
||||
visibility:
|
||||
weatherData.visibility?[startIndex + hourOfDay],
|
||||
feels:
|
||||
weatherData.apparentTemperature?[startIndex +
|
||||
hourOfDay],
|
||||
evaporation:
|
||||
weatherData.evapotranspiration?[startIndex +
|
||||
hourOfDay],
|
||||
precipitation:
|
||||
weatherData.precipitation?[startIndex + hourOfDay],
|
||||
direction:
|
||||
weatherData.winddirection10M?[startIndex +
|
||||
hourOfDay],
|
||||
pressure:
|
||||
weatherData.surfacePressure?[startIndex +
|
||||
hourOfDay],
|
||||
rain: weatherData.rain?[startIndex + hourOfDay],
|
||||
cloudcover:
|
||||
weatherData.cloudcover?[startIndex + hourOfDay],
|
||||
windgusts:
|
||||
weatherData.windgusts10M?[startIndex + hourOfDay],
|
||||
uvIndex: weatherData.uvIndex?[startIndex + hourOfDay],
|
||||
dewpoint2M:
|
||||
weatherData.dewpoint2M?[startIndex + hourOfDay],
|
||||
child: Hourly(
|
||||
time: time,
|
||||
weather: weatherCode,
|
||||
degree: temperature,
|
||||
timeDay: sunrise,
|
||||
timeNight: sunset,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
|
||||
return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
|
||||
}
|
||||
|
||||
Widget _buildHourlyDescContainer(
|
||||
WeatherCard weatherData,
|
||||
int startIndex,
|
||||
int hourOfDay,
|
||||
) {
|
||||
final hourlyIndex = startIndex + hourOfDay;
|
||||
|
||||
return DescContainer(
|
||||
humidity: weatherData.relativehumidity2M?[hourlyIndex],
|
||||
wind: weatherData.windspeed10M?[hourlyIndex],
|
||||
visibility: weatherData.visibility?[hourlyIndex],
|
||||
feels: weatherData.apparentTemperature?[hourlyIndex],
|
||||
evaporation: weatherData.evapotranspiration?[hourlyIndex],
|
||||
precipitation: weatherData.precipitation?[hourlyIndex],
|
||||
direction: weatherData.winddirection10M?[hourlyIndex],
|
||||
pressure: weatherData.surfacePressure?[hourlyIndex],
|
||||
rain: weatherData.rain?[hourlyIndex],
|
||||
cloudcover: weatherData.cloudcover?[hourlyIndex],
|
||||
windgusts: weatherData.windgusts10M?[hourlyIndex],
|
||||
uvIndex: weatherData.uvIndex?[hourlyIndex],
|
||||
dewpoint2M: weatherData.dewpoint2M?[hourlyIndex],
|
||||
precipitationProbability:
|
||||
weatherData.precipitationProbability?[startIndex +
|
||||
hourOfDay],
|
||||
shortwaveRadiation:
|
||||
weatherData.shortwaveRadiation?[startIndex +
|
||||
hourOfDay],
|
||||
weatherData.precipitationProbability?[hourlyIndex],
|
||||
shortwaveRadiation: weatherData.shortwaveRadiation?[hourlyIndex],
|
||||
initiallyExpanded: true,
|
||||
title: 'hourlyVariables'.tr,
|
||||
),
|
||||
DescContainer(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyDescContainer(
|
||||
WeatherCard weatherData,
|
||||
double? temperature2MMin,
|
||||
double? temperature2MMax,
|
||||
double? apparentTemperatureMin,
|
||||
double? apparentTemperatureMax,
|
||||
double? uvIndexMax,
|
||||
int? windDirection10MDominant,
|
||||
double? windSpeed10MMax,
|
||||
double? windGusts10MMax,
|
||||
int? precipitationProbabilityMax,
|
||||
double? rainSum,
|
||||
double? precipitationSum,
|
||||
) {
|
||||
return DescContainer(
|
||||
apparentTemperatureMin: apparentTemperatureMin,
|
||||
apparentTemperatureMax: apparentTemperatureMax,
|
||||
uvIndexMax: uvIndexMax,
|
||||
windDirection10MDominant: windDirection10MDominant,
|
||||
windSpeed10MMax: windSpeed10MMax,
|
||||
windGusts10MMax: windGusts10MMax,
|
||||
precipitationProbabilityMax:
|
||||
precipitationProbabilityMax,
|
||||
precipitationProbabilityMax: precipitationProbabilityMax,
|
||||
rainSum: rainSum,
|
||||
precipitationSum: precipitationSum,
|
||||
initiallyExpanded: true,
|
||||
title: 'dailyVariables'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
67
lib/app/ui/widgets/weather/daily/daily_card_list.dart
Normal file → Executable file
67
lib/app/ui/widgets/weather/daily/daily_card_list.dart
Normal file → Executable file
|
@ -16,21 +16,30 @@ class DailyCardList extends StatefulWidget {
|
|||
class _DailyCardListState extends State<DailyCardList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const transparent = Colors.transparent;
|
||||
final weatherData = widget.weatherData;
|
||||
final timeDaily = weatherData.timeDaily ?? [];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
appBar: _buildAppBar(context),
|
||||
body: SafeArea(
|
||||
child: ListView.builder(
|
||||
itemCount: timeDaily.length,
|
||||
itemBuilder: (context, index) =>
|
||||
_buildDailyCardItem(context, weatherData, index),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar _buildAppBar(BuildContext context) {
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
|
||||
splashColor: transparent,
|
||||
highlightColor: transparent,
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
title: Text(
|
||||
'weatherMore'.tr,
|
||||
|
@ -39,26 +48,36 @@ class _DailyCardListState extends State<DailyCardList> {
|
|||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView.builder(
|
||||
itemCount: timeDaily.length,
|
||||
itemBuilder:
|
||||
(context, index) => GestureDetector(
|
||||
onTap:
|
||||
() => Get.to(
|
||||
() =>
|
||||
DailyCardInfo(weatherData: weatherData, index: index),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyCardItem(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
int index,
|
||||
) {
|
||||
final timeDaily = weatherData.timeDaily?[index];
|
||||
final weathercodeDaily = weatherData.weathercodeDaily?[index];
|
||||
final temperature2MMax = weatherData.temperature2MMax?[index];
|
||||
final temperature2MMin = weatherData.temperature2MMin?[index];
|
||||
|
||||
if (timeDaily == null ||
|
||||
weathercodeDaily == null ||
|
||||
temperature2MMax == null ||
|
||||
temperature2MMin == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => Get.to(
|
||||
() => DailyCardInfo(weatherData: weatherData, index: index),
|
||||
transition: Transition.downToUp,
|
||||
),
|
||||
child: DailyCard(
|
||||
timeDaily: timeDaily[index],
|
||||
weathercodeDaily: weatherData.weathercodeDaily![index],
|
||||
temperature2MMax: weatherData.temperature2MMax![index],
|
||||
temperature2MMin: weatherData.temperature2MMin![index],
|
||||
),
|
||||
),
|
||||
),
|
||||
timeDaily: timeDaily,
|
||||
weathercodeDaily: weathercodeDaily,
|
||||
temperature2MMax: temperature2MMax,
|
||||
temperature2MMin: temperature2MMin,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
139
lib/app/ui/widgets/weather/daily/daily_container.dart
Normal file → Executable file
139
lib/app/ui/widgets/weather/daily/daily_container.dart
Normal file → Executable file
|
@ -28,7 +28,9 @@ class _DailyContainerState extends State<DailyContainer> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final splashColor = context.theme.colorScheme.primary.withOpacity(0.4);
|
||||
final splashColor = context.theme.colorScheme.primary.withValues(
|
||||
alpha: 0.4,
|
||||
);
|
||||
const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
|
||||
|
||||
final weatherData = widget.weatherData;
|
||||
|
@ -42,20 +44,59 @@ class _DailyContainerState extends State<DailyContainer> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||
child: Column(
|
||||
children: [
|
||||
ListView.builder(
|
||||
_buildDailyListView(
|
||||
context,
|
||||
weatherData,
|
||||
weatherCodeDaily,
|
||||
labelLarge,
|
||||
),
|
||||
const Divider(),
|
||||
_buildMoreInfoButton(context, splashColor, inkWellBorderRadius),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyListView(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
List<int?> weatherCodeDaily,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 7,
|
||||
itemBuilder: (ctx, index) {
|
||||
return _buildDailyItem(
|
||||
context,
|
||||
weatherData,
|
||||
weatherCodeDaily,
|
||||
index,
|
||||
labelLarge,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyItem(
|
||||
BuildContext context,
|
||||
WeatherCard weatherData,
|
||||
List<int?> weatherCodeDaily,
|
||||
int index,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
final splashColor = context.theme.colorScheme.primary.withValues(
|
||||
alpha: 0.4,
|
||||
);
|
||||
const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
|
||||
|
||||
return InkWell(
|
||||
splashColor: splashColor,
|
||||
borderRadius: inkWellBorderRadius,
|
||||
onTap:
|
||||
() => Get.to(
|
||||
() => DailyCardInfo(
|
||||
weatherData: weatherData,
|
||||
index: index,
|
||||
),
|
||||
onTap: () => Get.to(
|
||||
() => DailyCardInfo(weatherData: weatherData, index: index),
|
||||
transition: Transition.downToUp,
|
||||
),
|
||||
child: Container(
|
||||
|
@ -63,7 +104,21 @@ class _DailyContainerState extends State<DailyContainer> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
_buildDayText(weatherData, index, labelLarge),
|
||||
_buildWeatherInfo(weatherCodeDaily, index, labelLarge),
|
||||
_buildTemperatureRange(weatherData, index, labelLarge),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDayText(
|
||||
WeatherCard weatherData,
|
||||
int index,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
return Expanded(
|
||||
child: Text(
|
||||
DateFormat.EEEE(
|
||||
locale.languageCode,
|
||||
|
@ -71,60 +126,70 @@ class _DailyContainerState extends State<DailyContainer> {
|
|||
style: labelLarge,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherInfo(
|
||||
List<int?> weatherCodeDaily,
|
||||
int index,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
final weatherCode = weatherCodeDaily[index];
|
||||
if (weatherCode == null) {
|
||||
return const Expanded(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
statusWeather.getImage7Day(
|
||||
weatherCodeDaily[index],
|
||||
),
|
||||
scale: 3,
|
||||
),
|
||||
Image.asset(statusWeather.getImage7Day(weatherCode), scale: 3),
|
||||
const Gap(5),
|
||||
Expanded(
|
||||
child: Text(
|
||||
statusWeather.getText(
|
||||
weatherCodeDaily[index],
|
||||
),
|
||||
statusWeather.getText(weatherCode),
|
||||
style: labelLarge,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemperatureRange(
|
||||
WeatherCard weatherData,
|
||||
int index,
|
||||
TextStyle? labelLarge,
|
||||
) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
statusData.getDegree(
|
||||
(weatherData.temperature2MMin ?? [])[index]
|
||||
?.round(),
|
||||
(weatherData.temperature2MMax ?? [])[index]?.round(),
|
||||
),
|
||||
style: labelLarge,
|
||||
),
|
||||
Text(' / ', style: labelLarge),
|
||||
Text(
|
||||
statusData.getDegree(
|
||||
(weatherData.temperature2MMax ?? [])[index]
|
||||
?.round(),
|
||||
(weatherData.temperature2MMin ?? [])[index]?.round(),
|
||||
),
|
||||
style: labelLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
InkWell(
|
||||
}
|
||||
|
||||
Widget _buildMoreInfoButton(
|
||||
BuildContext context,
|
||||
Color splashColor,
|
||||
BorderRadius inkWellBorderRadius,
|
||||
) {
|
||||
return InkWell(
|
||||
splashColor: splashColor,
|
||||
borderRadius: inkWellBorderRadius,
|
||||
onTap: widget.onTap,
|
||||
|
@ -132,14 +197,10 @@ class _DailyContainerState extends State<DailyContainer> {
|
|||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
'weatherMore'.tr,
|
||||
style: textTheme.titleMedium,
|
||||
style: context.textTheme.titleMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
19
lib/app/ui/widgets/weather/desc/desc.dart
Normal file → Executable file
19
lib/app/ui/widgets/weather/desc/desc.dart
Normal file → Executable file
|
@ -10,6 +10,7 @@ class DescWeather extends StatefulWidget {
|
|||
required this.desc,
|
||||
this.message = '',
|
||||
});
|
||||
|
||||
final String imageName;
|
||||
final String value;
|
||||
final String desc;
|
||||
|
@ -26,13 +27,24 @@ class _DescWeatherState extends State<DescWeather> {
|
|||
Widget build(BuildContext context) {
|
||||
final textTheme = context.textTheme;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => hide = !hide),
|
||||
onTap: _toggleDescriptionVisibility,
|
||||
child: Tooltip(
|
||||
message: widget.message,
|
||||
child: SizedBox(
|
||||
height: 90,
|
||||
width: 100,
|
||||
child: Column(
|
||||
child: _buildContent(textTheme),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleDescriptionVisibility() {
|
||||
setState(() => hide = !hide);
|
||||
}
|
||||
|
||||
Widget _buildContent(TextTheme textTheme) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(widget.imageName, scale: 20),
|
||||
|
@ -51,9 +63,6 @@ class _DescWeatherState extends State<DescWeather> {
|
|||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
384
lib/app/ui/widgets/weather/desc/desc_container.dart
Normal file → Executable file
384
lib/app/ui/widgets/weather/desc/desc_container.dart
Normal file → Executable file
|
@ -50,7 +50,6 @@ class DescContainer extends StatefulWidget {
|
|||
final double? dewpoint2M;
|
||||
final int? precipitationProbability;
|
||||
final double? shortwaveRadiation;
|
||||
|
||||
final double? apparentTemperatureMin;
|
||||
final double? apparentTemperatureMax;
|
||||
final double? uvIndexMax;
|
||||
|
@ -60,7 +59,6 @@ class DescContainer extends StatefulWidget {
|
|||
final int? precipitationProbabilityMax;
|
||||
final double? rainSum;
|
||||
final double? precipitationSum;
|
||||
|
||||
final bool initiallyExpanded;
|
||||
final String title;
|
||||
|
||||
|
@ -74,230 +72,192 @@ class _DescContainerState extends State<DescContainer> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dewpoint2M = widget.dewpoint2M?.round();
|
||||
final feels = widget.feels;
|
||||
final visibility = widget.visibility;
|
||||
final direction = widget.direction;
|
||||
final wind = widget.wind;
|
||||
final windgusts = widget.windgusts;
|
||||
final evaporation = widget.evaporation;
|
||||
final precipitation = widget.precipitation;
|
||||
final rain = widget.rain;
|
||||
final precipitationProbability = widget.precipitationProbability;
|
||||
final humidity = widget.humidity;
|
||||
final cloudcover = widget.cloudcover;
|
||||
final pressure = widget.pressure;
|
||||
final uvIndex = widget.uvIndex;
|
||||
final shortwaveRadiation = widget.shortwaveRadiation;
|
||||
|
||||
final apparentTemperatureMin = widget.apparentTemperatureMin;
|
||||
final apparentTemperatureMax = widget.apparentTemperatureMax;
|
||||
final uvIndexMax = widget.uvIndexMax;
|
||||
final windDirection10MDominant = widget.windDirection10MDominant;
|
||||
final windSpeed10MMax = widget.windSpeed10MMax;
|
||||
final windGusts10MMax = widget.windGusts10MMax;
|
||||
final precipitationProbabilityMax = widget.precipitationProbabilityMax;
|
||||
final rainSum = widget.rainSum;
|
||||
final precipitationSum = widget.precipitationSum;
|
||||
|
||||
final initiallyExpanded = widget.initiallyExpanded;
|
||||
final title = widget.title;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
child: ExpansionTile(
|
||||
shape: const Border(),
|
||||
title: Text(title, style: context.textTheme.labelLarge),
|
||||
initiallyExpanded: initiallyExpanded,
|
||||
title: Text(widget.title, style: context.textTheme.labelLarge),
|
||||
initiallyExpanded: widget.initiallyExpanded,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
spacing: 5,
|
||||
children: [
|
||||
apparentTemperatureMin == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/cold.png',
|
||||
value: statusData.getDegree(
|
||||
apparentTemperatureMin.round(),
|
||||
),
|
||||
desc: 'apparentTemperatureMin'.tr,
|
||||
),
|
||||
apparentTemperatureMax == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/hot.png',
|
||||
value: statusData.getDegree(
|
||||
apparentTemperatureMax.round(),
|
||||
),
|
||||
desc: 'apparentTemperatureMax'.tr,
|
||||
),
|
||||
uvIndexMax == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/uv.png',
|
||||
value: '${uvIndexMax.round()}',
|
||||
desc: 'uvIndex'.tr,
|
||||
message: message.getUvIndex(uvIndexMax.round()),
|
||||
),
|
||||
windDirection10MDominant == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/windsock.png',
|
||||
value: '$windDirection10MDominant°',
|
||||
desc: 'direction'.tr,
|
||||
message: message.getDirection(windDirection10MDominant),
|
||||
),
|
||||
windSpeed10MMax == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/wind.png',
|
||||
value: statusData.getSpeed(windSpeed10MMax.round()),
|
||||
desc: 'wind'.tr,
|
||||
),
|
||||
windGusts10MMax == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/windgusts.png',
|
||||
value: statusData.getSpeed(windGusts10MMax.round()),
|
||||
desc: 'windgusts'.tr,
|
||||
),
|
||||
precipitationProbabilityMax == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/precipitation_probability.png',
|
||||
value: '$precipitationProbabilityMax%',
|
||||
desc: 'precipitationProbability'.tr,
|
||||
),
|
||||
rainSum == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/water.png',
|
||||
value: statusData.getPrecipitation(rainSum),
|
||||
desc: 'rain'.tr,
|
||||
),
|
||||
precipitationSum == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/rainfall.png',
|
||||
value: statusData.getPrecipitation(precipitationSum),
|
||||
desc: 'precipitation'.tr,
|
||||
),
|
||||
dewpoint2M == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/dew.png',
|
||||
value: statusData.getDegree(dewpoint2M.round()),
|
||||
desc: 'dewpoint'.tr,
|
||||
),
|
||||
feels == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/temperature.png',
|
||||
value: statusData.getDegree(feels.round()),
|
||||
desc: 'feels'.tr,
|
||||
),
|
||||
visibility == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/fog.png',
|
||||
value: statusData.getVisibility(visibility),
|
||||
desc: 'visibility'.tr,
|
||||
),
|
||||
direction == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/windsock.png',
|
||||
value: '$direction°',
|
||||
desc: 'direction'.tr,
|
||||
message: message.getDirection(direction),
|
||||
),
|
||||
wind == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/wind.png',
|
||||
value: statusData.getSpeed(wind.round()),
|
||||
desc: 'wind'.tr,
|
||||
),
|
||||
windgusts == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/windgusts.png',
|
||||
value: statusData.getSpeed(windgusts.round()),
|
||||
desc: 'windgusts'.tr,
|
||||
),
|
||||
evaporation == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/evaporation.png',
|
||||
value: statusData.getPrecipitation(evaporation.abs()),
|
||||
desc: 'evaporation'.tr,
|
||||
),
|
||||
precipitation == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/rainfall.png',
|
||||
value: statusData.getPrecipitation(precipitation),
|
||||
desc: 'precipitation'.tr,
|
||||
),
|
||||
rain == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/water.png',
|
||||
value: statusData.getPrecipitation(rain),
|
||||
desc: 'rain'.tr,
|
||||
),
|
||||
precipitationProbability == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/precipitation_probability.png',
|
||||
value: '$precipitationProbability%',
|
||||
desc: 'precipitationProbability'.tr,
|
||||
),
|
||||
humidity == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/humidity.png',
|
||||
value: '$humidity%',
|
||||
desc: 'humidity'.tr,
|
||||
),
|
||||
cloudcover == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/cloudy.png',
|
||||
value: '$cloudcover%',
|
||||
desc: 'cloudcover'.tr,
|
||||
),
|
||||
pressure == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/atmospheric.png',
|
||||
value: statusData.getPressure(pressure.round()),
|
||||
desc: 'pressure'.tr,
|
||||
message: message.getPressure(pressure.round()),
|
||||
),
|
||||
uvIndex == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/uv.png',
|
||||
value: '${uvIndex.round()}',
|
||||
desc: 'uvIndex'.tr,
|
||||
message: message.getUvIndex(uvIndex.round()),
|
||||
),
|
||||
shortwaveRadiation == null
|
||||
? Container()
|
||||
: DescWeather(
|
||||
imageName: 'assets/images/shortwave_radiation.png',
|
||||
value: '${shortwaveRadiation.round()} ${'W/m2'.tr}',
|
||||
desc: 'shortwaveRadiation'.tr,
|
||||
),
|
||||
],
|
||||
children: _buildWeatherDescriptions(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildWeatherDescriptions(BuildContext context) {
|
||||
final List<Widget> descriptions = [];
|
||||
|
||||
void addDescriptionIfNotNull({
|
||||
required dynamic value,
|
||||
required String imageName,
|
||||
required String desc,
|
||||
String? message,
|
||||
}) {
|
||||
if (value != null &&
|
||||
value != '' &&
|
||||
value != 'null°C' &&
|
||||
value != 'null°F' &&
|
||||
value != 'null°' &&
|
||||
value != 'null%' &&
|
||||
value != 'null ${'W/m2'.tr}') {
|
||||
descriptions.add(
|
||||
DescWeather(
|
||||
imageName: imageName,
|
||||
value: value.toString(),
|
||||
desc: desc,
|
||||
message: message ?? '',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
descriptions.add(Container());
|
||||
}
|
||||
}
|
||||
|
||||
final weatherData = [
|
||||
{
|
||||
'value': statusData.getDegree(widget.apparentTemperatureMin?.round()),
|
||||
'imageName': 'assets/images/cold.png',
|
||||
'desc': 'apparentTemperatureMin'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getDegree(widget.apparentTemperatureMax?.round()),
|
||||
'imageName': 'assets/images/hot.png',
|
||||
'desc': 'apparentTemperatureMax'.tr,
|
||||
},
|
||||
{
|
||||
'value': widget.uvIndexMax?.round(),
|
||||
'imageName': 'assets/images/uv.png',
|
||||
'desc': 'uvIndex'.tr,
|
||||
'message': message.getUvIndex(widget.uvIndexMax?.round()),
|
||||
},
|
||||
{
|
||||
'value': '${widget.windDirection10MDominant}°',
|
||||
'imageName': 'assets/images/windsock.png',
|
||||
'desc': 'direction'.tr,
|
||||
'message': message.getDirection(widget.windDirection10MDominant),
|
||||
},
|
||||
{
|
||||
'value': statusData.getSpeed(widget.windSpeed10MMax?.round()),
|
||||
'imageName': 'assets/images/wind.png',
|
||||
'desc': 'wind'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getSpeed(widget.windGusts10MMax?.round()),
|
||||
'imageName': 'assets/images/windgusts.png',
|
||||
'desc': 'windgusts'.tr,
|
||||
},
|
||||
{
|
||||
'value': '${widget.precipitationProbabilityMax}%',
|
||||
'imageName': 'assets/images/precipitation_probability.png',
|
||||
'desc': 'precipitationProbability'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPrecipitation(widget.rainSum),
|
||||
'imageName': 'assets/images/water.png',
|
||||
'desc': 'rain'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPrecipitation(widget.precipitationSum),
|
||||
'imageName': 'assets/images/rainfall.png',
|
||||
'desc': 'precipitation'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getDegree(widget.dewpoint2M?.round()),
|
||||
'imageName': 'assets/images/dew.png',
|
||||
'desc': 'dewpoint'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getDegree(widget.feels?.round()),
|
||||
'imageName': 'assets/images/temperature.png',
|
||||
'desc': 'feels'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getVisibility(widget.visibility),
|
||||
'imageName': 'assets/images/fog.png',
|
||||
'desc': 'visibility'.tr,
|
||||
},
|
||||
{
|
||||
'value': '${widget.direction}°',
|
||||
'imageName': 'assets/images/windsock.png',
|
||||
'desc': 'direction'.tr,
|
||||
'message': message.getDirection(widget.direction),
|
||||
},
|
||||
{
|
||||
'value': statusData.getSpeed(widget.wind?.round()),
|
||||
'imageName': 'assets/images/wind.png',
|
||||
'desc': 'wind'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getSpeed(widget.windgusts?.round()),
|
||||
'imageName': 'assets/images/windgusts.png',
|
||||
'desc': 'windgusts'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPrecipitation(widget.evaporation?.abs()),
|
||||
'imageName': 'assets/images/evaporation.png',
|
||||
'desc': 'evaporation'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPrecipitation(widget.precipitation),
|
||||
'imageName': 'assets/images/rainfall.png',
|
||||
'desc': 'precipitation'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPrecipitation(widget.rain),
|
||||
'imageName': 'assets/images/water.png',
|
||||
'desc': 'rain'.tr,
|
||||
},
|
||||
{
|
||||
'value': '${widget.precipitationProbability}%',
|
||||
'imageName': 'assets/images/precipitation_probability.png',
|
||||
'desc': 'precipitationProbability'.tr,
|
||||
},
|
||||
{
|
||||
'value': '${widget.humidity}%',
|
||||
'imageName': 'assets/images/humidity.png',
|
||||
'desc': 'humidity'.tr,
|
||||
},
|
||||
{
|
||||
'value': '${widget.cloudcover}%',
|
||||
'imageName': 'assets/images/cloudy.png',
|
||||
'desc': 'cloudcover'.tr,
|
||||
},
|
||||
{
|
||||
'value': statusData.getPressure(widget.pressure?.round()),
|
||||
'imageName': 'assets/images/atmospheric.png',
|
||||
'desc': 'pressure'.tr,
|
||||
'message': message.getPressure(widget.pressure?.round()),
|
||||
},
|
||||
{
|
||||
'value': widget.uvIndex?.round(),
|
||||
'imageName': 'assets/images/uv.png',
|
||||
'desc': 'uvIndex'.tr,
|
||||
'message': message.getUvIndex(widget.uvIndex?.round()),
|
||||
},
|
||||
{
|
||||
'value': '${widget.shortwaveRadiation?.round()} ${'W/m2'.tr}',
|
||||
'imageName': 'assets/images/shortwave_radiation.png',
|
||||
'desc': 'shortwaveRadiation'.tr,
|
||||
},
|
||||
];
|
||||
|
||||
for (var data in weatherData) {
|
||||
addDescriptionIfNotNull(
|
||||
value: data['value'],
|
||||
imageName: '${data['imageName']}',
|
||||
desc: '${data['desc']}',
|
||||
message: '${data['message']}',
|
||||
);
|
||||
}
|
||||
|
||||
return descriptions;
|
||||
}
|
||||
}
|
||||
|
|
34
lib/app/ui/widgets/weather/desc/message.dart
Normal file → Executable file
34
lib/app/ui/widgets/weather/desc/message.dart
Normal file → Executable file
|
@ -2,7 +2,20 @@ import 'package:get/get.dart';
|
|||
|
||||
class Message {
|
||||
String getPressure(int? pressure) {
|
||||
if (pressure != null) {
|
||||
return _getPressureDescription(pressure);
|
||||
}
|
||||
|
||||
String getUvIndex(int? uvIndex) {
|
||||
return _getUvIndexDescription(uvIndex);
|
||||
}
|
||||
|
||||
String getDirection(int? direction) {
|
||||
return _getDirectionDescription(direction);
|
||||
}
|
||||
|
||||
String _getPressureDescription(int? pressure) {
|
||||
if (pressure == null) return '';
|
||||
|
||||
if (pressure < 1000) {
|
||||
return 'low'.tr;
|
||||
} else if (pressure > 1020) {
|
||||
|
@ -10,13 +23,11 @@ class Message {
|
|||
} else {
|
||||
return 'normal'.tr;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String getUvIndex(int? uvIndex) {
|
||||
if (uvIndex != null) {
|
||||
String _getUvIndexDescription(int? uvIndex) {
|
||||
if (uvIndex == null) return '';
|
||||
|
||||
if (uvIndex < 3) {
|
||||
return 'uvLow'.tr;
|
||||
} else if (uvIndex < 6) {
|
||||
|
@ -28,13 +39,11 @@ class Message {
|
|||
} else {
|
||||
return 'uvExtreme'.tr;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String getDirection(int? direction) {
|
||||
if (direction != null) {
|
||||
String _getDirectionDescription(int? direction) {
|
||||
if (direction == null) return '';
|
||||
|
||||
if (direction >= 337.5 || direction < 22.5) {
|
||||
return 'north'.tr;
|
||||
} else if (direction >= 22.5 && direction < 67.5) {
|
||||
|
@ -52,8 +61,5 @@ class Message {
|
|||
} else {
|
||||
return 'northwest'.tr;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
51
lib/app/ui/widgets/weather/hourly.dart
Normal file → Executable file
51
lib/app/ui/widgets/weather/hourly.dart
Normal file → Executable file
|
@ -14,6 +14,7 @@ class Hourly extends StatefulWidget {
|
|||
required this.timeDay,
|
||||
required this.timeNight,
|
||||
});
|
||||
|
||||
final String time;
|
||||
final String timeDay;
|
||||
final String timeNight;
|
||||
|
@ -32,35 +33,45 @@ class _HourlyState extends State<Hourly> {
|
|||
Widget build(BuildContext context) {
|
||||
final textTheme = context.textTheme;
|
||||
final time = widget.time;
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Column(
|
||||
_buildTimeText(textTheme, time),
|
||||
_buildWeatherImage(),
|
||||
_buildTemperatureText(textTheme),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimeText(TextTheme textTheme, String time) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(statusData.getTimeFormat(time), style: textTheme.labelLarge),
|
||||
Text(
|
||||
DateFormat(
|
||||
'E',
|
||||
locale.languageCode,
|
||||
).format(DateTime.tryParse(time)!),
|
||||
DateFormat('E', locale.languageCode).format(DateTime.tryParse(time)!),
|
||||
style: textTheme.labelLarge?.copyWith(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
Image.asset(
|
||||
statusWeather.getImageToday(
|
||||
widget.weather,
|
||||
time,
|
||||
widget.timeDay,
|
||||
widget.timeNight,
|
||||
),
|
||||
scale: 3,
|
||||
),
|
||||
Text(
|
||||
statusData.getDegree(widget.degree.round()),
|
||||
style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherImage() {
|
||||
return Image.asset(
|
||||
statusWeather.getImageToday(
|
||||
widget.weather,
|
||||
widget.time,
|
||||
widget.timeDay,
|
||||
widget.timeNight,
|
||||
),
|
||||
scale: 3,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemperatureText(TextTheme textTheme) {
|
||||
return Text(
|
||||
statusData.getDegree(widget.degree.round()),
|
||||
style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
159
lib/app/ui/widgets/weather/now.dart
Normal file → Executable file
159
lib/app/ui/widgets/weather/now.dart
Normal file → Executable file
|
@ -18,6 +18,7 @@ class Now extends StatefulWidget {
|
|||
required this.tempMin,
|
||||
required this.feels,
|
||||
});
|
||||
|
||||
final String time;
|
||||
final String timeDay;
|
||||
final String timeNight;
|
||||
|
@ -38,49 +39,32 @@ class _NowState extends State<Now> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return largeElement
|
||||
? Padding(
|
||||
? _buildLargeElementLayout(context)
|
||||
: _buildCompactElementLayout(context);
|
||||
}
|
||||
|
||||
Widget _buildLargeElementLayout(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Gap(15),
|
||||
Image(
|
||||
image: AssetImage(
|
||||
statusWeather.getImageNow(
|
||||
widget.weather,
|
||||
widget.time,
|
||||
widget.timeDay,
|
||||
widget.timeNight,
|
||||
),
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
height: 200,
|
||||
),
|
||||
Text(
|
||||
'${roundDegree ? widget.degree.round() : widget.degree}',
|
||||
style: context.textTheme.displayLarge?.copyWith(
|
||||
fontSize: 90,
|
||||
fontWeight: FontWeight.w800,
|
||||
shadows: const [Shadow(blurRadius: 15, offset: Offset(5, 5))],
|
||||
),
|
||||
),
|
||||
_buildWeatherImage(200),
|
||||
_buildTemperatureText(context, widget.degree, 90),
|
||||
Text(
|
||||
statusWeather.getText(widget.weather),
|
||||
style: context.textTheme.titleLarge,
|
||||
),
|
||||
const Gap(5),
|
||||
Text(
|
||||
DateFormat.MMMMEEEEd(
|
||||
locale.languageCode,
|
||||
).format(DateTime.parse(widget.time)),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
_buildDateText(context),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Card(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactElementLayout(BuildContext context) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
@ -96,22 +80,77 @@ class _NowState extends State<Now> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.MMMMEEEEd(
|
||||
locale.languageCode,
|
||||
).format(DateTime.parse(widget.time)),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
_buildDateText(context),
|
||||
const Gap(5),
|
||||
Text(
|
||||
statusWeather.getText(widget.weather),
|
||||
style: context.textTheme.titleLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
|
||||
),
|
||||
_buildFeelsLikeText(context),
|
||||
const Gap(30),
|
||||
_buildTemperatureCompactText(context, widget.degree),
|
||||
const Gap(5),
|
||||
_buildMinMaxTemperatureText(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
_buildWeatherImage(140),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWeatherImage(double height) {
|
||||
return Image(
|
||||
image: AssetImage(
|
||||
statusWeather.getImageNow(
|
||||
widget.weather,
|
||||
widget.time,
|
||||
widget.timeDay,
|
||||
widget.timeNight,
|
||||
),
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemperatureText(
|
||||
BuildContext context,
|
||||
double degree,
|
||||
double? fontSize,
|
||||
) {
|
||||
return Text(
|
||||
'${roundDegree ? degree.round() : degree}',
|
||||
style: context.textTheme.displayLarge?.copyWith(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.w800,
|
||||
shadows: const [Shadow(blurRadius: 15, offset: Offset(5, 5))],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemperatureCompactText(BuildContext context, double degree) {
|
||||
return Text(
|
||||
statusData.getDegree(roundDegree ? widget.degree.round() : widget.degree),
|
||||
style: context.textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateText(BuildContext context) {
|
||||
return Text(
|
||||
DateFormat.MMMMEEEEd(
|
||||
locale.languageCode,
|
||||
).format(DateTime.parse(widget.time)),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: Colors.grey),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFeelsLikeText(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('feels'.tr, style: context.textTheme.bodyMedium),
|
||||
|
@ -121,18 +160,11 @@ class _NowState extends State<Now> {
|
|||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(30),
|
||||
Text(
|
||||
statusData.getDegree(
|
||||
roundDegree ? widget.degree.round() : widget.degree,
|
||||
),
|
||||
style: context.textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Row(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMinMaxTemperatureText(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
|
@ -145,25 +177,6 @@ class _NowState extends State<Now> {
|
|||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Image(
|
||||
image: AssetImage(
|
||||
statusWeather.getImageNow(
|
||||
widget.weather,
|
||||
widget.time,
|
||||
widget.timeDay,
|
||||
widget.timeNight,
|
||||
),
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
height: 140,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
87
lib/app/ui/widgets/weather/status/status_data.dart
Normal file → Executable file
87
lib/app/ui/widgets/weather/status/status_data.dart
Normal file → Executable file
|
@ -4,7 +4,35 @@ import 'package:rain/main.dart';
|
|||
import 'package:timezone/timezone.dart';
|
||||
|
||||
class StatusData {
|
||||
String getDegree(degree) {
|
||||
String getDegree(dynamic degree) {
|
||||
return _formatDegree(degree);
|
||||
}
|
||||
|
||||
String getSpeed(int? speed) {
|
||||
return _formatSpeed(speed);
|
||||
}
|
||||
|
||||
String getPressure(int? pressure) {
|
||||
return _formatPressure(pressure);
|
||||
}
|
||||
|
||||
String getVisibility(double? length) {
|
||||
return _formatVisibility(length);
|
||||
}
|
||||
|
||||
String getPrecipitation(double? precipitation) {
|
||||
return _formatPrecipitation(precipitation);
|
||||
}
|
||||
|
||||
String getTimeFormat(String time) {
|
||||
return _formatTime(time);
|
||||
}
|
||||
|
||||
String getTimeFormatTz(TZDateTime time) {
|
||||
return _formatTimeTz(time);
|
||||
}
|
||||
|
||||
String _formatDegree(dynamic degree) {
|
||||
switch (settings.degrees) {
|
||||
case 'celsius':
|
||||
return '$degree°C';
|
||||
|
@ -15,11 +43,13 @@ class StatusData {
|
|||
}
|
||||
}
|
||||
|
||||
String getSpeed(int? speed) {
|
||||
String _formatSpeed(int? speed) {
|
||||
if (speed == null) return '';
|
||||
|
||||
switch (settings.measurements) {
|
||||
case 'metric':
|
||||
return settings.wind == 'm/s'
|
||||
? '${(speed! * (5 / 18)).toPrecision(1)} ${'m/s'.tr}'
|
||||
? '${(speed * (5 / 18)).toPrecision(1)} ${'m/s'.tr}'
|
||||
: '$speed ${'kph'.tr}';
|
||||
case 'imperial':
|
||||
return '$speed ${'mph'.tr}';
|
||||
|
@ -28,28 +58,38 @@ class StatusData {
|
|||
}
|
||||
}
|
||||
|
||||
String getPressure(int? pressure) {
|
||||
String _formatPressure(int? pressure) {
|
||||
if (pressure == null) return '';
|
||||
|
||||
return settings.pressure == 'mmHg'
|
||||
? '${(pressure! * (3 / 4)).toPrecision(1)} ${'mmHg'.tr}'
|
||||
? '${(pressure * (3 / 4)).toPrecision(1)} ${'mmHg'.tr}'
|
||||
: '$pressure ${'hPa'.tr}';
|
||||
}
|
||||
|
||||
String getVisibility(double? length) {
|
||||
if (length != null) {
|
||||
String _formatVisibility(double? length) {
|
||||
if (length == null) return '';
|
||||
|
||||
switch (settings.measurements) {
|
||||
case 'metric':
|
||||
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
||||
return _formatMetricVisibility(length);
|
||||
case 'imperial':
|
||||
return '${length > 5280 ? (length / 5280).round() : (length / 5280).toStringAsFixed(2)} ${'mi'.tr}';
|
||||
return _formatImperialVisibility(length);
|
||||
default:
|
||||
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
return _formatMetricVisibility(length);
|
||||
}
|
||||
}
|
||||
|
||||
String getPrecipitation(double? precipitation) {
|
||||
String _formatMetricVisibility(double length) {
|
||||
return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}';
|
||||
}
|
||||
|
||||
String _formatImperialVisibility(double length) {
|
||||
return '${length > 5280 ? (length / 5280).round() : (length / 5280).toStringAsFixed(2)} ${'mi'.tr}';
|
||||
}
|
||||
|
||||
String _formatPrecipitation(double? precipitation) {
|
||||
if (precipitation == null) return '';
|
||||
|
||||
switch (settings.measurements) {
|
||||
case 'metric':
|
||||
return '$precipitation ${'mm'.tr}';
|
||||
|
@ -60,24 +100,21 @@ class StatusData {
|
|||
}
|
||||
}
|
||||
|
||||
String getTimeFormat(String time) {
|
||||
String _formatTime(String time) {
|
||||
final parsedTime = DateTime.tryParse(time);
|
||||
if (parsedTime == null) return '';
|
||||
|
||||
switch (settings.timeformat) {
|
||||
case '12':
|
||||
return DateFormat.jm(
|
||||
locale.languageCode,
|
||||
).format(DateTime.tryParse(time)!);
|
||||
return DateFormat.jm(locale.languageCode).format(parsedTime);
|
||||
case '24':
|
||||
return DateFormat.Hm(
|
||||
locale.languageCode,
|
||||
).format(DateTime.tryParse(time)!);
|
||||
return DateFormat.Hm(locale.languageCode).format(parsedTime);
|
||||
default:
|
||||
return DateFormat.Hm(
|
||||
locale.languageCode,
|
||||
).format(DateTime.tryParse(time)!);
|
||||
return DateFormat.Hm(locale.languageCode).format(parsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
String getTimeFormatTz(TZDateTime time) {
|
||||
String _formatTimeTz(TZDateTime time) {
|
||||
switch (settings.timeformat) {
|
||||
case '12':
|
||||
return DateFormat.jm(locale.languageCode).format(time);
|
||||
|
|
519
lib/app/ui/widgets/weather/status/status_weather.dart
Normal file → Executable file
519
lib/app/ui/widgets/weather/status/status_weather.dart
Normal file → Executable file
|
@ -9,120 +9,17 @@ class StatusWeather {
|
|||
String timeDay,
|
||||
String timeNight,
|
||||
) {
|
||||
final currentTime = DateTime.parse(time);
|
||||
final day = DateTime.parse(timeDay);
|
||||
final night = DateTime.parse(timeNight);
|
||||
|
||||
final dayTime = DateTime(
|
||||
day.year,
|
||||
day.month,
|
||||
day.day,
|
||||
day.hour,
|
||||
day.minute,
|
||||
return _getImageBasedOnTime(
|
||||
weather,
|
||||
time,
|
||||
timeDay,
|
||||
timeNight,
|
||||
_getDayNightImagePaths,
|
||||
);
|
||||
final nightTime = DateTime(
|
||||
night.year,
|
||||
night.month,
|
||||
night.day,
|
||||
night.hour,
|
||||
night.minute,
|
||||
);
|
||||
|
||||
switch (weather) {
|
||||
case 0:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}sun.png';
|
||||
} else {
|
||||
return '${assetImageRoot}full-moon.png';
|
||||
}
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}cloud.png';
|
||||
} else {
|
||||
return '${assetImageRoot}moon.png';
|
||||
}
|
||||
case 45:
|
||||
case 48:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}fog.png';
|
||||
} else {
|
||||
return '${assetImageRoot}fog_moon.png';
|
||||
}
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
case 56:
|
||||
case 57:
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
case 66:
|
||||
case 67:
|
||||
return '${assetImageRoot}rain.png';
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return '${assetImageRoot}rain-fall.png';
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
return '${assetImageRoot}snow.png';
|
||||
case 95:
|
||||
return '${assetImageRoot}thunder.png';
|
||||
case 96:
|
||||
case 99:
|
||||
return '${assetImageRoot}storm.png';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String getImageNowDaily(int? weather) {
|
||||
switch (weather) {
|
||||
case 0:
|
||||
return '${assetImageRoot}sun.png';
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
return '${assetImageRoot}cloud.png';
|
||||
case 45:
|
||||
case 48:
|
||||
return '${assetImageRoot}fog.png';
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
case 56:
|
||||
case 57:
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
case 66:
|
||||
case 67:
|
||||
return '${assetImageRoot}rain.png';
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return '${assetImageRoot}rain-fall.png';
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
return '${assetImageRoot}snow.png';
|
||||
case 95:
|
||||
return '${assetImageRoot}thunder.png';
|
||||
case 96:
|
||||
case 99:
|
||||
return '${assetImageRoot}storm.png';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
return _getDailyImage(weather);
|
||||
}
|
||||
|
||||
String getImageToday(
|
||||
|
@ -130,6 +27,45 @@ class StatusWeather {
|
|||
String time,
|
||||
String timeDay,
|
||||
String timeNight,
|
||||
) {
|
||||
return _getImageBasedOnTime(
|
||||
weather,
|
||||
time,
|
||||
timeDay,
|
||||
timeNight,
|
||||
_getTodayImagePaths,
|
||||
);
|
||||
}
|
||||
|
||||
String getImage7Day(int? weather) {
|
||||
return _getDailyImage(weather, isDay: true);
|
||||
}
|
||||
|
||||
String getText(int? weather) {
|
||||
return _getWeatherText(weather);
|
||||
}
|
||||
|
||||
String getImageNotification(
|
||||
int weather,
|
||||
String time,
|
||||
String timeDay,
|
||||
String timeNight,
|
||||
) {
|
||||
return _getImageBasedOnTime(
|
||||
weather,
|
||||
time,
|
||||
timeDay,
|
||||
timeNight,
|
||||
_getNotificationImagePaths,
|
||||
);
|
||||
}
|
||||
|
||||
String _getImageBasedOnTime(
|
||||
int weather,
|
||||
String time,
|
||||
String timeDay,
|
||||
String timeNight,
|
||||
Map<int, Map<bool, String>> imagePaths,
|
||||
) {
|
||||
final currentTime = DateTime.parse(time);
|
||||
final day = DateTime.parse(timeDay);
|
||||
|
@ -150,28 +86,23 @@ class StatusWeather {
|
|||
night.minute,
|
||||
);
|
||||
|
||||
final isDayTime =
|
||||
currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime);
|
||||
|
||||
return imagePaths[weather]?[isDayTime] ?? '';
|
||||
}
|
||||
|
||||
String _getDailyImage(int? weather, {bool isDay = false}) {
|
||||
switch (weather) {
|
||||
case 0:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}clear_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}clear_night.png';
|
||||
}
|
||||
return '$assetImageRoot${isDay ? 'clear_day' : 'sun'}.png';
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}cloudy_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}cloudy_night.png';
|
||||
}
|
||||
return '$assetImageRoot${isDay ? 'cloudy_day' : 'cloud'}.png';
|
||||
case 45:
|
||||
case 48:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}fog_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}fog_night.png';
|
||||
}
|
||||
return '${assetImageRoot}fog${isDay ? '_day' : ''}.png';
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
|
@ -185,77 +116,24 @@ class StatusWeather {
|
|||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}rain_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}rain_night.png';
|
||||
}
|
||||
return '${assetImageRoot}rain${isDay ? '_day' : ''}.png';
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}snow_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}snow_night.png';
|
||||
}
|
||||
return '${assetImageRoot}snow${isDay ? '_day' : ''}.png';
|
||||
case 95:
|
||||
case 96:
|
||||
case 99:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return '${assetImageRoot}thunder_day.png';
|
||||
} else {
|
||||
return '${assetImageRoot}thunder_night.png';
|
||||
}
|
||||
return '${assetImageRoot}thunder${isDay ? '_day' : ''}.png';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String getImage7Day(int? weather) {
|
||||
switch (weather) {
|
||||
case 0:
|
||||
return '${assetImageRoot}clear_day.png';
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
return '${assetImageRoot}cloudy_day.png';
|
||||
case 45:
|
||||
case 48:
|
||||
return '${assetImageRoot}fog_day.png';
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
case 56:
|
||||
case 57:
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
case 66:
|
||||
case 67:
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return '${assetImageRoot}rain_day.png';
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
return '${assetImageRoot}snow_day.png';
|
||||
case 95:
|
||||
case 96:
|
||||
case 99:
|
||||
return '${assetImageRoot}thunder_day.png';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String getText(int? weather) {
|
||||
String _getWeatherText(int? weather) {
|
||||
switch (weather) {
|
||||
case 0:
|
||||
return 'clear_sky'.tr;
|
||||
|
@ -301,82 +179,207 @@ class StatusWeather {
|
|||
}
|
||||
}
|
||||
|
||||
String getImageNotification(
|
||||
int weather,
|
||||
String time,
|
||||
String timeDay,
|
||||
String timeNight,
|
||||
) {
|
||||
final currentTime = DateTime.parse(time);
|
||||
final day = DateTime.parse(timeDay);
|
||||
final night = DateTime.parse(timeNight);
|
||||
final Map<int, Map<bool, String>> _getDayNightImagePaths = {
|
||||
0: {
|
||||
true: '${assetImageRoot}sun.png',
|
||||
false: '${assetImageRoot}full-moon.png',
|
||||
},
|
||||
1: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||
2: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||
3: {true: '${assetImageRoot}cloud.png', false: '${assetImageRoot}moon.png'},
|
||||
45: {
|
||||
true: '${assetImageRoot}fog.png',
|
||||
false: '${assetImageRoot}fog_moon.png',
|
||||
},
|
||||
48: {
|
||||
true: '${assetImageRoot}fog.png',
|
||||
false: '${assetImageRoot}fog_moon.png',
|
||||
},
|
||||
51: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
53: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
55: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
56: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
57: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
61: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
63: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
65: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
66: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
67: {true: '${assetImageRoot}rain.png', false: '${assetImageRoot}rain.png'},
|
||||
80: {
|
||||
true: '${assetImageRoot}rain-fall.png',
|
||||
false: '${assetImageRoot}rain-fall.png',
|
||||
},
|
||||
81: {
|
||||
true: '${assetImageRoot}rain-fall.png',
|
||||
false: '${assetImageRoot}rain-fall.png',
|
||||
},
|
||||
82: {
|
||||
true: '${assetImageRoot}rain-fall.png',
|
||||
false: '${assetImageRoot}rain-fall.png',
|
||||
},
|
||||
71: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
73: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
75: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
77: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
85: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
86: {true: '${assetImageRoot}snow.png', false: '${assetImageRoot}snow.png'},
|
||||
95: {
|
||||
true: '${assetImageRoot}thunder.png',
|
||||
false: '${assetImageRoot}thunder.png',
|
||||
},
|
||||
96: {
|
||||
true: '${assetImageRoot}storm.png',
|
||||
false: '${assetImageRoot}storm.png',
|
||||
},
|
||||
99: {
|
||||
true: '${assetImageRoot}storm.png',
|
||||
false: '${assetImageRoot}storm.png',
|
||||
},
|
||||
};
|
||||
|
||||
final dayTime = DateTime(
|
||||
day.year,
|
||||
day.month,
|
||||
day.day,
|
||||
day.hour,
|
||||
day.minute,
|
||||
);
|
||||
final nightTime = DateTime(
|
||||
night.year,
|
||||
night.month,
|
||||
night.day,
|
||||
night.hour,
|
||||
night.minute,
|
||||
);
|
||||
final Map<int, Map<bool, String>> _getTodayImagePaths = {
|
||||
0: {
|
||||
true: '${assetImageRoot}clear_day.png',
|
||||
false: '${assetImageRoot}clear_night.png',
|
||||
},
|
||||
1: {
|
||||
true: '${assetImageRoot}cloudy_day.png',
|
||||
false: '${assetImageRoot}cloudy_night.png',
|
||||
},
|
||||
2: {
|
||||
true: '${assetImageRoot}cloudy_day.png',
|
||||
false: '${assetImageRoot}cloudy_night.png',
|
||||
},
|
||||
3: {
|
||||
true: '${assetImageRoot}cloudy_day.png',
|
||||
false: '${assetImageRoot}cloudy_night.png',
|
||||
},
|
||||
45: {
|
||||
true: '${assetImageRoot}fog_day.png',
|
||||
false: '${assetImageRoot}fog_night.png',
|
||||
},
|
||||
48: {
|
||||
true: '${assetImageRoot}fog_day.png',
|
||||
false: '${assetImageRoot}fog_night.png',
|
||||
},
|
||||
51: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
53: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
55: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
56: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
57: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
61: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
63: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
65: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
66: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
67: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
80: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
81: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
82: {
|
||||
true: '${assetImageRoot}rain_day.png',
|
||||
false: '${assetImageRoot}rain_night.png',
|
||||
},
|
||||
71: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
73: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
75: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
77: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
85: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
86: {
|
||||
true: '${assetImageRoot}snow_day.png',
|
||||
false: '${assetImageRoot}snow_night.png',
|
||||
},
|
||||
95: {
|
||||
true: '${assetImageRoot}thunder_day.png',
|
||||
false: '${assetImageRoot}thunder_night.png',
|
||||
},
|
||||
96: {
|
||||
true: '${assetImageRoot}thunder_day.png',
|
||||
false: '${assetImageRoot}thunder_night.png',
|
||||
},
|
||||
99: {
|
||||
true: '${assetImageRoot}thunder_day.png',
|
||||
false: '${assetImageRoot}thunder_night.png',
|
||||
},
|
||||
};
|
||||
|
||||
switch (weather) {
|
||||
case 0:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return 'sun.png';
|
||||
} else {
|
||||
return 'full-moon.png';
|
||||
}
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return 'cloud.png';
|
||||
} else {
|
||||
return 'moon.png';
|
||||
}
|
||||
case 45:
|
||||
case 48:
|
||||
if (currentTime.isAfter(dayTime) && currentTime.isBefore(nightTime)) {
|
||||
return 'fog.png';
|
||||
} else {
|
||||
return 'fog_moon.png';
|
||||
}
|
||||
case 51:
|
||||
case 53:
|
||||
case 55:
|
||||
case 56:
|
||||
case 57:
|
||||
case 61:
|
||||
case 63:
|
||||
case 65:
|
||||
case 66:
|
||||
case 67:
|
||||
return 'rain.png';
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
return 'rain-fall.png';
|
||||
case 71:
|
||||
case 73:
|
||||
case 75:
|
||||
case 77:
|
||||
case 85:
|
||||
case 86:
|
||||
return 'snow.png';
|
||||
case 95:
|
||||
return 'thunder.png';
|
||||
case 96:
|
||||
case 99:
|
||||
return 'storm.png';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
final Map<int, Map<bool, String>> _getNotificationImagePaths = {
|
||||
0: {true: 'sun.png', false: 'full-moon.png'},
|
||||
1: {true: 'cloud.png', false: 'moon.png'},
|
||||
2: {true: 'cloud.png', false: 'moon.png'},
|
||||
3: {true: 'cloud.png', false: 'moon.png'},
|
||||
45: {true: 'fog.png', false: 'fog_moon.png'},
|
||||
48: {true: 'fog.png', false: 'fog_moon.png'},
|
||||
51: {true: 'rain.png', false: 'rain.png'},
|
||||
53: {true: 'rain.png', false: 'rain.png'},
|
||||
55: {true: 'rain.png', false: 'rain.png'},
|
||||
56: {true: 'rain.png', false: 'rain.png'},
|
||||
57: {true: 'rain.png', false: 'rain.png'},
|
||||
61: {true: 'rain.png', false: 'rain.png'},
|
||||
63: {true: 'rain.png', false: 'rain.png'},
|
||||
65: {true: 'rain.png', false: 'rain.png'},
|
||||
66: {true: 'rain.png', false: 'rain.png'},
|
||||
67: {true: 'rain.png', false: 'rain.png'},
|
||||
80: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||
81: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||
82: {true: 'rain-fall.png', false: 'rain-fall.png'},
|
||||
71: {true: 'snow.png', false: 'snow.png'},
|
||||
73: {true: 'snow.png', false: 'snow.png'},
|
||||
75: {true: 'snow.png', false: 'snow.png'},
|
||||
77: {true: 'snow.png', false: 'snow.png'},
|
||||
85: {true: 'snow.png', false: 'snow.png'},
|
||||
86: {true: 'snow.png', false: 'snow.png'},
|
||||
95: {true: 'thunder.png', false: 'thunder.png'},
|
||||
96: {true: 'storm.png', false: 'storm.png'},
|
||||
99: {true: 'storm.png', false: 'storm.png'},
|
||||
};
|
||||
}
|
||||
|
|
89
lib/app/ui/widgets/weather/sunset_sunrise.dart
Normal file → Executable file
89
lib/app/ui/widgets/weather/sunset_sunrise.dart
Normal file → Executable file
|
@ -32,65 +32,54 @@ class _SunsetSunriseState extends State<SunsetSunrise> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_buildSunTimeColumn(
|
||||
context,
|
||||
'sunrise'.tr,
|
||||
style: titleSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Gap(2),
|
||||
Text(
|
||||
statusData.getTimeFormat(widget.timeSunrise),
|
||||
style: titleLarge,
|
||||
'assets/images/sunrise.png',
|
||||
titleSmall,
|
||||
titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Flexible(
|
||||
child: Image.asset('assets/images/sunrise.png', scale: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_buildSunTimeColumn(
|
||||
context,
|
||||
'sunset'.tr,
|
||||
style: titleSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Gap(2),
|
||||
Text(
|
||||
statusData.getTimeFormat(widget.timeSunset),
|
||||
style: titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Flexible(
|
||||
child: Image.asset('assets/images/sunset.png', scale: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
'assets/images/sunset.png',
|
||||
titleSmall,
|
||||
titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSunTimeColumn(
|
||||
BuildContext context,
|
||||
String label,
|
||||
String time,
|
||||
String imagePath,
|
||||
TextStyle? labelStyle,
|
||||
TextStyle? timeStyle,
|
||||
) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: labelStyle, overflow: TextOverflow.ellipsis),
|
||||
const Gap(2),
|
||||
Text(time, style: timeStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(5),
|
||||
Flexible(child: Image.asset(imagePath, scale: 10)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
0
lib/app/utils/color_converter.dart
Normal file → Executable file
0
lib/app/utils/color_converter.dart
Normal file → Executable file
0
lib/app/utils/device_info.dart
Normal file → Executable file
0
lib/app/utils/device_info.dart
Normal file → Executable file
53
lib/app/utils/notification.dart
Normal file → Executable file
53
lib/app/utils/notification.dart
Normal file → Executable file
|
@ -4,31 +4,22 @@ import 'package:rain/main.dart';
|
|||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
class NotificationShow {
|
||||
Future showNotification(
|
||||
static const String _channelId = 'Rain';
|
||||
static const String _channelName = 'DARK NIGHT';
|
||||
|
||||
Future<void> showNotification(
|
||||
int id,
|
||||
String title,
|
||||
String body,
|
||||
DateTime date,
|
||||
String icon,
|
||||
) async {
|
||||
final imagePath = await WeatherController().getLocalImagePath(icon);
|
||||
try {
|
||||
final imagePath = await _getLocalImagePath(icon);
|
||||
final notificationDetails = await _buildNotificationDetails(imagePath);
|
||||
final scheduledTime = _getScheduledTime(date);
|
||||
|
||||
AndroidNotificationDetails androidNotificationDetails =
|
||||
AndroidNotificationDetails(
|
||||
'Rain',
|
||||
'DARK NIGHT',
|
||||
priority: Priority.high,
|
||||
importance: Importance.max,
|
||||
playSound: false,
|
||||
enableVibration: false,
|
||||
largeIcon: FilePathAndroidBitmap(imagePath),
|
||||
);
|
||||
NotificationDetails notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails,
|
||||
);
|
||||
|
||||
var scheduledTime = tz.TZDateTime.from(date, tz.local);
|
||||
flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
|
@ -37,5 +28,31 @@ class NotificationShow {
|
|||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
payload: imagePath,
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error showing notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _getLocalImagePath(String icon) async {
|
||||
return await WeatherController().getLocalImagePath(icon);
|
||||
}
|
||||
|
||||
Future<NotificationDetails> _buildNotificationDetails(
|
||||
String imagePath,
|
||||
) async {
|
||||
final androidNotificationDetails = AndroidNotificationDetails(
|
||||
_channelId,
|
||||
_channelName,
|
||||
priority: Priority.high,
|
||||
importance: Importance.max,
|
||||
playSound: false,
|
||||
enableVibration: false,
|
||||
largeIcon: FilePathAndroidBitmap(imagePath),
|
||||
);
|
||||
return NotificationDetails(android: androidNotificationDetails);
|
||||
}
|
||||
|
||||
tz.TZDateTime _getScheduledTime(DateTime date) {
|
||||
return tz.TZDateTime.from(date, tz.local);
|
||||
}
|
||||
}
|
||||
|
|
12
lib/app/utils/show_snack_bar.dart
Normal file → Executable file
12
lib/app/utils/show_snack_bar.dart
Normal file → Executable file
|
@ -1,20 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
final globalKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<ScaffoldMessengerState> globalKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
|
||||
void showSnackBar({required String content, Function? onPressed}) {
|
||||
void showSnackBar({required String content, VoidCallback? onPressed}) {
|
||||
globalKey.currentState?.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(content),
|
||||
action:
|
||||
onPressed != null
|
||||
? SnackBarAction(
|
||||
label: 'settings'.tr,
|
||||
onPressed: () {
|
||||
onPressed();
|
||||
},
|
||||
)
|
||||
? SnackBarAction(label: 'settings'.tr, onPressed: onPressed)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
|
24
lib/main.dart
Normal file → Executable file
24
lib/main.dart
Normal file → Executable file
|
@ -86,24 +86,24 @@ void callbackDispatcher() {
|
|||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await _initializeApp();
|
||||
await initializeApp();
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
Future<void> _initializeApp() async {
|
||||
_setupConnectivityListener();
|
||||
await _initializeTimeZone();
|
||||
await _initializeIsar();
|
||||
await _initializeNotifications();
|
||||
Future<void> initializeApp() async {
|
||||
setupConnectivityListener();
|
||||
await initializeTimeZone();
|
||||
await initializeIsar();
|
||||
await initializeNotifications();
|
||||
if (Platform.isAndroid) {
|
||||
await _setOptimalDisplayMode();
|
||||
await setOptimalDisplayMode();
|
||||
Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
|
||||
HomeWidget.setAppGroupId(appGroupId);
|
||||
}
|
||||
DeviceFeature().init();
|
||||
}
|
||||
|
||||
void _setupConnectivityListener() {
|
||||
void setupConnectivityListener() {
|
||||
Connectivity().onConnectivityChanged.listen((result) {
|
||||
isOnline.value =
|
||||
result.contains(ConnectivityResult.none)
|
||||
|
@ -112,13 +112,13 @@ void _setupConnectivityListener() {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> _initializeTimeZone() async {
|
||||
Future<void> initializeTimeZone() async {
|
||||
final timeZoneName = await FlutterTimezone.getLocalTimezone();
|
||||
tz.initializeTimeZones();
|
||||
tz.setLocalLocation(tz.getLocation(timeZoneName));
|
||||
}
|
||||
|
||||
Future<void> _initializeIsar() async {
|
||||
Future<void> initializeIsar() async {
|
||||
isar = await Isar.open([
|
||||
SettingsSchema,
|
||||
MainWeatherCacheSchema,
|
||||
|
@ -140,7 +140,7 @@ Future<void> _initializeIsar() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _initializeNotifications() async {
|
||||
Future<void> initializeNotifications() async {
|
||||
const initializationSettings = InitializationSettings(
|
||||
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
||||
iOS: DarwinInitializationSettings(),
|
||||
|
@ -149,7 +149,7 @@ Future<void> _initializeNotifications() async {
|
|||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||
}
|
||||
|
||||
Future<void> _setOptimalDisplayMode() async {
|
||||
Future<void> setOptimalDisplayMode() async {
|
||||
final supported = await FlutterDisplayMode.supported;
|
||||
final active = await FlutterDisplayMode.active;
|
||||
final sameResolution =
|
||||
|
|
188
lib/theme/theme.dart
Normal file → Executable file
188
lib/theme/theme.dart
Normal file → Executable file
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
|
||||
final ThemeData baseLigth = ThemeData.light(useMaterial3: true);
|
||||
final ThemeData baseLight = ThemeData.light(useMaterial3: true);
|
||||
final ThemeData baseDark = ThemeData.dark(useMaterial3: true);
|
||||
|
||||
const Color lightColor = Colors.white;
|
||||
|
@ -14,6 +14,7 @@ ColorScheme colorSchemeLight = ColorScheme.fromSeed(
|
|||
seedColor: Colors.deepPurple,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
|
||||
ColorScheme colorSchemeDark = ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple,
|
||||
brightness: Brightness.dark,
|
||||
|
@ -24,65 +25,12 @@ ThemeData lightTheme(
|
|||
ColorScheme? colorScheme,
|
||||
bool edgeToEdgeAvailable,
|
||||
) {
|
||||
return baseLigth.copyWith(
|
||||
return _buildTheme(
|
||||
baseTheme: baseLight,
|
||||
brightness: Brightness.light,
|
||||
colorScheme:
|
||||
colorScheme
|
||||
?.copyWith(
|
||||
brightness: Brightness.light,
|
||||
surface: baseLigth.colorScheme.surface,
|
||||
)
|
||||
.harmonized(),
|
||||
textTheme: GoogleFonts.ubuntuTextTheme(baseLigth.textTheme),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: color,
|
||||
foregroundColor: baseLigth.colorScheme.onSurface,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemStatusBarContrastEnforced: false,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor:
|
||||
edgeToEdgeAvailable ? Colors.transparent : colorScheme?.surface,
|
||||
),
|
||||
),
|
||||
primaryColor: color,
|
||||
canvasColor: color,
|
||||
scaffoldBackgroundColor: color,
|
||||
cardTheme: baseLigth.cardTheme.copyWith(
|
||||
color: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
bottomSheetTheme: baseLigth.bottomSheetTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
),
|
||||
navigationRailTheme: baseLigth.navigationRailTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
),
|
||||
navigationBarTheme: baseLigth.navigationBarTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
),
|
||||
inputDecorationTheme: baseLigth.inputDecorationTheme.copyWith(
|
||||
labelStyle: WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
|
||||
return const TextStyle(fontSize: 14);
|
||||
}),
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
indicatorColor: Colors.black,
|
||||
colorScheme: colorScheme,
|
||||
edgeToEdgeAvailable: edgeToEdgeAvailable,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -91,63 +39,121 @@ ThemeData darkTheme(
|
|||
ColorScheme? colorScheme,
|
||||
bool edgeToEdgeAvailable,
|
||||
) {
|
||||
return baseDark.copyWith(
|
||||
return _buildTheme(
|
||||
baseTheme: baseDark,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme:
|
||||
color: color,
|
||||
colorScheme: colorScheme,
|
||||
edgeToEdgeAvailable: edgeToEdgeAvailable,
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData _buildTheme({
|
||||
required ThemeData baseTheme,
|
||||
required Brightness brightness,
|
||||
required Color? color,
|
||||
required ColorScheme? colorScheme,
|
||||
required bool edgeToEdgeAvailable,
|
||||
}) {
|
||||
final harmonizedColorScheme =
|
||||
colorScheme
|
||||
?.copyWith(
|
||||
brightness: Brightness.dark,
|
||||
surface: baseDark.colorScheme.surface,
|
||||
brightness: brightness,
|
||||
surface: baseTheme.colorScheme.surface,
|
||||
)
|
||||
.harmonized(),
|
||||
textTheme: GoogleFonts.ubuntuTextTheme(baseDark.textTheme),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: color,
|
||||
foregroundColor: baseDark.colorScheme.onSurface,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemStatusBarContrastEnforced: false,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
edgeToEdgeAvailable ? Colors.transparent : colorScheme?.surface,
|
||||
),
|
||||
.harmonized();
|
||||
|
||||
return baseTheme.copyWith(
|
||||
brightness: brightness,
|
||||
colorScheme: harmonizedColorScheme,
|
||||
textTheme: GoogleFonts.ubuntuTextTheme(baseTheme.textTheme),
|
||||
appBarTheme: _buildAppBarTheme(
|
||||
color,
|
||||
baseTheme.colorScheme.onSurface,
|
||||
edgeToEdgeAvailable,
|
||||
brightness,
|
||||
harmonizedColorScheme,
|
||||
),
|
||||
primaryColor: color,
|
||||
canvasColor: color,
|
||||
scaffoldBackgroundColor: color,
|
||||
cardTheme: baseDark.cardTheme.copyWith(
|
||||
cardTheme: _buildCardTheme(color, harmonizedColorScheme),
|
||||
bottomSheetTheme: _buildBottomSheetTheme(color, harmonizedColorScheme),
|
||||
navigationRailTheme: baseTheme.navigationRailTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
),
|
||||
navigationBarTheme: _buildNavigationBarTheme(color, harmonizedColorScheme),
|
||||
inputDecorationTheme: _buildInputDecorationTheme(),
|
||||
);
|
||||
}
|
||||
|
||||
AppBarTheme _buildAppBarTheme(
|
||||
Color? color,
|
||||
Color? onSurfaceColor,
|
||||
bool edgeToEdgeAvailable,
|
||||
Brightness brightness,
|
||||
ColorScheme? colorScheme,
|
||||
) {
|
||||
return AppBarTheme(
|
||||
backgroundColor: color,
|
||||
foregroundColor: onSurfaceColor,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness:
|
||||
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemStatusBarContrastEnforced: false,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness:
|
||||
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
edgeToEdgeAvailable ? Colors.transparent : colorScheme?.surface,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
CardThemeData _buildCardTheme(Color? color, ColorScheme? colorScheme) {
|
||||
return CardThemeData(
|
||||
color: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
bottomSheetTheme: baseDark.bottomSheetTheme.copyWith(
|
||||
);
|
||||
}
|
||||
|
||||
BottomSheetThemeData _buildBottomSheetTheme(
|
||||
Color? color,
|
||||
ColorScheme? colorScheme,
|
||||
) {
|
||||
return BottomSheetThemeData(
|
||||
backgroundColor: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
),
|
||||
navigationRailTheme: baseDark.navigationRailTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
),
|
||||
navigationBarTheme: baseDark.navigationBarTheme.copyWith(
|
||||
);
|
||||
}
|
||||
|
||||
NavigationBarThemeData _buildNavigationBarTheme(
|
||||
Color? color,
|
||||
ColorScheme? colorScheme,
|
||||
) {
|
||||
return NavigationBarThemeData(
|
||||
backgroundColor: color,
|
||||
surfaceTintColor:
|
||||
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
|
||||
),
|
||||
inputDecorationTheme: baseDark.inputDecorationTheme.copyWith(
|
||||
);
|
||||
}
|
||||
|
||||
InputDecorationTheme _buildInputDecorationTheme() {
|
||||
return InputDecorationTheme(
|
||||
labelStyle: WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
|
||||
return const TextStyle(fontSize: 14);
|
||||
}),
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
32
lib/theme/theme_controller.dart
Normal file → Executable file
32
lib/theme/theme_controller.dart
Normal file → Executable file
|
@ -4,29 +4,37 @@ import 'package:rain/app/data/db.dart';
|
|||
import 'package:rain/main.dart';
|
||||
|
||||
class ThemeController extends GetxController {
|
||||
ThemeMode get theme =>
|
||||
settings.theme == 'system'
|
||||
? ThemeMode.system
|
||||
: settings.theme == 'dark'
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light;
|
||||
ThemeMode get theme => _getThemeMode();
|
||||
|
||||
void saveOledTheme(bool isOled) {
|
||||
settings.amoledTheme = isOled;
|
||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||
_updateSetting((settings) => settings.amoledTheme = isOled);
|
||||
}
|
||||
|
||||
void saveMaterialTheme(bool isMaterial) {
|
||||
settings.materialColor = isMaterial;
|
||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||
_updateSetting((settings) => settings.materialColor = isMaterial);
|
||||
}
|
||||
|
||||
void saveTheme(String themeMode) {
|
||||
settings.theme = themeMode;
|
||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||
_updateSetting((settings) => settings.theme = themeMode);
|
||||
}
|
||||
|
||||
void changeTheme(ThemeData theme) => Get.changeTheme(theme);
|
||||
|
||||
void changeThemeMode(ThemeMode themeMode) => Get.changeThemeMode(themeMode);
|
||||
|
||||
ThemeMode _getThemeMode() {
|
||||
switch (settings.theme) {
|
||||
case 'system':
|
||||
return ThemeMode.system;
|
||||
case 'dark':
|
||||
return ThemeMode.dark;
|
||||
default:
|
||||
return ThemeMode.light;
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSetting(void Function(Settings) update) {
|
||||
update(settings);
|
||||
isar.writeTxnSync(() => isar.settings.putSync(settings));
|
||||
}
|
||||
}
|
||||
|
|
0
lib/translation/bn_in.dart
Normal file → Executable file
0
lib/translation/bn_in.dart
Normal file → Executable file
0
lib/translation/cs_cz.dart
Normal file → Executable file
0
lib/translation/cs_cz.dart
Normal file → Executable file
0
lib/translation/da_dk.dart
Normal file → Executable file
0
lib/translation/da_dk.dart
Normal file → Executable file
0
lib/translation/de_de.dart
Normal file → Executable file
0
lib/translation/de_de.dart
Normal file → Executable file
0
lib/translation/en_us.dart
Normal file → Executable file
0
lib/translation/en_us.dart
Normal file → Executable file
0
lib/translation/es_es.dart
Normal file → Executable file
0
lib/translation/es_es.dart
Normal file → Executable file
0
lib/translation/fa_ir.dart
Normal file → Executable file
0
lib/translation/fa_ir.dart
Normal file → Executable file
0
lib/translation/fr_fr.dart
Normal file → Executable file
0
lib/translation/fr_fr.dart
Normal file → Executable file
0
lib/translation/ga_ie.dart
Normal file → Executable file
0
lib/translation/ga_ie.dart
Normal file → Executable file
0
lib/translation/hi_in.dart
Normal file → Executable file
0
lib/translation/hi_in.dart
Normal file → Executable file
0
lib/translation/hu_hu.dart
Normal file → Executable file
0
lib/translation/hu_hu.dart
Normal file → Executable file
0
lib/translation/it_it.dart
Normal file → Executable file
0
lib/translation/it_it.dart
Normal file → Executable file
0
lib/translation/ka_ge.dart
Normal file → Executable file
0
lib/translation/ka_ge.dart
Normal file → Executable file
0
lib/translation/ko_kr.dart
Normal file → Executable file
0
lib/translation/ko_kr.dart
Normal file → Executable file
0
lib/translation/nl_nl.dart
Normal file → Executable file
0
lib/translation/nl_nl.dart
Normal file → Executable file
0
lib/translation/pl_pl.dart
Normal file → Executable file
0
lib/translation/pl_pl.dart
Normal file → Executable file
0
lib/translation/pt_br.dart
Normal file → Executable file
0
lib/translation/pt_br.dart
Normal file → Executable file
0
lib/translation/ro_ro.dart
Normal file → Executable file
0
lib/translation/ro_ro.dart
Normal file → Executable file
0
lib/translation/ru_ru.dart
Normal file → Executable file
0
lib/translation/ru_ru.dart
Normal file → Executable file
0
lib/translation/sk_sk.dart
Normal file → Executable file
0
lib/translation/sk_sk.dart
Normal file → Executable file
0
lib/translation/tr_tr.dart
Normal file → Executable file
0
lib/translation/tr_tr.dart
Normal file → Executable file
0
lib/translation/translation.dart
Normal file → Executable file
0
lib/translation/translation.dart
Normal file → Executable file
0
lib/translation/ur_pk.dart
Normal file → Executable file
0
lib/translation/ur_pk.dart
Normal file → Executable file
0
lib/translation/zh_ch.dart
Normal file → Executable file
0
lib/translation/zh_ch.dart
Normal file → Executable file
0
lib/translation/zh_tw.dart
Normal file → Executable file
0
lib/translation/zh_tw.dart
Normal file → Executable file
88
pubspec.lock
88
pubspec.lock
|
@ -50,10 +50,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -122,10 +122,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
version: "8.10.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -298,10 +298,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -343,10 +343,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_expandable_fab
|
||||
sha256: "4d03f54e5384897e32606e9959cef5e7857e5a203e24684f95dfbb5f7fb9b88e"
|
||||
sha256: c2936d398169166064d025df91a3bb417109a859e725d9b80c6ef7f04e01b6ab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.5.1"
|
||||
flutter_hsvcolor_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -367,18 +367,18 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "6.0.0"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "33b3e0269ae9d51669957a923f2376bee96299b09915d856395af8c4238aebfa"
|
||||
sha256: b94a50aabbe56ef254f95f3be75640f99120429f0a153b2dc30143cffc9bfdf3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "19.1.0"
|
||||
version: "19.2.1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -449,10 +449,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_timezone
|
||||
sha256: bc286cecb0366d88e6c4644e3962ebd1ce1d233abc658eb1e0cd803389f84b64
|
||||
sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
version: "4.1.1"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -494,26 +494,26 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: geocoding
|
||||
sha256: d580c801cba9386b4fac5047c4c785a4e19554f46be42f4f5e5b7deacd088a66
|
||||
sha256: "606be036287842d779d7ec4e2f6c9435fc29bbbd3c6da6589710f981d8852895"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "4.0.0"
|
||||
geocoding_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geocoding_android
|
||||
sha256: "1b13eca79b11c497c434678fed109c2be020b158cec7512c848c102bc7232603"
|
||||
sha256: fe7df2e35dc49a5176af634ff9c7b0312d9a2adc94320b936a56241f8028bbbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "4.0.0"
|
||||
geocoding_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geocoding_ios
|
||||
sha256: "94ddba60387501bd1c11e18dca7c5a9e8c645d6e3da9c38b9762434941870c24"
|
||||
sha256: "43bde988312feb1a3cb6c3d514e9f4b04b564d1884fa56bd8241030bbb3bde36"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
geocoding_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -526,10 +526,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: e7ebfa04ce451daf39b5499108c973189a71a919aa53c1204effda1c5b93b822
|
||||
sha256: ee2212a3df8292ec4c90b91183b8001d3f5a800823c974b570c5f9344ca320dc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.0"
|
||||
version: "14.0.1"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -606,10 +606,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: home_widget
|
||||
sha256: "7430f7549d42cef2e729bd3c779de748b93f1eb78b1abfe6bca8fffd1cfce3e9"
|
||||
sha256: ad9634ef5894f3bac73f04d59e2e5151a39798f49985399fd928dadc828d974a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0+1"
|
||||
version: "0.8.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -622,10 +622,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
http_cache_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -678,18 +678,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: internet_connection_checker_plus
|
||||
sha256: eb3a6f03e7b1641589f580993d29aee0b3c4920fc618f7556de359fedb87b02e
|
||||
sha256: "5aea4a1ee0fcca736980a7d04d96fe8c0b53dea330690053305a5c5392230112"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.1"
|
||||
version: "2.7.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -766,10 +766,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -798,10 +798,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "6.0.0"
|
||||
lists:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1179,10 +1179,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: timezone
|
||||
sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d
|
||||
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
version: "0.10.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1299,10 +1299,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1323,10 +1323,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1339,10 +1339,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.12.0"
|
||||
version: "5.13.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1364,7 +1364,7 @@ packages:
|
|||
description:
|
||||
path: workmanager
|
||||
ref: main
|
||||
resolved-ref: "4ce065135dc1b91fee918f81596b42a56850391d"
|
||||
resolved-ref: "387d30698678dbf40585fa4732be60a74f9e07ef"
|
||||
url: "https://github.com/fluttercommunity/flutter_workmanager.git"
|
||||
source: git
|
||||
version: "0.5.2"
|
||||
|
@ -1401,5 +1401,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.2 <4.0.0"
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
22
pubspec.yaml
22
pubspec.yaml
|
@ -6,7 +6,7 @@ publish_to: "none"
|
|||
version: 1.3.8+41
|
||||
|
||||
environment:
|
||||
sdk: ">=3.7.2 <4.0.0"
|
||||
sdk: ">=3.8.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
@ -16,13 +16,13 @@ dependencies:
|
|||
get: ^4.7.2
|
||||
gap: ^3.0.1
|
||||
dio: ^5.8.0+1
|
||||
intl: ^0.19.0
|
||||
intl: ^0.20.2
|
||||
shimmer: ^3.0.0
|
||||
latlong2: ^0.9.1
|
||||
timezone: ^0.10.0
|
||||
geocoding: ^3.0.0
|
||||
geolocator: ^14.0.0
|
||||
home_widget: ^0.7.0+1
|
||||
timezone: ^0.10.1
|
||||
geocoding: ^4.0.0
|
||||
geolocator: ^14.0.1
|
||||
home_widget: ^0.8.0
|
||||
restart_app: ^1.3.2
|
||||
flutter_map: ^8.1.1
|
||||
google_fonts: ^6.2.1
|
||||
|
@ -32,7 +32,7 @@ dependencies:
|
|||
path_provider: ^2.1.5
|
||||
# quick_settings: ^1.0.1
|
||||
json_annotation: ^4.9.0
|
||||
flutter_timezone: ^4.1.0
|
||||
flutter_timezone: ^4.1.1
|
||||
device_info_plus: ^11.4.0
|
||||
package_info_plus: ^8.3.0
|
||||
connectivity_plus: ^6.1.4
|
||||
|
@ -44,11 +44,11 @@ dependencies:
|
|||
dio_cache_interceptor: ^4.0.3
|
||||
http_cache_file_store: ^2.0.1
|
||||
flutter_map_animations: ^0.9.0
|
||||
flutter_expandable_fab: ^2.4.1
|
||||
flutter_expandable_fab: ^2.5.1
|
||||
flutter_hsvcolor_picker: ^1.5.1
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
flutter_local_notifications: ^19.1.0
|
||||
internet_connection_checker_plus: ^2.7.1
|
||||
flutter_local_notifications: ^19.2.1
|
||||
internet_connection_checker_plus: ^2.7.2
|
||||
isar:
|
||||
version: ^3.1.8
|
||||
hosted: https://pub.isar-community.dev/
|
||||
|
@ -74,7 +74,7 @@ dev_dependencies:
|
|||
sdk: flutter
|
||||
freezed: ^3.0.0-0.0.dev
|
||||
build_runner: ^2.4.15
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_lints: ^6.0.0
|
||||
json_serializable: ^6.9.0
|
||||
flutter_native_splash: ^2.4.6
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue