diff --git a/lib/app/api/api.dart b/lib/app/api/api.dart old mode 100644 new mode 100755 diff --git a/lib/app/api/city_api.dart b/lib/app/api/city_api.dart old mode 100644 new mode 100755 diff --git a/lib/app/api/weather_api.dart b/lib/app/api/weather_api.dart old mode 100644 new mode 100755 diff --git a/lib/app/api/weather_api.freezed.dart b/lib/app/api/weather_api.freezed.dart old mode 100644 new mode 100755 diff --git a/lib/app/api/weather_api.g.dart b/lib/app/api/weather_api.g.dart old mode 100644 new mode 100755 diff --git a/lib/app/controller/controller.dart b/lib/app/controller/controller.dart old mode 100644 new mode 100755 index 3e28c5e..32d3ceb --- a/lib/app/controller/controller.dart +++ b/lib/app/controller/controller.dart @@ -134,7 +134,7 @@ class WeatherController extends GetxController { await readCache(); } - Future getCurrentLocationSearch() async { + Future> getCurrentLocationSearch() async { if (!(await isOnline.value)) { showSnackBar(content: 'no_inter'.tr); } @@ -313,14 +313,13 @@ class WeatherController extends GetxController { } Future updateCacheCard(bool refresh) async { - final weatherCard = - refresh - ? isar.weatherCards.where().sortByIndex().findAllSync() - : isar.weatherCards - .filter() - .timestampLessThan(cacheExpiry) - .sortByIndex() - .findAllSync(); + final weatherCard = refresh + ? isar.weatherCards.where().sortByIndex().findAllSync() + : isar.weatherCards + .filter() + .timestampLessThan(cacheExpiry) + .sortByIndex() + .findAllSync(); if (!(await isOnline.value) || weatherCard.isEmpty) { return; @@ -335,53 +334,52 @@ 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(); }); } } + void _updateWeatherCard(WeatherCard oldCard, WeatherCard updatedCard) { + 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); + } + Future updateCard(WeatherCard weatherCard) async { if (!(await isOnline.value)) { return; @@ -396,43 +394,7 @@ class WeatherController extends GetxController { ); isar.writeTxnSync(() { - weatherCard - ..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(weatherCard); + _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!); diff --git a/lib/app/data/db.dart b/lib/app/data/db.dart old mode 100644 new mode 100755 diff --git a/lib/app/data/db.g.dart b/lib/app/data/db.g.dart old mode 100644 new mode 100755 diff --git a/lib/app/ui/geolocation.dart b/lib/app/ui/geolocation.dart old mode 100644 new mode 100755 index 8f8b959..2b6165c --- a/lib/app/ui/geolocation.dart +++ b/lib/app/ui/geolocation.dart @@ -33,6 +33,8 @@ class _SelectGeolocationState extends State { final _controllerCity = TextEditingController(); final _controllerDistrict = TextEditingController(); + static const kTextFieldElevation = 4.0; + static const colorFilter = ColorFilter.matrix([ -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 { ]); 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 { setState(() {}); } - void fillControllerGeo(location) { + void fillControllerGeo(Map location) { _controllerLat.text = '${location['lat']}'; _controllerLon.text = '${location['lon']}'; _controllerCity.text = location['district']; @@ -82,35 +83,310 @@ class _SelectGeolocationState extends State { ); } + 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( + 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.empty(); + } + return WeatherAPI().getCity(textEditingValue.text, locale); + }, + onSelected: (Result selection) => fillController(selection), + displayStringForOption: (Result option) => + '${option.name}, ${option.admin1}', + optionsViewBuilder: + ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return _buildOptionsView(context, onSelected, options); + }, + ); + } + + Widget _buildOptionsView( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable 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 _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 _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 _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 { 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 { ), Row( children: [ - Flexible( - child: RawAutocomplete( - 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 - onSelected, - Iterable 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 { ], ), ), - 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 { ), ); } + + 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, + ), + ), + ); + } } diff --git a/lib/app/ui/home.dart b/lib/app/ui/home.dart old mode 100644 new mode 100755 index fc450a9..655d2ba --- a/lib/app/ui/home.dart +++ b/lib/app/ui/home.dart @@ -82,6 +82,148 @@ class _HomePageState extends State with TickerProviderStateMixin { tabController.animateTo(tabIndex); } + 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(); + } + } + + Widget _buildSearchField(TextStyle? labelLarge) { + return RawAutocomplete( + focusNode: _focusNode, + textEditingController: _controller, + fieldViewBuilder: (_, __, ___, ____) { + return TextField( + controller: _controller, + focusNode: _focusNode, + style: labelLarge?.copyWith(fontSize: 16), + decoration: InputDecoration(hintText: 'search'.tr), + ); + }, + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text.isEmpty) { + return const Iterable.empty(); + } + return WeatherAPI().getCity(textEditingValue.text, locale); + }, + onSelected: (Result selection) async { + await weatherController.deleteAll(true); + await weatherController.getLocation( + double.parse('${selection.latitude}'), + double.parse('${selection.longitude}'), + selection.admin1, + selection.name, + ); + visible = false; + _controller.clear(); + _focusNode.unfocus(); + setState(() {}); + }, + displayStringForOption: + (Result option) => '${option.name}, ${option.admin1}', + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return _buildOptionsView(context, onSelected, options, labelLarge); + }, + ); + } + + Widget _buildOptionsView( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + TextStyle? labelLarge, + ) { + return Align( + alignment: Alignment.topLeft, + child: Material( + borderRadius: BorderRadius.circular(20), + elevation: 4.0, + child: SizedBox( + width: 250, + 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: labelLarge, + ), + ), + ); + }, + ), + ), + ), + ); + } + + Widget _buildSearchIconButton() { + return IconButton( + onPressed: () { + if (visible) { + _controller.clear(); + _focusNode.unfocus(); + visible = false; + } else { + visible = true; + } + setState(() {}); + }, + icon: Icon( + visible + ? IconsaxPlusLinear.close_circle + : IconsaxPlusLinear.search_normal_1, + size: 18, + ), + ); + } + @override Widget build(BuildContext context) { final textTheme = context.textTheme; @@ -100,146 +242,23 @@ class _HomePageState extends State with TickerProviderStateMixin { 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( - focusNode: _focusNode, - textEditingController: _controller, - fieldViewBuilder: (_, __, ___, ____) { - return TextField( - controller: _controller, - focusNode: _focusNode, - style: labelLarge?.copyWith(fontSize: 16), - decoration: InputDecoration(hintText: 'search'.tr), - ); - }, - optionsBuilder: (TextEditingValue textEditingValue) { - if (textEditingValue.text.isEmpty) { - return const Iterable.empty(); - } - return WeatherAPI().getCity( - textEditingValue.text, - locale, - ); - }, - onSelected: (Result selection) async { - await weatherController.deleteAll(true); - await weatherController.getLocation( - double.parse('${selection.latitude}'), - double.parse('${selection.longitude}'), - selection.admin1, - selection.name, - ); - visible = false; - _controller.clear(); - _focusNode.unfocus(); - setState(() {}); - }, - displayStringForOption: - (Result option) => '${option.name}, ${option.admin1}', - optionsViewBuilder: ( - BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options, - ) { - return Align( - alignment: Alignment.topLeft, - child: Material( - borderRadius: BorderRadius.circular(20), - elevation: 4.0, - child: SizedBox( - width: 250, - 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: labelLarge, - ), - ), - ); - }, - ), - ), - ), + leading: + tabIndex == 0 + ? IconButton( + onPressed: () { + Get.to( + () => const SelectGeolocation(isStart: false), + transition: Transition.downToUp, ); }, + icon: const Icon( + IconsaxPlusLinear.global_search, + size: 18, + ), ) - : 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( - onPressed: () { - if (visible) { - _controller.clear(); - _focusNode.unfocus(); - visible = false; - } else { - visible = true; - } - setState(() {}); - }, - icon: Icon( - visible - ? IconsaxPlusLinear.close_circle - : IconsaxPlusLinear.search_normal_1, - size: 18, - ), - ), - ], - int() => null, - }, + : null, + title: _buildAppBarTitle(tabIndex, textStyle, labelLarge), + actions: tabIndex == 0 ? [_buildSearchIconButton()] : null, ), body: SafeArea( child: TabBarView(controller: tabController, children: pages), diff --git a/lib/app/ui/main/view/main.dart b/lib/app/ui/main/view/main.dart old mode 100644 new mode 100755 index eb33e04..fe0987e --- a/lib/app/ui/main/view/main.dart +++ b/lib/app/ui/main/view/main.dart @@ -24,36 +24,12 @@ class _MainPageState extends State { @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,114 +41,202 @@ class _MainPageState extends State { final tempMax = mainWeather.temperature2MMax![dayOfNow]; final tempMin = mainWeather.temperature2MMin![dayOfNow]; - return ListView( - children: [ - 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( - margin: const EdgeInsets.only(bottom: 15), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - child: SizedBox( - height: 135, - child: ScrollablePositionedList.separated( - key: const PageStorageKey(0), - separatorBuilder: (BuildContext context, int index) { - return const VerticalDivider( - width: 10, - indent: 40, - endIndent: 40, - ); - }, - scrollDirection: Axis.horizontal, - itemScrollController: - weatherController.itemScrollController, - itemCount: mainWeather.time!.length, - itemBuilder: (ctx, i) { - final i24 = (i / 24).floor(); - - return GestureDetector( - onTap: () { - weatherController.hourOfDay.value = i; - weatherController.dayOfNow.value = i24; - 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: mainWeather.time![i], - weather: mainWeather.weathercode![i], - degree: mainWeather.temperature2M![i], - timeDay: mainWeather.sunrise![i24], - timeNight: mainWeather.sunset![i24], - ), - ), - ); - }, - ), - ), - ), - ), - SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset), - DescContainer( - humidity: mainWeather.relativehumidity2M?[hourOfDay], - wind: mainWeather.windspeed10M?[hourOfDay], - visibility: mainWeather.visibility?[hourOfDay], - feels: mainWeather.apparentTemperature?[hourOfDay], - evaporation: mainWeather.evapotranspiration?[hourOfDay], - precipitation: mainWeather.precipitation?[hourOfDay], - direction: mainWeather.winddirection10M?[hourOfDay], - pressure: mainWeather.surfacePressure?[hourOfDay], - rain: mainWeather.rain?[hourOfDay], - cloudcover: mainWeather.cloudcover?[hourOfDay], - windgusts: mainWeather.windgusts10M?[hourOfDay], - uvIndex: mainWeather.uvIndex?[hourOfDay], - dewpoint2M: mainWeather.dewpoint2M?[hourOfDay], - precipitationProbability: - mainWeather.precipitationProbability?[hourOfDay], - shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay], - initiallyExpanded: false, - title: 'hourlyVariables'.tr, - ), - DailyContainer( - weatherData: weatherCard, - onTap: - () => Get.to( - () => DailyCardList(weatherData: weatherCard), - transition: Transition.downToUp, - ), - ), - ], + return _buildMainView( + context, + mainWeather, + weatherCard, + hourOfDay, + dayOfNow, + sunrise, + sunset, + tempMax!, + tempMin!, ); }), ), ); } + + Future _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: [ + _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, + ); + } + + 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), + child: SizedBox( + height: 135, + child: ScrollablePositionedList.separated( + key: const PageStorageKey(0), + separatorBuilder: (BuildContext context, int index) { + return const VerticalDivider( + width: 10, + indent: 40, + endIndent: 40, + ); + }, + scrollDirection: Axis.horizontal, + 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( + onTap: () { + weatherController.hourOfDay.value = i; + weatherController.dayOfNow.value = i24; + 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: mainWeather.time![i], + weather: mainWeather.weathercode![i], + degree: mainWeather.temperature2M![i], + timeDay: mainWeather.sunrise![i24], + timeNight: mainWeather.sunset![i24], + ), + ), + ); + } + + 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], + feels: mainWeather.apparentTemperature?[hourOfDay], + evaporation: mainWeather.evapotranspiration?[hourOfDay], + precipitation: mainWeather.precipitation?[hourOfDay], + direction: mainWeather.winddirection10M?[hourOfDay], + pressure: mainWeather.surfacePressure?[hourOfDay], + rain: mainWeather.rain?[hourOfDay], + cloudcover: mainWeather.cloudcover?[hourOfDay], + windgusts: mainWeather.windgusts10M?[hourOfDay], + uvIndex: mainWeather.uvIndex?[hourOfDay], + dewpoint2M: mainWeather.dewpoint2M?[hourOfDay], + precipitationProbability: + mainWeather.precipitationProbability?[hourOfDay], + shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay], + initiallyExpanded: false, + title: 'hourlyVariables'.tr, + ); + } + + Widget _buildDailyContainer(WeatherCard weatherCard) { + return DailyContainer( + weatherData: weatherCard, + onTap: () => Get.to( + () => DailyCardList(weatherData: weatherCard), + transition: Transition.downToUp, + ), + ); + } } diff --git a/lib/app/ui/map/view/map.dart b/lib/app/ui/map/view/map.dart old mode 100644 new mode 100755 index cfff832..7f26040 --- a/lib/app/ui/map/view/map.dart +++ b/lib/app/ui/map/view/map.dart @@ -113,7 +113,7 @@ class _MapPageState extends State with TickerProviderStateMixin { _focusNode.unfocus(); } - Widget _buidStyleMarkers( + Widget _buildStyleMarkers( int weathercode, String time, String sunrise, @@ -154,7 +154,7 @@ class _MapPageState extends State 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 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 with TickerProviderStateMixin { : const SizedBox.shrink(); } + Widget _buildSearchField() { + return RawAutocomplete( + 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.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 onSelected, + Iterable 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 with TickerProviderStateMixin { ), ], ), - RawAutocomplete( - 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.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 onSelected, - Iterable 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(), ], ); }, diff --git a/lib/app/ui/onboarding.dart b/lib/app/ui/onboarding.dart old mode 100644 new mode 100755 index 3d4db4a..29561c3 --- a/lib/app/ui/onboarding.dart +++ b/lib/app/ui/onboarding.dart @@ -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 { @override void initState() { - pageController = PageController(initialPage: 0); super.initState(); + pageController = PageController(initialPage: 0); } @override @@ -45,55 +45,65 @@ class _OnBordingState extends State { body: SafeArea( child: Column( children: [ - Expanded( - child: PageView.builder( - controller: pageController, - itemCount: data.length, - onPageChanged: (index) { - setState(() { - pageIndex = index; - }); - }, - itemBuilder: - (context, index) => OnboardContent( - image: data[index].image, - title: data[index].title, - description: data[index].description, - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ...List.generate( - data.length, - (index) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: DotIndicator(isActive: index == pageIndex), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - child: MyTextButton( - buttonName: - pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr, - onPressed: () { - pageIndex == data.length - 1 - ? onBoardHome() - : pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.ease, - ); - }, - ), - ), + _buildPageView(), + _buildDotIndicators(), + _buildActionButton(), ], ), ), ); } + + Widget _buildPageView() { + return Expanded( + child: PageView.builder( + controller: pageController, + itemCount: data.length, + onPageChanged: (index) { + setState(() { + pageIndex = index; + }); + }, + itemBuilder: (context, index) => OnboardContent( + image: data[index].image, + title: data[index].title, + description: data[index].description, + ), + ), + ); + } + + Widget _buildDotIndicators() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + data.length, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: DotIndicator(isActive: index == pageIndex), + ), + ), + ); + } + + Widget _buildActionButton() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: MyTextButton( + buttonName: pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr, + onPressed: () { + if (pageIndex == data.length - 1) { + onBoardHome(); + } else { + pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ); + } + }, + ), + ); + } } class DotIndicator extends StatelessWidget { @@ -108,10 +118,9 @@ class DotIndicator extends StatelessWidget { height: 8, width: 8, decoration: BoxDecoration( - color: - isActive - ? context.theme.colorScheme.secondary - : context.theme.colorScheme.secondaryContainer, + 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 diff --git a/lib/app/ui/places/view/place_info.dart b/lib/app/ui/places/view/place_info.dart old mode 100644 new mode 100755 index 3cdd075..f56f709 --- a/lib/app/ui/places/view/place_info.dart +++ b/lib/app/ui/places/view/place_info.dart @@ -56,138 +56,19 @@ class _PlaceInfoState extends State { final weatherCard = widget.weatherCard; return RefreshIndicator( - onRefresh: () async { - await weatherController.updateCard(weatherCard); - getTime(); - setState(() {}); - }, + onRefresh: _handleRefresh, child: Scaffold( - appBar: AppBar( - centerTitle: true, - automaticallyImplyLeading: false, - leading: IconButton( - onPressed: () => Get.back(), - icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20), - ), - title: Text( - weatherCard.district!.isNotEmpty - ? '${weatherCard.city}' - ', ${weatherCard.district}' - : '${weatherCard.city}', - style: context.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - fontSize: 18, - ), - ), - ), + appBar: _buildAppBar(context, weatherCard), body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: ListView( children: [ - Now( - time: weatherCard.time![timeNow], - weather: weatherCard.weathercode![timeNow], - degree: weatherCard.temperature2M![timeNow], - feels: weatherCard.apparentTemperature![timeNow]!, - timeDay: weatherCard.sunrise![dayNow], - timeNight: weatherCard.sunset![dayNow], - tempMax: weatherCard.temperature2MMax![dayNow]!, - tempMin: weatherCard.temperature2MMin![dayNow]!, - ), - Card( - margin: const EdgeInsets.only(bottom: 15), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - child: SizedBox( - height: 135, - child: ScrollablePositionedList.separated( - key: const PageStorageKey(1), - separatorBuilder: (BuildContext context, int index) { - return const VerticalDivider( - width: 10, - indent: 40, - endIndent: 40, - ); - }, - scrollDirection: Axis.horizontal, - itemScrollController: itemScrollController, - itemCount: weatherCard.time!.length, - itemBuilder: - (ctx, i) => GestureDetector( - onTap: () { - timeNow = i; - dayNow = (i / 24).floor(); - setState(() {}); - }, - child: Container( - margin: const EdgeInsets.symmetric(vertical: 5), - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 5, - ), - decoration: BoxDecoration( - color: - i == timeNow - ? context - .theme - .colorScheme - .secondaryContainer - : Colors.transparent, - 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()], - ), - ), - ), - ), - ), - ), - ), - SunsetSunrise( - timeSunrise: weatherCard.sunrise![dayNow], - timeSunset: weatherCard.sunset![dayNow], - ), - DescContainer( - humidity: weatherCard.relativehumidity2M?[timeNow], - wind: weatherCard.windspeed10M?[timeNow], - visibility: weatherCard.visibility?[timeNow], - feels: weatherCard.apparentTemperature?[timeNow], - evaporation: weatherCard.evapotranspiration?[timeNow], - precipitation: weatherCard.precipitation?[timeNow], - direction: weatherCard.winddirection10M?[timeNow], - pressure: weatherCard.surfacePressure?[timeNow], - rain: weatherCard.rain?[timeNow], - cloudcover: weatherCard.cloudcover?[timeNow], - windgusts: weatherCard.windgusts10M?[timeNow], - uvIndex: weatherCard.uvIndex?[timeNow], - dewpoint2M: weatherCard.dewpoint2M?[timeNow], - precipitationProbability: - weatherCard.precipitationProbability?[timeNow], - shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow], - initiallyExpanded: false, - title: 'hourlyVariables'.tr, - ), - DailyContainer( - weatherData: weatherCard, - onTap: - () => Get.to( - () => DailyCardList(weatherData: weatherCard), - transition: Transition.downToUp, - ), - ), + _buildNowWidget(weatherCard), + _buildHourlyList(weatherCard), + _buildSunsetSunriseWidget(weatherCard), + _buildHourlyDescContainer(weatherCard), + _buildDailyContainer(weatherCard), ], ), ), @@ -195,4 +76,137 @@ class _PlaceInfoState extends State { ), ); } + + Future _handleRefresh() async { + await weatherController.updateCard(widget.weatherCard); + getTime(); + setState(() {}); + } + + AppBar _buildAppBar(BuildContext context, WeatherCard weatherCard) { + return AppBar( + centerTitle: true, + automaticallyImplyLeading: false, + leading: IconButton( + onPressed: () => Get.back(), + icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20), + ), + title: Text( + weatherCard.district!.isNotEmpty + ? '${weatherCard.city}, ${weatherCard.district}' + : '${weatherCard.city}', + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 18, + ), + ), + ); + } + + Widget _buildNowWidget(WeatherCard weatherCard) { + return Now( + time: weatherCard.time![timeNow], + weather: weatherCard.weathercode![timeNow], + degree: weatherCard.temperature2M![timeNow], + feels: weatherCard.apparentTemperature![timeNow]!, + timeDay: weatherCard.sunrise![dayNow], + timeNight: weatherCard.sunset![dayNow], + tempMax: weatherCard.temperature2MMax![dayNow]!, + tempMin: weatherCard.temperature2MMin![dayNow]!, + ); + } + + Widget _buildHourlyList(WeatherCard weatherCard) { + return Card( + margin: const EdgeInsets.only(bottom: 15), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: SizedBox( + height: 135, + child: ScrollablePositionedList.separated( + key: const PageStorageKey(1), + separatorBuilder: (BuildContext context, int index) { + return const VerticalDivider( + width: 10, + indent: 40, + endIndent: 40, + ); + }, + scrollDirection: Axis.horizontal, + itemScrollController: itemScrollController, + itemCount: weatherCard.time!.length, + itemBuilder: (ctx, i) => _buildHourlyItem(weatherCard, i), + ), + ), + ), + ); + } + + Widget _buildHourlyItem(WeatherCard weatherCard, int i) { + return GestureDetector( + onTap: () { + timeNow = i; + dayNow = (i / 24).floor(); + setState(() {}); + }, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), + decoration: BoxDecoration( + color: + i == timeNow + ? context.theme.colorScheme.secondaryContainer + : Colors.transparent, + 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()], + ), + ), + ); + } + + Widget _buildSunsetSunriseWidget(WeatherCard weatherCard) { + return SunsetSunrise( + timeSunrise: weatherCard.sunrise![dayNow], + timeSunset: weatherCard.sunset![dayNow], + ); + } + + Widget _buildHourlyDescContainer(WeatherCard weatherCard) { + return DescContainer( + humidity: weatherCard.relativehumidity2M?[timeNow], + wind: weatherCard.windspeed10M?[timeNow], + visibility: weatherCard.visibility?[timeNow], + feels: weatherCard.apparentTemperature?[timeNow], + evaporation: weatherCard.evapotranspiration?[timeNow], + precipitation: weatherCard.precipitation?[timeNow], + direction: weatherCard.winddirection10M?[timeNow], + pressure: weatherCard.surfacePressure?[timeNow], + rain: weatherCard.rain?[timeNow], + cloudcover: weatherCard.cloudcover?[timeNow], + windgusts: weatherCard.windgusts10M?[timeNow], + uvIndex: weatherCard.uvIndex?[timeNow], + dewpoint2M: weatherCard.dewpoint2M?[timeNow], + precipitationProbability: weatherCard.precipitationProbability?[timeNow], + shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow], + initiallyExpanded: false, + title: 'hourlyVariables'.tr, + ); + } + + Widget _buildDailyContainer(WeatherCard weatherCard) { + return DailyContainer( + weatherData: weatherCard, + onTap: + () => Get.to( + () => DailyCardList(weatherData: weatherCard), + transition: Transition.downToUp, + ), + ); + } } diff --git a/lib/app/ui/places/view/place_list.dart b/lib/app/ui/places/view/place_list.dart old mode 100644 new mode 100755 index 2910d72..7fafd5a --- a/lib/app/ui/places/view/place_list.dart +++ b/lib/app/ui/places/view/place_list.dart @@ -14,91 +14,103 @@ class PlaceList extends StatefulWidget { class _PlaceListState extends State { 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( - child: SingleChildScrollView( - child: Column( - children: [ - Image.asset('assets/icons/City.png', scale: 6), - SizedBox( - width: Get.size.width * 0.8, - child: Text( - 'noWeatherCard'.tr, - textAlign: TextAlign.center, - style: titleMedium?.copyWith( - fontWeight: FontWeight.w600, - fontSize: 18, - ), - ), - ), - ], - ), - ), - ) - : NestedScrollView( - physics: const NeverScrollableScrollPhysics(), - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverToBoxAdapter( - child: MyTextForm( - labelText: 'search'.tr, - type: TextInputType.text, - icon: const Icon( - IconsaxPlusLinear.search_normal_1, - size: 20, - ), - controller: searchTasks, - margin: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - onChanged: applyFilter, - iconButton: - searchTasks.text.isNotEmpty - ? IconButton( - onPressed: () { - searchTasks.clear(); - applyFilter(''); - }, - icon: const Icon( - IconsaxPlusLinear.close_circle, - color: Colors.grey, - size: 20, - ), - ) - : null, - ), - ), - ]; - }, - body: RefreshIndicator( - onRefresh: () async { - await weatherController.updateCacheCard(true); - setState(() {}); - }, - child: PlaceCardList(searchCity: filter), + + 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: [ + Image.asset('assets/icons/City.png', scale: 6), + SizedBox( + width: Get.size.width * 0.8, + child: Text( + 'noWeatherCard'.tr, + textAlign: TextAlign.center, + style: titleMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 18, ), ), + ), + ], + ), + ), ); } + + Widget _buildListView(BuildContext context) { + return NestedScrollView( + physics: const NeverScrollableScrollPhysics(), + headerSliverBuilder: (context, innerBoxIsScrolled) { + 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), + controller: searchTasks, + margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + onChanged: applyFilter, + iconButton: + searchTasks.text.isNotEmpty + ? IconButton( + onPressed: clearSearch, + icon: const Icon( + IconsaxPlusLinear.close_circle, + color: Colors.grey, + size: 20, + ), + ) + : null, + ), + ); + } + + Future _handleRefresh() async { + await weatherController.updateCacheCard(true); + setState(() {}); + } } diff --git a/lib/app/ui/places/widgets/create_place.dart b/lib/app/ui/places/widgets/create_place.dart old mode 100644 new mode 100755 index d1207df..0ef872a --- a/lib/app/ui/places/widgets/create_place.dart +++ b/lib/app/ui/places/widgets/create_place.dart @@ -23,12 +23,14 @@ class _CreatePlaceState extends State final formKey = GlobalKey(); 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 _animation; @@ -36,10 +38,12 @@ class _CreatePlaceState extends State @override void initState() { super.initState(); - if (widget.latitude != null && widget.longitude != null) { - _controllerLat = TextEditingController(text: widget.latitude); - _controllerLon = TextEditingController(text: widget.longitude); - } + _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 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,208 +116,13 @@ class _CreatePlaceState extends State crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only(top: 14, bottom: 7), - child: Text( - 'create'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - RawAutocomplete( - 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.empty(); - } - return WeatherAPI().getCity( - textEditingValue.text, - locale, - ); - }, - onSelected: - (Result selection) => fillController(selection), - displayStringForOption: - (Result option) => '${option.name}, ${option.admin1}', - optionsViewBuilder: ( - BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options, - ) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - 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, - ), - ), - ); - }, - ), - ), - ), - ); - }, - ), - 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, - ), - 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), - onChanged: (value) => setState(() {}), - 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), - onChanged: (value) => setState(() {}), - 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), - onChanged: (value) => setState(() {}), - margin: const EdgeInsets.only( - left: 10, - right: 10, - top: 10, - ), - validator: (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 { - if (formKey.currentState!.validate()) { - textTrim(_controllerLat); - textTrim(_controllerLon); - textTrim(_controllerCity); - textTrim(_controllerDistrict); - setState(() => isLoading = true); - await weatherController.addCardWeather( - double.parse(_controllerLat.text), - double.parse(_controllerLon.text), - _controllerCity.text, - _controllerDistrict.text, - ); - setState(() => isLoading = false); - Get.back(); - } - }, - ), - ), - ), + _buildTitleText(), + _buildSearchField(), + _buildLatitudeField(), + _buildLongitudeField(), + _buildCityField(), + _buildDistrictField(), + _buildSubmitButton(), ], ), ), @@ -320,4 +133,214 @@ class _CreatePlaceState extends State ), ); } + + Widget _buildTitleText() { + return Padding( + padding: const EdgeInsets.only(top: 14, bottom: 7), + child: Text( + 'create'.tr, + style: context.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ); + } + + Widget _buildSearchField() { + return RawAutocomplete( + 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.empty(); + } + return WeatherAPI().getCity(textEditingValue.text, locale); + }, + onSelected: (Result selection) => fillController(selection), + displayStringForOption: + (Result option) => '${option.name}, ${option.admin1}', + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return _buildOptionsView(context, onSelected, options); + }, + ); + } + + Widget _buildOptionsView( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + 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 _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), + 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), + ), + ); + } + + 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; + } + + String? _validateDistrict(String? value) { + if (value == null || value.isEmpty) { + return 'validateName'.tr; + } + return null; + } + + Future _handleSubmit() async { + if (formKey.currentState!.validate()) { + textTrim(_controllerLat); + textTrim(_controllerLon); + textTrim(_controllerCity); + textTrim(_controllerDistrict); + + setState(() => isLoading = true); + await weatherController.addCardWeather( + double.parse(_controllerLat.text), + double.parse(_controllerLon.text), + _controllerCity.text, + _controllerDistrict.text, + ); + setState(() => isLoading = false); + Get.back(); + } + } } diff --git a/lib/app/ui/places/widgets/place_card.dart b/lib/app/ui/places/widgets/place_card.dart old mode 100644 new mode 100755 index 1d266af..3e35537 --- a/lib/app/ui/places/widgets/place_card.dart +++ b/lib/app/ui/places/widgets/place_card.dart @@ -19,6 +19,7 @@ class PlaceCard extends StatefulWidget { required this.timeNight, required this.timeDaily, }); + final List time; final List timeDay; final List timeNight; @@ -40,104 +41,115 @@ class _PlaceCardState extends State { @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( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - statusData.getDegree( - widget - .degree[weatherController.getTime( - widget.time, - widget.timezone, - )] - .round() - .toInt(), - ), - style: context.textTheme.titleLarge?.copyWith( - fontSize: 22, - fontWeight: FontWeight.w600, - ), - ), - const Gap(7), - Text( - statusWeather.getText( - widget.weather[weatherController.getTime( - widget.time, - widget.timezone, - )], - ), - style: context.textTheme.titleMedium?.copyWith( - color: Colors.grey, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - const Gap(10), - Text( - widget.district.isEmpty - ? widget.city - : widget.city.isEmpty - ? widget.district - : widget.city == widget.district - ? widget.city - : '${widget.city}' - ', ${widget.district}', - style: context.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w500, - ), - ), - const Gap(5), - StreamBuilder( - stream: Stream.periodic(const Duration(seconds: 1)), - builder: (context, snapshot) { - return Text( - '${'time'.tr}: ${statusData.getTimeFormatTz(tz.TZDateTime.now(tz.getLocation(widget.timezone)))}', - style: context.textTheme.titleMedium?.copyWith( - color: Colors.grey, - fontWeight: FontWeight.w400, - ), - ); - }, - ), - ], - ), - ), + _buildWeatherInfo(context, currentTimeIndex, currentDayIndex), const Gap(5), - 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, - )], - ), - scale: 6.5, - ), + _buildWeatherImage(currentTimeIndex, currentDayIndex), ], ), ), ); } + + Widget _buildWeatherInfo( + BuildContext context, + int currentTimeIndex, + int currentDayIndex, + ) { + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + statusData.getDegree( + widget.degree[currentTimeIndex].round().toInt(), + ), + style: context.textTheme.titleLarge?.copyWith( + fontSize: 22, + fontWeight: FontWeight.w600, + ), + ), + const Gap(7), + Text( + statusWeather.getText(widget.weather[currentTimeIndex]), + style: context.textTheme.titleMedium?.copyWith( + color: Colors.grey, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + const Gap(10), + _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, + ), + ); + } + + Widget _buildCurrentTimeText(BuildContext context) { + return StreamBuilder( + stream: Stream.periodic(const Duration(seconds: 1)), + builder: (context, snapshot) { + return Text( + '${'time'.tr}: ${statusData.getTimeFormatTz(tz.TZDateTime.now(tz.getLocation(widget.timezone)))}', + style: context.textTheme.titleMedium?.copyWith( + color: Colors.grey, + fontWeight: FontWeight.w400, + ), + ); + }, + ); + } + + Widget _buildWeatherImage(int currentTimeIndex, int currentDayIndex) { + return Image.asset( + statusWeather.getImageNow( + widget.weather[currentTimeIndex], + widget.time[currentTimeIndex], + widget.timeDay[currentDayIndex], + widget.timeNight[currentDayIndex], + ), + scale: 6.5, + ); + } } diff --git a/lib/app/ui/places/widgets/place_card_list.dart b/lib/app/ui/places/widgets/place_card_list.dart old mode 100644 new mode 100755 index 51462ed..6f1a0b1 --- a/lib/app/ui/places/widgets/place_card_list.dart +++ b/lib/app/ui/places/widgets/place_card_list.dart @@ -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,92 +22,134 @@ class _PlaceCardListState extends State { final textTheme = context.textTheme; final titleMedium = textTheme.titleMedium; - var weatherCards = - weatherController.weatherCards - .where( - (weatherCard) => - (widget.searchCity.isEmpty || - weatherCard.city!.toLowerCase().contains( - widget.searchCity, - )), - ) - .toList() - .obs; + final weatherCards = _filterWeatherCards( + weatherController.weatherCards, + widget.searchCity, + ); return ReorderableListView( onReorder: (oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex), - children: [ - ...weatherCards.map( - (weatherCardList) => Dismissible( - key: ValueKey(weatherCardList), - direction: DismissDirection.endToStart, - background: Container( - alignment: Alignment.centerRight, - child: const Padding( - padding: EdgeInsets.only(right: 15), - child: Icon(IconsaxPlusLinear.trash_square, color: Colors.red), - ), - ), - confirmDismiss: (DismissDirection direction) 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, - ), - actions: [ - TextButton( - onPressed: () => Get.back(result: false), - child: Text( - 'cancel'.tr, - style: titleMedium?.copyWith( - color: Colors.blueAccent, - ), - ), - ), - TextButton( - onPressed: () => Get.back(result: true), - child: Text( - 'delete'.tr, - style: titleMedium?.copyWith(color: Colors.red), - ), - ), - ], - ); - }, - ); - }, - onDismissed: (DismissDirection direction) async { - await weatherController.deleteCardWeather(weatherCardList); - }, - child: GestureDetector( - onTap: - () => Get.to( - () => PlaceInfo(weatherCard: weatherCardList), - transition: Transition.downToUp, - ), - child: PlaceCard( - time: weatherCardList.time!, - timeDaily: weatherCardList.timeDaily!, - timeDay: weatherCardList.sunrise!, - timeNight: weatherCardList.sunset!, - weather: weatherCardList.weathercode!, - degree: weatherCardList.temperature2M!, - district: weatherCardList.district!, - city: weatherCardList.city!, - timezone: weatherCardList.timezone!, - ), - ), + children: _buildWeatherCardList( + weatherCards, + context, + textTheme, + titleMedium, + ), + ); + } + + List _filterWeatherCards( + List weatherCards, + String searchCity, + ) { + return weatherCards + .where( + (weatherCard) => + (searchCity.isEmpty || + weatherCard.city!.toLowerCase().contains(searchCity)), + ) + .toList(); + } + + List _buildWeatherCardList( + List 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: _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), + ), + ); + } + + Future _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), + actions: [ + TextButton( + onPressed: () => Get.back(result: false), + child: Text( + 'cancel'.tr, + style: titleMedium?.copyWith(color: Colors.blueAccent), + ), + ), + TextButton( + onPressed: () => Get.back(result: true), + child: Text( + 'delete'.tr, + style: titleMedium?.copyWith(color: Colors.red), + ), + ), + ], + ); + }, + ); + } + + Widget _buildCardGestureDetector(WeatherCard weatherCardList) { + return GestureDetector( + onTap: + () => Get.to( + () => PlaceInfo(weatherCard: weatherCardList), + transition: Transition.downToUp, + ), + child: PlaceCard( + time: weatherCardList.time!, + timeDaily: weatherCardList.timeDaily!, + timeDay: weatherCardList.sunrise!, + timeNight: weatherCardList.sunset!, + weather: weatherCardList.weathercode!, + degree: weatherCardList.temperature2M!, + district: weatherCardList.district!, + city: weatherCardList.city!, + timezone: weatherCardList.timezone!, + ), ); } } diff --git a/lib/app/ui/settings/view/settings.dart b/lib/app/ui/settings/view/settings.dart old mode 100644 new mode 100755 index e161aed..884e284 --- a/lib/app/ui/settings/view/settings.dart +++ b/lib/app/ui/settings/view/settings.dart @@ -34,20 +34,20 @@ class _SettingsPageState extends State { String? colorBackground; String? colorText; - Future infoVersion() async { + @override + void initState() { + super.initState(); + _infoVersion(); + } + + Future _infoVersion() async { final packageInfo = await PackageInfo.fromPlatform(); setState(() { appVersion = packageInfo.version; }); } - @override - void initState() { - infoVersion(); - super.initState(); - } - - updateLanguage(Locale locale) { + void _updateLanguage(Locale locale) { settings.language = '$locale'; isar.writeTxnSync(() => isar.settings.putSync(settings)); Get.updateLocale(locale); @@ -60,1200 +60,1200 @@ class _SettingsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SettingCard( - icon: const Icon(IconsaxPlusLinear.brush_1), - text: 'appearance'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'appearance'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.moon), - text: 'theme'.tr, - dropdown: true, - dropdownName: settings.theme?.tr, - dropdownList: [ - 'system'.tr, - 'dark'.tr, - 'light'.tr, - ], - dropdownCange: (String? newValue) { - final newThemeMode = newValue?.tr; - final darkTheme = 'dark'.tr; - final systemTheme = 'system'.tr; - ThemeMode themeMode = - newThemeMode == systemTheme - ? ThemeMode.system - : newThemeMode == darkTheme - ? ThemeMode.dark - : ThemeMode.light; - String theme = - newThemeMode == systemTheme - ? 'system' - : newThemeMode == darkTheme - ? 'dark' - : 'light'; - themeController.saveTheme(theme); - themeController.changeThemeMode(themeMode); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.mobile), - text: 'amoledTheme'.tr, - switcher: true, - value: settings.amoledTheme, - onChange: (value) { - themeController.saveOledTheme(value); - MyApp.updateAppState( - context, - newAmoledTheme: value, - ); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.colorfilter), - text: 'materialColor'.tr, - switcher: true, - value: settings.materialColor, - onChange: (value) { - themeController.saveMaterialTheme(value); - MyApp.updateAppState( - context, - newMaterialColor: value, - ); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.additem), - text: 'largeElement'.tr, - switcher: true, - value: settings.largeElement, - onChange: (value) { - settings.largeElement = value; - isar.writeTxnSync( - () => isar.settings.putSync(settings), - ); - MyApp.updateAppState( - context, - newLargeElement: value, - ); - setState(() {}); - }, - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.code_1), - text: 'functions'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'functions'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.map), - text: 'location'.tr, - switcher: true, - value: settings.location, - onChange: (value) async { - if (value) { - 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; - } - weatherController.getCurrentLocation(); - } - isar.writeTxnSync(() { - settings.location = value; - isar.settings.putSync(settings); - }); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon( - IconsaxPlusLinear.notification_1, - ), - text: 'notifications'.tr, - switcher: true, - value: settings.notifications, - onChange: (value) async { - final resultExact = - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin - >() - ?.requestExactAlarmsPermission(); - final result = - Platform.isIOS - ? await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin - >() - ?.requestPermissions() - : await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin - >() - ?.requestNotificationsPermission(); - if (result != null && resultExact != null) { - isar.writeTxnSync(() { - settings.notifications = value; - isar.settings.putSync(settings); - }); - if (value) { - weatherController.notification( - weatherController.mainWeather, - ); - } else { - flutterLocalNotificationsPlugin - .cancelAll(); - } - setState(() {}); - } - }, - ), - SettingCard( - elevation: 4, - icon: const Icon( - IconsaxPlusLinear.notification_status, - ), - text: 'timeRange'.tr, - dropdown: true, - dropdownName: '$timeRange', - dropdownList: const [ - '1', - '2', - '3', - '4', - '5', - ], - dropdownCange: (String? newValue) { - isar.writeTxnSync(() { - settings.timeRange = int.parse(newValue!); - isar.settings.putSync(settings); - }); - MyApp.updateAppState( - context, - newTimeRange: int.parse(newValue!), - ); - if (settings.notifications) { - flutterLocalNotificationsPlugin.cancelAll(); - weatherController.notification( - weatherController.mainWeather, - ); - } - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.timer_start), - text: 'timeStart'.tr, - info: true, - infoSettings: true, - infoWidget: _TextInfo( - info: - settings.timeformat == '12' - ? DateFormat.jm( - locale.languageCode, - ).format( - DateFormat.Hm( - locale.languageCode, - ).parse( - weatherController - .timeConvert(timeStart) - .format(context), - ), - ) - : DateFormat.Hm( - locale.languageCode, - ).format( - DateFormat.Hm( - locale.languageCode, - ).parse( - weatherController - .timeConvert(timeStart) - .format(context), - ), - ), - ), - onPressed: () async { - final TimeOfDay? timeStartPicker = - await showTimePicker( - context: context, - initialTime: weatherController - .timeConvert(timeStart), - builder: (context, child) { - final Widget mediaQueryWrapper = - MediaQuery( - data: MediaQuery.of( - context, - ).copyWith( - alwaysUse24HourFormat: - settings.timeformat == - '12' - ? false - : true, - ), - child: child!, - ); - return mediaQueryWrapper; - }, - ); - if (timeStartPicker != null) { - isar.writeTxnSync(() { - settings.timeStart = timeStartPicker - .format(context); - isar.settings.putSync(settings); - }); - if (!context.mounted) return; - MyApp.updateAppState( - context, - newTimeStart: timeStartPicker.format( - context, - ), - ); - if (settings.notifications) { - flutterLocalNotificationsPlugin - .cancelAll(); - weatherController.notification( - weatherController.mainWeather, - ); - } - } - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.timer_pause), - text: 'timeEnd'.tr, - info: true, - infoSettings: true, - infoWidget: _TextInfo( - info: - settings.timeformat == '12' - ? DateFormat.jm( - locale.languageCode, - ).format( - DateFormat.Hm( - locale.languageCode, - ).parse( - weatherController - .timeConvert(timeEnd) - .format(context), - ), - ) - : DateFormat.Hm( - locale.languageCode, - ).format( - DateFormat.Hm( - locale.languageCode, - ).parse( - weatherController - .timeConvert(timeEnd) - .format(context), - ), - ), - ), - onPressed: () async { - final TimeOfDay? timeEndPicker = - await showTimePicker( - context: context, - initialTime: weatherController - .timeConvert(timeEnd), - builder: (context, child) { - final Widget mediaQueryWrapper = - MediaQuery( - data: MediaQuery.of( - context, - ).copyWith( - alwaysUse24HourFormat: - settings.timeformat == - '12' - ? false - : true, - ), - child: child!, - ); - return mediaQueryWrapper; - }, - ); - if (timeEndPicker != null) { - isar.writeTxnSync(() { - settings.timeEnd = timeEndPicker.format( - context, - ); - isar.settings.putSync(settings); - }); - if (!context.mounted) return; - MyApp.updateAppState( - context, - newTimeEnd: timeEndPicker.format(context), - ); - if (settings.notifications) { - flutterLocalNotificationsPlugin - .cancelAll(); - weatherController.notification( - weatherController.mainWeather, - ); - } - } - }, - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.d_square), - text: 'data'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'data'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.cloud_notif), - text: 'roundDegree'.tr, - switcher: true, - value: settings.roundDegree, - onChange: (value) { - settings.roundDegree = value; - isar.writeTxnSync( - () => isar.settings.putSync(settings), - ); - MyApp.updateAppState( - context, - newRoundDegree: value, - ); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.sun_1), - text: 'degrees'.tr, - dropdown: true, - dropdownName: settings.degrees.tr, - dropdownList: [ - 'celsius'.tr, - 'fahrenheit'.tr, - ], - dropdownCange: (String? newValue) async { - isar.writeTxnSync(() { - settings.degrees = - newValue == 'celsius'.tr - ? 'celsius' - : 'fahrenheit'; - isar.settings.putSync(settings); - }); - await weatherController.deleteAll(false); - await weatherController.setLocation(); - await weatherController.updateCacheCard(true); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.rulerpen), - text: 'measurements'.tr, - dropdown: true, - dropdownName: settings.measurements.tr, - dropdownList: [ - 'metric'.tr, - 'imperial'.tr, - ], - dropdownCange: (String? newValue) async { - isar.writeTxnSync(() { - settings.measurements = - newValue == 'metric'.tr - ? 'metric' - : 'imperial'; - isar.settings.putSync(settings); - }); - await weatherController.deleteAll(false); - await weatherController.setLocation(); - await weatherController.updateCacheCard(true); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.wind), - text: 'wind'.tr, - dropdown: true, - dropdownName: settings.wind.tr, - dropdownList: ['kph'.tr, 'm/s'.tr], - dropdownCange: (String? newValue) async { - isar.writeTxnSync(() { - settings.wind = - newValue == 'kph'.tr ? 'kph' : 'm/s'; - isar.settings.putSync(settings); - }); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.ruler), - text: 'pressure'.tr, - dropdown: true, - dropdownName: settings.pressure.tr, - dropdownList: ['hPa'.tr, 'mmHg'.tr], - dropdownCange: (String? newValue) async { - isar.writeTxnSync(() { - settings.pressure = - newValue == 'hPa'.tr ? 'hPa' : 'mmHg'; - isar.settings.putSync(settings); - }); - setState(() {}); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.clock_1), - text: 'timeformat'.tr, - dropdown: true, - dropdownName: settings.timeformat.tr, - dropdownList: ['12'.tr, '24'.tr], - dropdownCange: (String? newValue) { - isar.writeTxnSync(() { - settings.timeformat = - newValue == '12'.tr ? '12' : '24'; - isar.settings.putSync(settings); - }); - setState(() {}); - }, - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.setting_3), - text: 'widget'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'widget'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.add_square), - text: 'addWidget'.tr, - onPressed: () { - HomeWidget.requestPinWidget( - name: androidWidgetName, - androidName: androidWidgetName, - qualifiedAndroidName: - 'com.yoshi.rain.OreoWidget', - ); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon( - IconsaxPlusLinear.bucket_square, - ), - text: 'widgetBackground'.tr, - info: true, - infoWidget: CircleAvatar( - backgroundColor: context.theme.indicatorColor, - radius: 11, - child: CircleAvatar( - backgroundColor: - widgetBackgroundColor.isEmpty - ? context.theme.primaryColor - : HexColor.fromHex( - widgetBackgroundColor, - ), - radius: 10, - ), - ), - onPressed: () { - colorBackground = null; - showDialog( - context: context, - builder: - (context) => Dialog( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Padding( - padding: - const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'widgetBackground'.tr, - style: context - .textTheme - .titleMedium - ?.copyWith( - fontSize: 18, - ), - ), - ), - Padding( - padding: - const EdgeInsets.symmetric( - horizontal: 15, - ), - child: Theme( - data: context.theme.copyWith( - inputDecorationTheme: - InputDecorationTheme( - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular( - 8, - ), - ), - ), - ), - child: ColorPicker( - color: - widgetBackgroundColor - .isEmpty - ? context - .theme - .primaryColor - : HexColor.fromHex( - widgetBackgroundColor, - ), - onChanged: (pickedColor) { - colorBackground = - pickedColor.toHex(); - }, - ), - ), - ), - IconButton( - icon: const Icon( - IconsaxPlusLinear - .tick_square, - ), - onPressed: () { - if (colorBackground == - null) { - return; - } - weatherController - .updateWidgetBackgroundColor( - colorBackground!, - ); - MyApp.updateAppState( - context, - newWidgetBackgroundColor: - colorBackground, - ); - Get.back(); - }, - ), - ], - ), - ), - ), - ); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon(IconsaxPlusLinear.text_block), - text: 'widgetText'.tr, - info: true, - infoWidget: CircleAvatar( - backgroundColor: context.theme.indicatorColor, - radius: 11, - child: CircleAvatar( - backgroundColor: - widgetTextColor.isEmpty - ? context.theme.primaryColor - : HexColor.fromHex(widgetTextColor), - radius: 10, - ), - ), - onPressed: () { - colorText = null; - showDialog( - context: context, - builder: - (context) => Dialog( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Padding( - padding: - const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'widgetText'.tr, - style: context - .textTheme - .titleMedium - ?.copyWith( - fontSize: 18, - ), - ), - ), - Padding( - padding: - const EdgeInsets.symmetric( - horizontal: 15, - ), - child: Theme( - data: context.theme.copyWith( - inputDecorationTheme: - InputDecorationTheme( - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular( - 8, - ), - ), - ), - ), - child: ColorPicker( - color: - widgetTextColor - .isEmpty - ? context - .theme - .primaryColor - : HexColor.fromHex( - widgetTextColor, - ), - onChanged: (pickedColor) { - colorText = - pickedColor.toHex(); - }, - ), - ), - ), - IconButton( - icon: const Icon( - IconsaxPlusLinear - .tick_square, - ), - onPressed: () { - if (colorText == null) { - return; - } - weatherController - .updateWidgetTextColor( - colorText!, - ); - MyApp.updateAppState( - context, - newWidgetTextColor: - colorText, - ); - Get.back(); - }, - ), - ], - ), - ), - ), - ); - }, - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.map), - text: 'map'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15, - ), - child: Text( - 'map'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon( - IconsaxPlusLinear.location_slash, - ), - text: 'hideMap'.tr, - switcher: true, - value: settings.hideMap, - onChange: (value) { - settings.hideMap = value; - isar.writeTxnSync( - () => isar.settings.putSync(settings), - ); - setState(() {}); - Future.delayed( - const Duration(milliseconds: 500), - () => Restart.restartApp(), - ); - }, - ), - SettingCard( - elevation: 4, - icon: const Icon( - IconsaxPlusLinear.trash_square, - ), - text: 'clearCacheStore'.tr, - onPressed: - () => showAdaptiveDialog( - context: context, - builder: - (context) => AlertDialog.adaptive( - title: Text( - 'deletedCacheStore'.tr, - style: - context.textTheme.titleLarge, - ), - content: Text( - 'deletedCacheStoreQuery'.tr, - style: - context.textTheme.titleMedium, - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - 'cancel'.tr, - style: context - .textTheme - .titleMedium - ?.copyWith( - color: - Colors.blueAccent, - ), - ), - ), - TextButton( - onPressed: () async { - final dir = - await getTemporaryDirectory(); - final cacheStoreFuture = - FileCacheStore( - '${dir.path}${Platform.pathSeparator}MapTiles', - ); - cacheStoreFuture.clean(); - Get.back(); - }, - child: Text( - 'delete'.tr, - style: context - .textTheme - .titleMedium - ?.copyWith( - color: Colors.red, - ), - ), - ), - ], - ), - ), - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.language_square), - text: 'language'.tr, - info: true, - infoSettings: true, - infoWidget: _TextInfo( - info: - appLanguages.firstWhere( - (element) => (element['locale'] == locale), - orElse: () => {'name': ''}, - )['name'], - ), - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return ListView( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 15), - child: Text( - 'language'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - textAlign: TextAlign.center, - ), - ), - ListView.builder( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - itemCount: appLanguages.length, - itemBuilder: (context, index) { - return Card( - elevation: 4, - margin: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - child: ListTile( - title: Text( - appLanguages[index]['name'], - style: context.textTheme.labelLarge, - textAlign: TextAlign.center, - ), - onTap: () { - MyApp.updateAppState( - context, - newLocale: - appLanguages[index]['locale'], - ); - updateLanguage( - appLanguages[index]['locale'], - ); - }, - ), - ); - }, - ), - const Gap(10), - ], - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.link_square), - text: 'groups'.tr, - onPressed: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - child: StatefulBuilder( - builder: (BuildContext context, setState) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 15, - ), - child: Text( - 'groups'.tr, - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(LineAwesomeIcons.discord), - text: 'Discord', - onPressed: - () => weatherController.urlLauncher( - 'https://discord.gg/JMMa9aHh8f', - ), - ), - SettingCard( - elevation: 4, - icon: const Icon(LineAwesomeIcons.telegram), - text: 'Telegram', - onPressed: - () => weatherController.urlLauncher( - 'https://t.me/darkmoonightX', - ), - ), - const Gap(10), - ], - ), - ); - }, - ), - ); - }, - ); - }, - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.document), - text: 'license'.tr, - onPressed: - () => Get.to( - () => LicensePage( - applicationIcon: Container( - width: 100, - height: 100, - margin: const EdgeInsets.symmetric(vertical: 5), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20)), - image: DecorationImage( - image: AssetImage('assets/icons/icon.png'), - ), - ), - ), - applicationName: 'Rain', - applicationVersion: appVersion, - ), - transition: Transition.downToUp, - ), - ), - SettingCard( - icon: const Icon(IconsaxPlusLinear.hierarchy_square_2), - text: 'version'.tr, - info: true, - infoWidget: _TextInfo(info: '$appVersion'), - ), - SettingCard( - icon: const Icon(LineAwesomeIcons.github), - text: '${'project'.tr} GitHub', - onPressed: - () => weatherController.urlLauncher( - 'https://github.com/darkmoonight/Rain', - ), - ), - Padding( - padding: const EdgeInsets.all(10), - child: GestureDetector( - child: Text( - 'openMeteo'.tr, - style: context.textTheme.bodyMedium, - overflow: TextOverflow.visible, - textAlign: TextAlign.center, - ), - onTap: - () => - weatherController.urlLauncher('https://open-meteo.com/'), - ), - ), - const Gap(10), + _buildAppearanceCard(context), + _buildFunctionsCard(context), + _buildDataCard(context), + _buildWidgetCard(context), + _buildMapCard(context), + _buildLanguageCard(context), + _buildGroupsCard(context), + _buildLicenseCard(context), + _buildVersionCard(context), + _buildGitHubCard(context), + _buildOpenMeteoText(context), ], ), ); } + + Widget _buildAppearanceCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.brush_1), + text: 'appearance'.tr, + onPressed: () { + _showAppearanceBottomSheet(context); + }, + ); + } + + void _showAppearanceBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildAppearanceTitle(context), + _buildThemeSettingCard(context, setState), + _buildAmoledThemeSettingCard(context, setState), + _buildMaterialColorSettingCard(context, setState), + _buildLargeElementSettingCard(context, setState), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildAppearanceTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'appearance'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildThemeSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.moon), + text: 'theme'.tr, + dropdown: true, + dropdownName: settings.theme?.tr, + dropdownList: ['system'.tr, 'dark'.tr, 'light'.tr], + dropdownChange: (String? newValue) { + _updateTheme(newValue, context, setState); + }, + ); + } + + void _updateTheme( + String? newValue, + BuildContext context, + StateSetter setState, + ) { + ThemeMode themeMode = newValue?.tr == 'system'.tr + ? ThemeMode.system + : newValue?.tr == 'dark'.tr + ? ThemeMode.dark + : ThemeMode.light; + String theme = newValue?.tr == 'system'.tr + ? 'system' + : newValue?.tr == 'dark'.tr + ? 'dark' + : 'light'; + themeController.saveTheme(theme); + themeController.changeThemeMode(themeMode); + setState(() {}); + } + + Widget _buildAmoledThemeSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.mobile), + text: 'amoledTheme'.tr, + switcher: true, + value: settings.amoledTheme, + onChange: (value) { + themeController.saveOledTheme(value); + MyApp.updateAppState(context, newAmoledTheme: value); + }, + ); + } + + Widget _buildMaterialColorSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.colorfilter), + text: 'materialColor'.tr, + switcher: true, + value: settings.materialColor, + onChange: (value) { + themeController.saveMaterialTheme(value); + MyApp.updateAppState(context, newMaterialColor: value); + }, + ); + } + + Widget _buildLargeElementSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.additem), + text: 'largeElement'.tr, + switcher: true, + value: settings.largeElement, + onChange: (value) { + settings.largeElement = value; + isar.writeTxnSync(() => isar.settings.putSync(settings)); + MyApp.updateAppState(context, newLargeElement: value); + setState(() {}); + }, + ); + } + + Widget _buildFunctionsCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.code_1), + text: 'functions'.tr, + onPressed: () { + _showFunctionsBottomSheet(context); + }, + ); + } + + void _showFunctionsBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildFunctionsTitle(context), + _buildLocationSettingCard(context, setState), + _buildNotificationsSettingCard(context, setState), + _buildTimeRangeSettingCard(context, setState), + _buildTimeStartSettingCard(context, setState), + _buildTimeEndSettingCard(context, setState), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildFunctionsTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'functions'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildLocationSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.map), + text: 'location'.tr, + switcher: true, + value: settings.location, + onChange: (value) async { + if (value) { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + if (!context.mounted) return; + await _showLocationDialog(context); + return; + } + weatherController.getCurrentLocation(); + } + isar.writeTxnSync(() { + settings.location = value; + isar.settings.putSync(settings); + }); + setState(() {}); + }, + ); + } + + Future _showLocationDialog(BuildContext context) 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 _buildNotificationsSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.notification_1), + text: 'notifications'.tr, + switcher: true, + value: settings.notifications, + onChange: (value) async { + final resultExact = await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.requestExactAlarmsPermission(); + final result = Platform.isIOS + ? await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin + >() + ?.requestPermissions() + : await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.requestNotificationsPermission(); + if (result != null && resultExact != null) { + isar.writeTxnSync(() { + settings.notifications = value; + isar.settings.putSync(settings); + }); + if (value) { + weatherController.notification(weatherController.mainWeather); + } else { + flutterLocalNotificationsPlugin.cancelAll(); + } + setState(() {}); + } + }, + ); + } + + Widget _buildTimeRangeSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.notification_status), + text: 'timeRange'.tr, + dropdown: true, + dropdownName: '$timeRange', + dropdownList: const ['1', '2', '3', '4', '5'], + dropdownChange: (String? newValue) { + isar.writeTxnSync(() { + settings.timeRange = int.parse(newValue!); + isar.settings.putSync(settings); + }); + MyApp.updateAppState(context, newTimeRange: int.parse(newValue!)); + if (settings.notifications) { + flutterLocalNotificationsPlugin.cancelAll(); + weatherController.notification(weatherController.mainWeather); + } + }, + ); + } + + Widget _buildTimeStartSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.timer_start), + text: 'timeStart'.tr, + info: true, + infoSettings: true, + infoWidget: _TextInfo( + info: settings.timeformat == '12' + ? DateFormat.jm(locale.languageCode).format( + DateFormat.Hm(locale.languageCode).parse( + weatherController.timeConvert(timeStart).format(context), + ), + ) + : DateFormat.Hm(locale.languageCode).format( + DateFormat.Hm(locale.languageCode).parse( + weatherController.timeConvert(timeStart).format(context), + ), + ), + ), + onPressed: () async { + final TimeOfDay? timeStartPicker = await showTimePicker( + context: context, + initialTime: weatherController.timeConvert(timeStart), + builder: (context, child) { + final Widget mediaQueryWrapper = MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: settings.timeformat == '12' + ? false + : true, + ), + child: child!, + ); + return mediaQueryWrapper; + }, + ); + if (timeStartPicker != null) { + isar.writeTxnSync(() { + settings.timeStart = timeStartPicker.format(context); + isar.settings.putSync(settings); + }); + if (!context.mounted) return; + MyApp.updateAppState( + context, + newTimeStart: timeStartPicker.format(context), + ); + if (settings.notifications) { + flutterLocalNotificationsPlugin.cancelAll(); + weatherController.notification(weatherController.mainWeather); + } + } + }, + ); + } + + Widget _buildTimeEndSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.timer_pause), + text: 'timeEnd'.tr, + info: true, + infoSettings: true, + infoWidget: _TextInfo( + info: settings.timeformat == '12' + ? DateFormat.jm(locale.languageCode).format( + DateFormat.Hm( + locale.languageCode, + ).parse(weatherController.timeConvert(timeEnd).format(context)), + ) + : DateFormat.Hm(locale.languageCode).format( + DateFormat.Hm( + locale.languageCode, + ).parse(weatherController.timeConvert(timeEnd).format(context)), + ), + ), + onPressed: () async { + final TimeOfDay? timeEndPicker = await showTimePicker( + context: context, + initialTime: weatherController.timeConvert(timeEnd), + builder: (context, child) { + final Widget mediaQueryWrapper = MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: settings.timeformat == '12' + ? false + : true, + ), + child: child!, + ); + return mediaQueryWrapper; + }, + ); + if (timeEndPicker != null) { + isar.writeTxnSync(() { + settings.timeEnd = timeEndPicker.format(context); + isar.settings.putSync(settings); + }); + if (!context.mounted) return; + MyApp.updateAppState( + context, + newTimeEnd: timeEndPicker.format(context), + ); + if (settings.notifications) { + flutterLocalNotificationsPlugin.cancelAll(); + weatherController.notification(weatherController.mainWeather); + } + } + }, + ); + } + + Widget _buildDataCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.d_square), + text: 'data'.tr, + onPressed: () { + _showDataBottomSheet(context); + }, + ); + } + + void _showDataBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildDataTitle(context), + _buildRoundDegreeSettingCard(context, setState), + _buildDegreesSettingCard(context, setState), + _buildMeasurementsSettingCard(context, setState), + _buildWindSettingCard(context, setState), + _buildPressureSettingCard(context, setState), + _buildTimeFormatSettingCard(context, setState), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildDataTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'data'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildRoundDegreeSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.cloud_notif), + text: 'roundDegree'.tr, + switcher: true, + value: settings.roundDegree, + onChange: (value) { + settings.roundDegree = value; + isar.writeTxnSync(() => isar.settings.putSync(settings)); + MyApp.updateAppState(context, newRoundDegree: value); + setState(() {}); + }, + ); + } + + Widget _buildDegreesSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.sun_1), + text: 'degrees'.tr, + dropdown: true, + dropdownName: settings.degrees.tr, + dropdownList: ['celsius'.tr, 'fahrenheit'.tr], + dropdownChange: (String? newValue) async { + isar.writeTxnSync(() { + settings.degrees = newValue == 'celsius'.tr + ? 'celsius' + : 'fahrenheit'; + isar.settings.putSync(settings); + }); + await weatherController.deleteAll(false); + await weatherController.setLocation(); + await weatherController.updateCacheCard(true); + setState(() {}); + }, + ); + } + + Widget _buildMeasurementsSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.rulerpen), + text: 'measurements'.tr, + dropdown: true, + dropdownName: settings.measurements.tr, + dropdownList: ['metric'.tr, 'imperial'.tr], + dropdownChange: (String? newValue) async { + isar.writeTxnSync(() { + settings.measurements = newValue == 'metric'.tr + ? 'metric' + : 'imperial'; + isar.settings.putSync(settings); + }); + await weatherController.deleteAll(false); + await weatherController.setLocation(); + await weatherController.updateCacheCard(true); + setState(() {}); + }, + ); + } + + Widget _buildWindSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.wind), + text: 'wind'.tr, + dropdown: true, + dropdownName: settings.wind.tr, + dropdownList: ['kph'.tr, 'm/s'.tr], + dropdownChange: (String? newValue) async { + isar.writeTxnSync(() { + settings.wind = newValue == 'kph'.tr ? 'kph' : 'm/s'; + isar.settings.putSync(settings); + }); + setState(() {}); + }, + ); + } + + Widget _buildPressureSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.ruler), + text: 'pressure'.tr, + dropdown: true, + dropdownName: settings.pressure.tr, + dropdownList: ['hPa'.tr, 'mmHg'.tr], + dropdownChange: (String? newValue) async { + isar.writeTxnSync(() { + settings.pressure = newValue == 'hPa'.tr ? 'hPa' : 'mmHg'; + isar.settings.putSync(settings); + }); + setState(() {}); + }, + ); + } + + Widget _buildTimeFormatSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.clock_1), + text: 'timeformat'.tr, + dropdown: true, + dropdownName: settings.timeformat.tr, + dropdownList: ['12'.tr, '24'.tr], + dropdownChange: (String? newValue) { + isar.writeTxnSync(() { + settings.timeformat = newValue == '12'.tr ? '12' : '24'; + isar.settings.putSync(settings); + }); + setState(() {}); + }, + ); + } + + Widget _buildWidgetCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.setting_3), + text: 'widget'.tr, + onPressed: () { + _showWidgetBottomSheet(context); + }, + ); + } + + void _showWidgetBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildWidgetTitle(context), + _buildAddWidgetSettingCard(context, setState), + _buildWidgetBackgroundSettingCard(context, setState), + _buildWidgetTextSettingCard(context, setState), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildWidgetTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'widget'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildAddWidgetSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.add_square), + text: 'addWidget'.tr, + onPressed: () { + HomeWidget.requestPinWidget( + name: androidWidgetName, + androidName: androidWidgetName, + qualifiedAndroidName: 'com.yoshi.rain.OreoWidget', + ); + }, + ); + } + + Widget _buildWidgetBackgroundSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.bucket_square), + text: 'widgetBackground'.tr, + info: true, + infoWidget: CircleAvatar( + backgroundColor: context.theme.indicatorColor, + radius: 11, + child: CircleAvatar( + backgroundColor: widgetBackgroundColor.isEmpty + ? context.theme.primaryColor + : HexColor.fromHex(widgetBackgroundColor), + radius: 10, + ), + ), + onPressed: () { + colorBackground = null; + showDialog( + context: context, + builder: (context) => Dialog( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildWidgetBackgroundTitle(context), + _buildColorPicker(context), + _buildColorPickerButton(context), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildWidgetBackgroundTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'widgetBackground'.tr, + style: context.textTheme.titleMedium?.copyWith(fontSize: 18), + ), + ); + } + + Widget _buildColorPicker(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Theme( + data: context.theme.copyWith( + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + ), + child: ColorPicker( + color: widgetBackgroundColor.isEmpty + ? context.theme.primaryColor + : HexColor.fromHex(widgetBackgroundColor), + onChanged: (pickedColor) { + colorBackground = pickedColor.toHex(); + }, + ), + ), + ); + } + + Widget _buildColorPickerButton(BuildContext context) { + return IconButton( + icon: const Icon(IconsaxPlusLinear.tick_square), + onPressed: () { + if (colorBackground == null) { + return; + } + weatherController.updateWidgetBackgroundColor(colorBackground!); + MyApp.updateAppState( + context, + newWidgetBackgroundColor: colorBackground, + ); + Get.back(); + }, + ); + } + + Widget _buildWidgetTextSettingCard( + BuildContext context, + StateSetter setState, + ) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.text_block), + text: 'widgetText'.tr, + info: true, + infoWidget: CircleAvatar( + backgroundColor: context.theme.indicatorColor, + radius: 11, + child: CircleAvatar( + backgroundColor: widgetTextColor.isEmpty + ? context.theme.primaryColor + : HexColor.fromHex(widgetTextColor), + radius: 10, + ), + ), + onPressed: () { + colorText = null; + showDialog( + context: context, + builder: (context) => Dialog( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildWidgetTextTitle(context), + _buildTextColorPicker(context), + _buildTextColorPickerButton(context), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildWidgetTextTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'widgetText'.tr, + style: context.textTheme.titleMedium?.copyWith(fontSize: 18), + ), + ); + } + + Widget _buildTextColorPicker(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Theme( + data: context.theme.copyWith( + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + ), + child: ColorPicker( + color: widgetTextColor.isEmpty + ? context.theme.primaryColor + : HexColor.fromHex(widgetTextColor), + onChanged: (pickedColor) { + colorText = pickedColor.toHex(); + }, + ), + ), + ); + } + + Widget _buildTextColorPickerButton(BuildContext context) { + return IconButton( + icon: const Icon(IconsaxPlusLinear.tick_square), + onPressed: () { + if (colorText == null) { + return; + } + weatherController.updateWidgetTextColor(colorText!); + MyApp.updateAppState(context, newWidgetTextColor: colorText); + Get.back(); + }, + ); + } + + Widget _buildMapCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.map), + text: 'map'.tr, + onPressed: () { + _showMapBottomSheet(context); + }, + ); + } + + void _showMapBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildMapTitle(context), + _buildHideMapSettingCard(context, setState), + _buildClearCacheStoreSettingCard(context), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildMapTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'map'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildHideMapSettingCard(BuildContext context, StateSetter setState) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.location_slash), + text: 'hideMap'.tr, + switcher: true, + value: settings.hideMap, + onChange: (value) { + settings.hideMap = value; + isar.writeTxnSync(() => isar.settings.putSync(settings)); + setState(() {}); + Future.delayed( + const Duration(milliseconds: 500), + () => Restart.restartApp(), + ); + }, + ); + } + + Widget _buildClearCacheStoreSettingCard(BuildContext context) { + return SettingCard( + elevation: 4, + icon: const Icon(IconsaxPlusLinear.trash_square), + text: 'clearCacheStore'.tr, + onPressed: () => _showClearCacheStoreDialog(context), + ); + } + + void _showClearCacheStoreDialog(BuildContext context) { + showAdaptiveDialog( + context: context, + builder: (context) => AlertDialog.adaptive( + title: Text( + 'deletedCacheStore'.tr, + style: context.textTheme.titleLarge, + ), + content: Text( + 'deletedCacheStoreQuery'.tr, + style: context.textTheme.titleMedium, + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, + style: context.textTheme.titleMedium?.copyWith( + color: Colors.blueAccent, + ), + ), + ), + TextButton( + onPressed: () async { + final dir = await getTemporaryDirectory(); + final cacheStoreFuture = FileCacheStore( + '${dir.path}${Platform.pathSeparator}MapTiles', + ); + cacheStoreFuture.clean(); + Get.back(); + }, + child: Text( + 'delete'.tr, + style: context.textTheme.titleMedium?.copyWith(color: Colors.red), + ), + ), + ], + ), + ); + } + + Widget _buildLanguageCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.language_square), + text: 'language'.tr, + info: true, + infoSettings: true, + infoWidget: _TextInfo( + info: appLanguages.firstWhere( + (element) => (element['locale'] == locale), + orElse: () => {'name': ''}, + )['name'], + ), + onPressed: () { + _showLanguageBottomSheet(context); + }, + ); + } + + void _showLanguageBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return ListView( + children: [ + _buildLanguageTitle(context), + _buildLanguageList(context), + const Gap(10), + ], + ); + }, + ), + ); + }, + ); + } + + Widget _buildLanguageTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'language'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + textAlign: TextAlign.center, + ), + ); + } + + Widget _buildLanguageList(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: appLanguages.length, + itemBuilder: (context, index) { + return Card( + elevation: 4, + margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: ListTile( + title: Text( + appLanguages[index]['name'], + style: context.textTheme.labelLarge, + textAlign: TextAlign.center, + ), + onTap: () { + MyApp.updateAppState( + context, + newLocale: appLanguages[index]['locale'], + ); + _updateLanguage(appLanguages[index]['locale']); + }, + ), + ); + }, + ); + } + + Widget _buildGroupsCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.link_square), + text: 'groups'.tr, + onPressed: () { + _showGroupsBottomSheet(context); + }, + ); + } + + void _showGroupsBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + child: StatefulBuilder( + builder: (BuildContext context, setState) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildGroupsTitle(context), + _buildDiscordSettingCard(context), + _buildTelegramSettingCard(context), + const Gap(10), + ], + ), + ); + }, + ), + ); + }, + ); + } + + Widget _buildGroupsTitle(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + 'groups'.tr, + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), + ), + ); + } + + Widget _buildDiscordSettingCard(BuildContext context) { + return SettingCard( + elevation: 4, + icon: const Icon(LineAwesomeIcons.discord), + text: 'Discord', + onPressed: () => + weatherController.urlLauncher('https://discord.gg/JMMa9aHh8f'), + ); + } + + Widget _buildTelegramSettingCard(BuildContext context) { + return SettingCard( + elevation: 4, + icon: const Icon(LineAwesomeIcons.telegram), + text: 'Telegram', + onPressed: () => + weatherController.urlLauncher('https://t.me/darkmoonightX'), + ); + } + + Widget _buildLicenseCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.document), + text: 'license'.tr, + onPressed: () => Get.to( + () => LicensePage( + applicationIcon: Container( + width: 100, + height: 100, + margin: const EdgeInsets.symmetric(vertical: 5), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + image: DecorationImage( + image: AssetImage('assets/icons/icon.png'), + ), + ), + ), + applicationName: 'Rain', + applicationVersion: appVersion, + ), + transition: Transition.downToUp, + ), + ); + } + + Widget _buildVersionCard(BuildContext context) { + return SettingCard( + icon: const Icon(IconsaxPlusLinear.hierarchy_square_2), + text: 'version'.tr, + info: true, + infoWidget: _TextInfo(info: '$appVersion'), + ); + } + + Widget _buildGitHubCard(BuildContext context) { + return SettingCard( + icon: const Icon(LineAwesomeIcons.github), + text: '${'project'.tr} GitHub', + onPressed: () => + weatherController.urlLauncher('https://github.com/darkmoonight/Rain'), + ); + } + + Widget _buildOpenMeteoText(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10), + child: GestureDetector( + child: Text( + 'openMeteo'.tr, + style: context.textTheme.bodyMedium, + overflow: TextOverflow.visible, + textAlign: TextAlign.center, + ), + onTap: () => weatherController.urlLauncher('https://open-meteo.com/'), + ), + ); + } } class _TextInfo extends StatelessWidget { diff --git a/lib/app/ui/settings/widgets/setting_card.dart b/lib/app/ui/settings/widgets/setting_card.dart old mode 100644 new mode 100755 index c22e9f7..2721a7b --- a/lib/app/ui/settings/widgets/setting_card.dart +++ b/lib/app/ui/settings/widgets/setting_card.dart @@ -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? dropdownList; - final Function(String?)? dropdownCange; + final ValueChanged? dropdownChange; final bool? value; - final Function()? onPressed; - final Function(bool)? onChange; + final VoidCallback? onPressed; + final ValueChanged? onChange; final double? elevation; @override @@ -49,45 +50,58 @@ class SettingCard extends StatelessWidget { style: context.textTheme.titleMedium, overflow: TextOverflow.visible, ), - trailing: - switcher - ? Transform.scale( - scale: 0.8, - child: Switch(value: value!, onChanged: onChange), - ) - : dropdown - ? DropdownButton( - icon: const Padding( - padding: EdgeInsets.only(left: 7), - child: Icon(IconsaxPlusLinear.arrow_down), - ), - iconSize: 15, - alignment: AlignmentDirectional.centerEnd, - borderRadius: const BorderRadius.all(Radius.circular(15)), - underline: Container(), - value: dropdownName, - items: - dropdownList!.map>(( - String value, - ) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: dropdownCange, - ) - : info - ? infoSettings - ? Wrap( - children: [ - infoWidget!, - const Icon(IconsaxPlusLinear.arrow_right_3, size: 18), - ], - ) - : infoWidget! - : const Icon(IconsaxPlusLinear.arrow_right_3, size: 18), + 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), + ); + } + + Widget _buildDropdownWidget() { + return DropdownButton( + icon: const Padding( + padding: EdgeInsets.only(left: 7), + child: Icon(IconsaxPlusLinear.arrow_down), + ), + iconSize: 15, + alignment: AlignmentDirectional.centerEnd, + borderRadius: const BorderRadius.all(Radius.circular(15)), + underline: Container(), + value: dropdownName, + items: dropdownList!.map>((String value) { + return DropdownMenuItem(value: value, child: Text(value)); + }).toList(), + onChanged: dropdownChange, + ); + } + + Widget _buildInfoWidget() { + if (infoSettings) { + return Wrap( + children: [ + infoWidget!, + const Icon(IconsaxPlusLinear.arrow_right_3, size: 18), + ], + ); + } else { + return infoWidget!; + } + } } diff --git a/lib/app/ui/widgets/button.dart b/lib/app/ui/widgets/button.dart old mode 100644 new mode 100755 index 6205978..8aa03cf --- a/lib/app/ui/widgets/button.dart +++ b/lib/app/ui/widgets/button.dart @@ -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), + ), + ); + } } diff --git a/lib/app/ui/widgets/shimmer.dart b/lib/app/ui/widgets/shimmer.dart old mode 100644 new mode 100755 index a439a89..f470e1d --- a/lib/app/ui/widgets/shimmer.dart +++ b/lib/app/ui/widgets/shimmer.dart @@ -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)); + } } diff --git a/lib/app/ui/widgets/text_form.dart b/lib/app/ui/widgets/text_form.dart old mode 100644 new mode 100755 index f0d9d9b..3c67219 --- a/lib/app/ui/widgets/text_form.dart +++ b/lib/app/ui/widgets/text_form.dart @@ -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( - focusNode: focusNode, - controller: controller, - keyboardType: type, - style: context.textTheme.labelLarge, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric( - horizontal: 12.5, - vertical: 0, - ), - prefixIcon: icon, - suffixIcon: iconButton, - labelText: labelText, - ), - validator: validator, - onChanged: onChanged, - ), + child: _buildTextFormField(context), + ); + } + + Widget _buildTextFormField(BuildContext context) { + return TextFormField( + focusNode: focusNode, + controller: controller, + keyboardType: type, + style: context.textTheme.labelLarge, + 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, ); } } diff --git a/lib/app/ui/widgets/weather/daily/daily_card.dart b/lib/app/ui/widgets/weather/daily/daily_card.dart old mode 100644 new mode 100755 index 11afec1..b23cf68 --- a/lib/app/ui/widgets/weather/daily/daily_card.dart +++ b/lib/app/ui/widgets/weather/daily/daily_card.dart @@ -14,6 +14,7 @@ class DailyCard extends StatefulWidget { required this.temperature2MMax, required this.temperature2MMin, }); + final DateTime timeDaily; final int? weathercodeDaily; final double? temperature2MMax; @@ -29,54 +30,70 @@ class _DailyCardState extends State { @override Widget build(BuildContext context) { - return widget.weathercodeDaily == null - ? Container() - : Card( - margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${statusData.getDegree(widget.temperature2MMin?.round())} / ${statusData.getDegree(widget.temperature2MMax?.round())}', - style: context.textTheme.titleLarge?.copyWith( - fontSize: 22, - fontWeight: FontWeight.w600, - ), - ), - const Gap(5), - Text( - DateFormat.MMMMEEEEd( - locale.languageCode, - ).format(widget.timeDaily), - style: context.textTheme.titleMedium?.copyWith( - color: Colors.grey, - fontWeight: FontWeight.w400, - ), - ), - const Gap(5), - Text( - statusWeather.getText(widget.weathercodeDaily), - style: context.textTheme.titleMedium?.copyWith( - color: Colors.grey, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - const Gap(5), - Image.asset( - statusWeather.getImageNowDaily(widget.weathercodeDaily), - scale: 6.5, - ), - ], + 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: [ + _buildTemperatureInfo(context), + const Gap(5), + _buildWeatherImage(), + ], + ), + ), + ); + } + + Widget _buildTemperatureInfo(BuildContext context) { + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${statusData.getDegree(widget.temperature2MMin?.round())} / ${statusData.getDegree(widget.temperature2MMax?.round())}', + style: context.textTheme.titleLarge?.copyWith( + fontSize: 22, + fontWeight: FontWeight.w600, ), ), - ); + const Gap(5), + _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, + ), + ); + } + + Widget _buildWeatherDescription(BuildContext context) { + return Text( + statusWeather.getText(widget.weathercodeDaily), + style: context.textTheme.titleMedium?.copyWith( + color: Colors.grey, + fontWeight: FontWeight.w400, + ), + ); + } + + Widget _buildWeatherImage() { + return Image.asset( + statusWeather.getImageNowDaily(widget.weathercodeDaily), + scale: 6.5, + ); } } diff --git a/lib/app/ui/widgets/weather/daily/daily_card_info.dart b/lib/app/ui/widgets/weather/daily/daily_card_info.dart old mode 100644 new mode 100755 index 1d04f42..ccd57d5 --- a/lib/app/ui/widgets/weather/daily/daily_card_info.dart +++ b/lib/app/ui/widgets/weather/daily/daily_card_info.dart @@ -52,32 +52,11 @@ class _DailyCardInfoState extends State { 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,172 +68,281 @@ class _DailyCardInfoState extends State { }, itemCount: timeDaily.length, itemBuilder: (context, index) { - final indexedWeatherCodeDaily = weatherCodeDaily[index]; - final temperature2MMin = weatherData.temperature2MMin?[index]; - final temperature2MMax = weatherData.temperature2MMax?[index]; - final apparentTemperatureMin = - weatherData.apparentTemperatureMin?[index]; - final apparentTemperatureMax = - weatherData.apparentTemperatureMax?[index]; - final uvIndexMax = weatherData.uvIndexMax?[index]; - final windDirection10MDominant = - weatherData.winddirection10MDominant?[index]; - final windSpeed10MMax = weatherData.windspeed10MMax?[index]; - final windGusts10MMax = weatherData.windgusts10MMax?[index]; - final precipitationProbabilityMax = - weatherData.precipitationProbabilityMax?[index]; - final rainSum = weatherData.rainSum?[index]; - final precipitationSum = weatherData.precipitationSum?[index]; - final sunrise = weatherData.sunrise![index]; - final sunset = weatherData.sunset![index]; - - final startIndex = index * 24; - - return indexedWeatherCodeDaily == null - ? null - : 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], - timeDay: sunrise, - timeNight: sunset, - tempMax: temperature2MMax!, - tempMin: temperature2MMin!, - ), - Card( - margin: const EdgeInsets.only(bottom: 15), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - child: SizedBox( - height: 135, - child: ScrollablePositionedList.separated( - separatorBuilder: ( - BuildContext context, - int index, - ) { - return const VerticalDivider( - width: 10, - indent: 40, - endIndent: 40, - ); - }, - 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, - ), - ), - ); - }, - ), - ), - ), - ), - 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], - precipitationProbability: - weatherData.precipitationProbability?[startIndex + - hourOfDay], - shortwaveRadiation: - weatherData.shortwaveRadiation?[startIndex + - hourOfDay], - initiallyExpanded: true, - title: 'hourlyVariables'.tr, - ), - DescContainer( - apparentTemperatureMin: apparentTemperatureMin, - apparentTemperatureMax: apparentTemperatureMax, - uvIndexMax: uvIndexMax, - windDirection10MDominant: windDirection10MDominant, - windSpeed10MMax: windSpeed10MMax, - windGusts10MMax: windGusts10MMax, - precipitationProbabilityMax: - precipitationProbabilityMax, - rainSum: rainSum, - precipitationSum: precipitationSum, - initiallyExpanded: true, - title: 'dailyVariables'.tr, - ), - ], - ), - ); + return _buildPageContent(context, weatherData, index); }, ), ), ); } + + AppBar _buildAppBar( + BuildContext context, + TextTheme textTheme, + List 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 uvIndexMax = weatherData.uvIndexMax?[index]; + final windDirection10MDominant = + weatherData.winddirection10MDominant?[index]; + final windSpeed10MMax = weatherData.windspeed10MMax?[index]; + final windGusts10MMax = weatherData.windgusts10MMax?[index]; + final precipitationProbabilityMax = + weatherData.precipitationProbabilityMax?[index]; + final rainSum = weatherData.rainSum?[index]; + final precipitationSum = weatherData.precipitationSum?[index]; + final sunrise = weatherData.sunrise?[index]; + final sunset = weatherData.sunset?[index]; + + if (sunrise == null || sunset == null) { + return Container(); + } + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + child: ListView( + children: [ + _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: 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), + child: SizedBox( + height: 135, + child: ScrollablePositionedList.separated( + separatorBuilder: (BuildContext context, int index) { + return const VerticalDivider( + width: 10, + indent: 40, + endIndent: 40, + ); + }, + scrollDirection: Axis.horizontal, + itemCount: 24, + itemBuilder: (ctx, i) { + 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)), + ), + 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?[hourlyIndex], + shortwaveRadiation: weatherData.shortwaveRadiation?[hourlyIndex], + initiallyExpanded: true, + title: 'hourlyVariables'.tr, + ); + } + + 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, + rainSum: rainSum, + precipitationSum: precipitationSum, + initiallyExpanded: true, + title: 'dailyVariables'.tr, + ); + } } diff --git a/lib/app/ui/widgets/weather/daily/daily_card_list.dart b/lib/app/ui/widgets/weather/daily/daily_card_list.dart old mode 100644 new mode 100755 index 787bde4..a8e1b46 --- a/lib/app/ui/widgets/weather/daily/daily_card_list.dart +++ b/lib/app/ui/widgets/weather/daily/daily_card_list.dart @@ -16,50 +16,69 @@ class DailyCardList extends StatefulWidget { class _DailyCardListState extends State { @override Widget build(BuildContext context) { - const transparent = Colors.transparent; final weatherData = widget.weatherData; final timeDaily = weatherData.timeDaily ?? []; return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - centerTitle: true, - leading: IconButton( - onPressed: () { - Get.back(); - }, - icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20), - splashColor: transparent, - highlightColor: transparent, - ), - title: Text( - 'weatherMore'.tr, - style: context.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - fontSize: 18, - ), - ), - ), + appBar: _buildAppBar(context), body: SafeArea( child: ListView.builder( itemCount: timeDaily.length, - itemBuilder: - (context, index) => 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], - ), - ), + itemBuilder: (context, index) => + _buildDailyCardItem(context, weatherData, index), ), ), ); } + + AppBar _buildAppBar(BuildContext context) { + 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( + 'weatherMore'.tr, + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 18, + ), + ), + ); + } + + 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, + weathercodeDaily: weathercodeDaily, + temperature2MMax: temperature2MMax, + temperature2MMin: temperature2MMin, + ), + ); + } } diff --git a/lib/app/ui/widgets/weather/daily/daily_container.dart b/lib/app/ui/widgets/weather/daily/daily_container.dart old mode 100644 new mode 100755 index e2056c4..a63667c --- a/lib/app/ui/widgets/weather/daily/daily_container.dart +++ b/lib/app/ui/widgets/weather/daily/daily_container.dart @@ -28,7 +28,9 @@ class _DailyContainerState extends State { @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,104 +44,163 @@ class _DailyContainerState extends State { padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), child: Column( children: [ - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: 7, - itemBuilder: (ctx, index) { - return InkWell( - splashColor: splashColor, - borderRadius: inkWellBorderRadius, - onTap: - () => Get.to( - () => DailyCardInfo( - weatherData: weatherData, - index: index, - ), - transition: Transition.downToUp, - ), - child: Container( - margin: const EdgeInsets.symmetric(vertical: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - DateFormat.EEEE( - locale.languageCode, - ).format((weatherData.timeDaily ?? [])[index]), - style: labelLarge, - overflow: TextOverflow.ellipsis, - ), - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - statusWeather.getImage7Day( - weatherCodeDaily[index], - ), - scale: 3, - ), - const Gap(5), - Expanded( - child: Text( - statusWeather.getText( - weatherCodeDaily[index], - ), - style: labelLarge, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - statusData.getDegree( - (weatherData.temperature2MMin ?? [])[index] - ?.round(), - ), - style: labelLarge, - ), - Text(' / ', style: labelLarge), - Text( - statusData.getDegree( - (weatherData.temperature2MMax ?? [])[index] - ?.round(), - ), - style: labelLarge, - ), - ], - ), - ), - ], - ), - ), - ); - }, + _buildDailyListView( + context, + weatherData, + weatherCodeDaily, + labelLarge, ), const Divider(), - InkWell( - splashColor: splashColor, - borderRadius: inkWellBorderRadius, - onTap: widget.onTap, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - 'weatherMore'.tr, - style: textTheme.titleMedium, - overflow: TextOverflow.ellipsis, - ), - ), - ), + _buildMoreInfoButton(context, splashColor, inkWellBorderRadius), ], ), ), ); } + + Widget _buildDailyListView( + BuildContext context, + WeatherCard weatherData, + List 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 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), + transition: Transition.downToUp, + ), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _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, + ).format((weatherData.timeDaily ?? [])[index]), + style: labelLarge, + overflow: TextOverflow.ellipsis, + ), + ); + } + + Widget _buildWeatherInfo( + List 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(weatherCode), scale: 3), + const Gap(5), + Expanded( + child: Text( + statusWeather.getText(weatherCode), + style: labelLarge, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + + Widget _buildTemperatureRange( + WeatherCard weatherData, + int index, + TextStyle? labelLarge, + ) { + return Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + statusData.getDegree( + (weatherData.temperature2MMax ?? [])[index]?.round(), + ), + style: labelLarge, + ), + Text(' / ', style: labelLarge), + Text( + statusData.getDegree( + (weatherData.temperature2MMin ?? [])[index]?.round(), + ), + style: labelLarge, + ), + ], + ), + ); + } + + Widget _buildMoreInfoButton( + BuildContext context, + Color splashColor, + BorderRadius inkWellBorderRadius, + ) { + return InkWell( + splashColor: splashColor, + borderRadius: inkWellBorderRadius, + onTap: widget.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + 'weatherMore'.tr, + style: context.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } } diff --git a/lib/app/ui/widgets/weather/desc/desc.dart b/lib/app/ui/widgets/weather/desc/desc.dart old mode 100644 new mode 100755 index ee28267..20a2c7c --- a/lib/app/ui/widgets/weather/desc/desc.dart +++ b/lib/app/ui/widgets/weather/desc/desc.dart @@ -10,6 +10,7 @@ class DescWeather extends StatefulWidget { required this.desc, this.message = '', }); + final String imageName; final String value; final String desc; @@ -26,34 +27,42 @@ class _DescWeatherState extends State { 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( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset(widget.imageName, scale: 20), - const Gap(5), - Text( - widget.value, - style: textTheme.labelLarge, - overflow: TextOverflow.ellipsis, - ), - Expanded( - child: Text( - widget.desc, - style: textTheme.bodySmall, - overflow: hide ? TextOverflow.ellipsis : TextOverflow.visible, - textAlign: TextAlign.center, - ), - ), - ], - ), + child: _buildContent(textTheme), ), ), ); } + + void _toggleDescriptionVisibility() { + setState(() => hide = !hide); + } + + Widget _buildContent(TextTheme textTheme) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(widget.imageName, scale: 20), + const Gap(5), + Text( + widget.value, + style: textTheme.labelLarge, + overflow: TextOverflow.ellipsis, + ), + Expanded( + child: Text( + widget.desc, + style: textTheme.bodySmall, + overflow: hide ? TextOverflow.ellipsis : TextOverflow.visible, + textAlign: TextAlign.center, + ), + ), + ], + ); + } } diff --git a/lib/app/ui/widgets/weather/desc/desc_container.dart b/lib/app/ui/widgets/weather/desc/desc_container.dart old mode 100644 new mode 100755 index 76ef35a..8354cad --- a/lib/app/ui/widgets/weather/desc/desc_container.dart +++ b/lib/app/ui/widgets/weather/desc/desc_container.dart @@ -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 { @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 _buildWeatherDescriptions(BuildContext context) { + final List 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; + } } diff --git a/lib/app/ui/widgets/weather/desc/message.dart b/lib/app/ui/widgets/weather/desc/message.dart old mode 100644 new mode 100755 index 9254848..1a5bcba --- a/lib/app/ui/widgets/weather/desc/message.dart +++ b/lib/app/ui/widgets/weather/desc/message.dart @@ -2,58 +2,64 @@ import 'package:get/get.dart'; class Message { String getPressure(int? pressure) { - if (pressure != null) { - if (pressure < 1000) { - return 'low'.tr; - } else if (pressure > 1020) { - return 'high'.tr; - } else { - return 'normal'.tr; - } - } else { - return ''; - } + return _getPressureDescription(pressure); } String getUvIndex(int? uvIndex) { - if (uvIndex != null) { - if (uvIndex < 3) { - return 'uvLow'.tr; - } else if (uvIndex < 6) { - return 'uvAverage'.tr; - } else if (uvIndex < 8) { - return 'uvHigh'.tr; - } else if (uvIndex < 11) { - return 'uvVeryHigh'.tr; - } else { - return 'uvExtreme'.tr; - } - } else { - return ''; - } + return _getUvIndexDescription(uvIndex); } String getDirection(int? direction) { - if (direction != null) { - if (direction >= 337.5 || direction < 22.5) { - return 'north'.tr; - } else if (direction >= 22.5 && direction < 67.5) { - return 'northeast'.tr; - } else if (direction >= 67.5 && direction < 112.5) { - return 'east'.tr; - } else if (direction >= 112.5 && direction < 157.5) { - return 'southeast'.tr; - } else if (direction >= 157.5 && direction < 202.5) { - return 'south'.tr; - } else if (direction >= 202.5 && direction < 247.5) { - return 'southwest'.tr; - } else if (direction >= 247.5 && direction < 292.5) { - return 'west'.tr; - } else { - return 'northwest'.tr; - } + return _getDirectionDescription(direction); + } + + String _getPressureDescription(int? pressure) { + if (pressure == null) return ''; + + if (pressure < 1000) { + return 'low'.tr; + } else if (pressure > 1020) { + return 'high'.tr; } else { - return ''; + return 'normal'.tr; + } + } + + String _getUvIndexDescription(int? uvIndex) { + if (uvIndex == null) return ''; + + if (uvIndex < 3) { + return 'uvLow'.tr; + } else if (uvIndex < 6) { + return 'uvAverage'.tr; + } else if (uvIndex < 8) { + return 'uvHigh'.tr; + } else if (uvIndex < 11) { + return 'uvVeryHigh'.tr; + } else { + return 'uvExtreme'.tr; + } + } + + 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) { + return 'northeast'.tr; + } else if (direction >= 67.5 && direction < 112.5) { + return 'east'.tr; + } else if (direction >= 112.5 && direction < 157.5) { + return 'southeast'.tr; + } else if (direction >= 157.5 && direction < 202.5) { + return 'south'.tr; + } else if (direction >= 202.5 && direction < 247.5) { + return 'southwest'.tr; + } else if (direction >= 247.5 && direction < 292.5) { + return 'west'.tr; + } else { + return 'northwest'.tr; } } } diff --git a/lib/app/ui/widgets/weather/hourly.dart b/lib/app/ui/widgets/weather/hourly.dart old mode 100644 new mode 100755 index 61bee81..3bc93f4 --- a/lib/app/ui/widgets/weather/hourly.dart +++ b/lib/app/ui/widgets/weather/hourly.dart @@ -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 { Widget build(BuildContext context) { final textTheme = context.textTheme; final time = widget.time; + return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Column( - children: [ - Text(statusData.getTimeFormat(time), style: textTheme.labelLarge), - Text( - 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, - ), + _buildTimeText(textTheme, time), + _buildWeatherImage(), + _buildTemperatureText(textTheme), + ], + ); + } + + Widget _buildTimeText(TextTheme textTheme, String time) { + return Column( + children: [ + Text(statusData.getTimeFormat(time), style: textTheme.labelLarge), Text( - statusData.getDegree(widget.degree.round()), - style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), + DateFormat('E', locale.languageCode).format(DateTime.tryParse(time)!), + style: textTheme.labelLarge?.copyWith(color: Colors.grey), ), ], ); } + + 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), + ); + } } diff --git a/lib/app/ui/widgets/weather/now.dart b/lib/app/ui/widgets/weather/now.dart old mode 100644 new mode 100755 index 2836ea6..1805479 --- a/lib/app/ui/widgets/weather/now.dart +++ b/lib/app/ui/widgets/weather/now.dart @@ -18,6 +18,7 @@ class Now extends StatefulWidget { required this.tempMin, required this.feels, }); + final String time; final String timeDay; final String timeNight; @@ -38,132 +39,144 @@ class _NowState extends State { @override Widget build(BuildContext context) { return largeElement - ? 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))], - ), - ), - 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, - ), - ), - ], + ? _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), + _buildWeatherImage(200), + _buildTemperatureText(context, widget.degree, 90), + Text( + statusWeather.getText(widget.weather), + style: context.textTheme.titleLarge, ), - ) - : Card( - margin: const EdgeInsets.only(bottom: 15), - child: Padding( - padding: const EdgeInsets.only( - top: 18, - bottom: 18, - left: 25, - right: 15, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - DateFormat.MMMMEEEEd( - locale.languageCode, - ).format(DateTime.parse(widget.time)), - style: context.textTheme.labelLarge?.copyWith( - color: Colors.grey, - ), - ), - const Gap(5), - Text( - statusWeather.getText(widget.weather), - style: context.textTheme.titleLarge?.copyWith( - fontSize: 20, - ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('feels'.tr, style: context.textTheme.bodyMedium), - Text(' • ', style: context.textTheme.bodyMedium), - Text( - statusData.getDegree(widget.feels.round()), - 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( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - statusData.getDegree((widget.tempMin.round())), - style: context.textTheme.labelLarge, - ), - Text(' / ', style: context.textTheme.labelLarge), - Text( - statusData.getDegree((widget.tempMax.round())), - style: context.textTheme.labelLarge, - ), - ], - ), - ], + const Gap(5), + _buildDateText(context), + ], + ), + ); + } + + Widget _buildCompactElementLayout(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 15), + child: Padding( + padding: const EdgeInsets.only( + top: 18, + bottom: 18, + left: 25, + right: 15, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDateText(context), + const Gap(5), + Text( + statusWeather.getText(widget.weather), + style: context.textTheme.titleLarge?.copyWith(fontSize: 20), ), - ), - Image( - image: AssetImage( - statusWeather.getImageNow( - widget.weather, - widget.time, - widget.timeDay, - widget.timeNight, - ), - ), - fit: BoxFit.fill, - height: 140, - ), - ], + _buildFeelsLikeText(context), + const Gap(30), + _buildTemperatureCompactText(context, widget.degree), + const Gap(5), + _buildMinMaxTemperatureText(context), + ], + ), ), - ), - ); + _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), + Text(' • ', style: context.textTheme.bodyMedium), + Text( + statusData.getDegree(widget.feels.round()), + style: context.textTheme.bodyMedium, + ), + ], + ); + } + + Widget _buildMinMaxTemperatureText(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + statusData.getDegree((widget.tempMin.round())), + style: context.textTheme.labelLarge, + ), + Text(' / ', style: context.textTheme.labelLarge), + Text( + statusData.getDegree((widget.tempMax.round())), + style: context.textTheme.labelLarge, + ), + ], + ); } } diff --git a/lib/app/ui/widgets/weather/status/status_data.dart b/lib/app/ui/widgets/weather/status/status_data.dart old mode 100644 new mode 100755 index 3a20b20..568c145 --- a/lib/app/ui/widgets/weather/status/status_data.dart +++ b/lib/app/ui/widgets/weather/status/status_data.dart @@ -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) { - switch (settings.measurements) { - case 'metric': - return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}'; - case 'imperial': - return '${length > 5280 ? (length / 5280).round() : (length / 5280).toStringAsFixed(2)} ${'mi'.tr}'; - default: - return '${length > 1000 ? (length / 1000).round() : (length / 1000).toStringAsFixed(2)} ${'km'.tr}'; - } - } else { - return ''; + String _formatVisibility(double? length) { + if (length == null) return ''; + + switch (settings.measurements) { + case 'metric': + return _formatMetricVisibility(length); + case 'imperial': + return _formatImperialVisibility(length); + default: + 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); diff --git a/lib/app/ui/widgets/weather/status/status_weather.dart b/lib/app/ui/widgets/weather/status/status_weather.dart old mode 100644 new mode 100755 index 5d58fdd..6bc8516 --- a/lib/app/ui/widgets/weather/status/status_weather.dart +++ b/lib/app/ui/widgets/weather/status/status_weather.dart @@ -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> 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> _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> _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> _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'}, + }; } diff --git a/lib/app/ui/widgets/weather/sunset_sunrise.dart b/lib/app/ui/widgets/weather/sunset_sunrise.dart old mode 100644 new mode 100755 index 57f3f9d..5078e4c --- a/lib/app/ui/widgets/weather/sunset_sunrise.dart +++ b/lib/app/ui/widgets/weather/sunset_sunrise.dart @@ -32,65 +32,54 @@ class _SunsetSunriseState extends State { 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( - 'sunrise'.tr, - style: titleSmall, - overflow: TextOverflow.ellipsis, - ), - const Gap(2), - Text( - statusData.getTimeFormat(widget.timeSunrise), - style: titleLarge, - ), - ], - ), - ), - const Gap(5), - Flexible( - child: Image.asset('assets/images/sunrise.png', scale: 10), - ), - ], - ), + _buildSunTimeColumn( + context, + 'sunrise'.tr, + statusData.getTimeFormat(widget.timeSunrise), + 'assets/images/sunrise.png', + titleSmall, + titleLarge, ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '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), - ), - ], - ), + _buildSunTimeColumn( + context, + 'sunset'.tr, + statusData.getTimeFormat(widget.timeSunset), + '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)), + ], + ), + ); + } } diff --git a/lib/app/utils/color_converter.dart b/lib/app/utils/color_converter.dart old mode 100644 new mode 100755 diff --git a/lib/app/utils/device_info.dart b/lib/app/utils/device_info.dart old mode 100644 new mode 100755 diff --git a/lib/app/utils/notification.dart b/lib/app/utils/notification.dart old mode 100644 new mode 100755 index edd4d89..d0f8f9a --- a/lib/app/utils/notification.dart +++ b/lib/app/utils/notification.dart @@ -4,38 +4,55 @@ 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 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, - ); + await flutterLocalNotificationsPlugin.zonedSchedule( + id, + title, + body, + scheduledTime, + notificationDetails, + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + payload: imagePath, + ); + } catch (e) { + print('Error showing notification: $e'); + } + } - var scheduledTime = tz.TZDateTime.from(date, tz.local); - flutterLocalNotificationsPlugin.zonedSchedule( - id, - title, - body, - scheduledTime, - notificationDetails, - androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - payload: imagePath, + Future _getLocalImagePath(String icon) async { + return await WeatherController().getLocalImagePath(icon); + } + + Future _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); } } diff --git a/lib/app/utils/show_snack_bar.dart b/lib/app/utils/show_snack_bar.dart old mode 100644 new mode 100755 index 612bfcb..4ccf103 --- a/lib/app/utils/show_snack_bar.dart +++ b/lib/app/utils/show_snack_bar.dart @@ -1,20 +1,16 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -final globalKey = GlobalKey(); +final GlobalKey globalKey = + GlobalKey(); -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, ), ); diff --git a/lib/main.dart b/lib/main.dart old mode 100644 new mode 100755 index 93a9adc..45d365e --- a/lib/main.dart +++ b/lib/main.dart @@ -86,24 +86,24 @@ void callbackDispatcher() { void main() async { WidgetsFlutterBinding.ensureInitialized(); - await _initializeApp(); + await initializeApp(); runApp(const MyApp()); } -Future _initializeApp() async { - _setupConnectivityListener(); - await _initializeTimeZone(); - await _initializeIsar(); - await _initializeNotifications(); +Future 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 _initializeTimeZone() async { +Future initializeTimeZone() async { final timeZoneName = await FlutterTimezone.getLocalTimezone(); tz.initializeTimeZones(); tz.setLocalLocation(tz.getLocation(timeZoneName)); } -Future _initializeIsar() async { +Future initializeIsar() async { isar = await Isar.open([ SettingsSchema, MainWeatherCacheSchema, @@ -140,7 +140,7 @@ Future _initializeIsar() async { } } -Future _initializeNotifications() async { +Future initializeNotifications() async { const initializationSettings = InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: DarwinInitializationSettings(), @@ -149,7 +149,7 @@ Future _initializeNotifications() async { await flutterLocalNotificationsPlugin.initialize(initializationSettings); } -Future _setOptimalDisplayMode() async { +Future setOptimalDisplayMode() async { final supported = await FlutterDisplayMode.supported; final active = await FlutterDisplayMode.active; final sameResolution = diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart old mode 100644 new mode 100755 index a74c586..17882f4 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -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 states) { - return const TextStyle(fontSize: 14); - }), - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - ), - indicatorColor: Colors.black, + color: color, + 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: - colorScheme - ?.copyWith( - brightness: Brightness.dark, - surface: baseDark.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, - ), + 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, + surface: baseTheme.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( - color: color, - surfaceTintColor: - color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - shadowColor: Colors.transparent, - ), - bottomSheetTheme: baseDark.bottomSheetTheme.copyWith( - backgroundColor: color, - surfaceTintColor: - color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, - ), - navigationRailTheme: baseDark.navigationRailTheme.copyWith( + cardTheme: _buildCardTheme(color, harmonizedColorScheme), + bottomSheetTheme: _buildBottomSheetTheme(color, harmonizedColorScheme), + navigationRailTheme: baseTheme.navigationRailTheme.copyWith( backgroundColor: color, ), - navigationBarTheme: baseDark.navigationBarTheme.copyWith( - backgroundColor: color, - surfaceTintColor: - color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, - ), - inputDecorationTheme: baseDark.inputDecorationTheme.copyWith( - labelStyle: WidgetStateTextStyle.resolveWith((Set states) { - return const TextStyle(fontSize: 14); - }), - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, + 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, + ); +} + +BottomSheetThemeData _buildBottomSheetTheme( + Color? color, + ColorScheme? colorScheme, +) { + return BottomSheetThemeData( + backgroundColor: color, + surfaceTintColor: + color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, + ); +} + +NavigationBarThemeData _buildNavigationBarTheme( + Color? color, + ColorScheme? colorScheme, +) { + return NavigationBarThemeData( + backgroundColor: color, + surfaceTintColor: + color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, + ); +} + +InputDecorationTheme _buildInputDecorationTheme() { + return InputDecorationTheme( + labelStyle: WidgetStateTextStyle.resolveWith((Set states) { + return const TextStyle(fontSize: 14); + }), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ); +} diff --git a/lib/theme/theme_controller.dart b/lib/theme/theme_controller.dart old mode 100644 new mode 100755 index a7b1757..86d1c77 --- a/lib/theme/theme_controller.dart +++ b/lib/theme/theme_controller.dart @@ -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)); + } } diff --git a/lib/translation/bn_in.dart b/lib/translation/bn_in.dart old mode 100644 new mode 100755 diff --git a/lib/translation/cs_cz.dart b/lib/translation/cs_cz.dart old mode 100644 new mode 100755 diff --git a/lib/translation/da_dk.dart b/lib/translation/da_dk.dart old mode 100644 new mode 100755 diff --git a/lib/translation/de_de.dart b/lib/translation/de_de.dart old mode 100644 new mode 100755 diff --git a/lib/translation/en_us.dart b/lib/translation/en_us.dart old mode 100644 new mode 100755 diff --git a/lib/translation/es_es.dart b/lib/translation/es_es.dart old mode 100644 new mode 100755 diff --git a/lib/translation/fa_ir.dart b/lib/translation/fa_ir.dart old mode 100644 new mode 100755 diff --git a/lib/translation/fr_fr.dart b/lib/translation/fr_fr.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ga_ie.dart b/lib/translation/ga_ie.dart old mode 100644 new mode 100755 diff --git a/lib/translation/hi_in.dart b/lib/translation/hi_in.dart old mode 100644 new mode 100755 diff --git a/lib/translation/hu_hu.dart b/lib/translation/hu_hu.dart old mode 100644 new mode 100755 diff --git a/lib/translation/it_it.dart b/lib/translation/it_it.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ka_ge.dart b/lib/translation/ka_ge.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ko_kr.dart b/lib/translation/ko_kr.dart old mode 100644 new mode 100755 diff --git a/lib/translation/nl_nl.dart b/lib/translation/nl_nl.dart old mode 100644 new mode 100755 diff --git a/lib/translation/pl_pl.dart b/lib/translation/pl_pl.dart old mode 100644 new mode 100755 diff --git a/lib/translation/pt_br.dart b/lib/translation/pt_br.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ro_ro.dart b/lib/translation/ro_ro.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ru_ru.dart b/lib/translation/ru_ru.dart old mode 100644 new mode 100755 diff --git a/lib/translation/sk_sk.dart b/lib/translation/sk_sk.dart old mode 100644 new mode 100755 diff --git a/lib/translation/tr_tr.dart b/lib/translation/tr_tr.dart old mode 100644 new mode 100755 diff --git a/lib/translation/translation.dart b/lib/translation/translation.dart old mode 100644 new mode 100755 diff --git a/lib/translation/ur_pk.dart b/lib/translation/ur_pk.dart old mode 100644 new mode 100755 diff --git a/lib/translation/zh_ch.dart b/lib/translation/zh_ch.dart old mode 100644 new mode 100755 diff --git a/lib/translation/zh_tw.dart b/lib/translation/zh_tw.dart old mode 100644 new mode 100755 diff --git a/pubspec.lock b/pubspec.lock index ef9fe75..11afdad 100644 --- a/pubspec.lock +++ b/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" diff --git a/pubspec.yaml b/pubspec.yaml index 320a347..ed79c01 100644 --- a/pubspec.yaml +++ b/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