mirror of
https://github.com/PranshulGG/WeatherMaster.git
synced 2026-10-21 10:40:23 -05:00
view changelogs, fixed background updates, updated strings, expressive loader
This commit is contained in:
1 parent
dfda321aa6
commit
f0bf80bb67
25 files changed
+936
-353
No files matched your search
@@ -79,7 +79,6 @@ flutter {
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
implementation("androidx.glance:glance-appwidget:1.0.0-alpha05")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
|
||||
}
|
||||
@@ -571,5 +571,6 @@
|
||||
"insights_sub_49": "Very cold weather expected — possibly record lows.",
|
||||
"insights_sub_50": "Temperatures are within the usual range — no records expected.",
|
||||
"background_updates_not_working": "Background updates not working?",
|
||||
"background_updates_not_working_sub": "Some device vendors restrict apps in the background. Check dontkillmyapp.com for steps to fix this."
|
||||
"background_updates_not_working_sub": "Some device vendors restrict apps in the background. Check dontkillmyapp.com for steps to fix this.",
|
||||
"about_weathermaster_sub": "Changelog, Version, License, and More"
|
||||
}
|
||||
@@ -571,5 +571,6 @@
|
||||
"insights_sub_49": "Very cold weather expected — possibly record lows.",
|
||||
"insights_sub_50": "Temperatures are within the usual range — no records expected.",
|
||||
"background_updates_not_working": "Background updates not working?",
|
||||
"background_updates_not_working_sub": "Some device vendors restrict apps in the background. Check dontkillmyapp.com for steps to fix this."
|
||||
"background_updates_not_working_sub": "Some device vendors restrict apps in the background. Check dontkillmyapp.com for steps to fix this.",
|
||||
"about_weathermaster_sub": "Changelog, Version, License, and More"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/theme.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||
|
||||
List<LinearGradient> getGradients(
|
||||
bool isLight, bool isFroggyLayout, iscurrentDay) {
|
||||
@@ -16,8 +17,11 @@ List<LinearGradient> getGradients(
|
||||
? iscurrentDay
|
||||
? LinearGradient(
|
||||
colors: [
|
||||
Color(paletteWeather.secondary.get(30)),
|
||||
Color(paletteWeather.secondary.get(15))
|
||||
Color(CorePalette.of(
|
||||
const Color.fromARGB(255, 31, 25, 0).toARGB32())
|
||||
.secondary
|
||||
.get(30)),
|
||||
Color(paletteWeather.secondary.get(18))
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
@@ -32,8 +36,11 @@ List<LinearGradient> getGradients(
|
||||
stops: [0, 0.5])
|
||||
: LinearGradient(
|
||||
colors: [
|
||||
Color(paletteWeather.secondary.get(25)),
|
||||
Color(paletteWeather.secondary.get(20))
|
||||
Color(CorePalette.of(
|
||||
const Color.fromARGB(255, 31, 25, 0).toARGB32())
|
||||
.secondary
|
||||
.get(30)),
|
||||
Color(paletteWeather.secondary.get(18))
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
@@ -74,7 +81,10 @@ List<LinearGradient> getGradients(
|
||||
stops: [0, 0.5],
|
||||
)
|
||||
: LinearGradient(
|
||||
colors: [const Color.fromARGB(255, 4, 36, 83), Color(0xFF004a76)],
|
||||
colors: [
|
||||
const Color.fromARGB(255, 5, 47, 110),
|
||||
Color.fromARGB(255, 0, 51, 92)
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: [0, 0.5]),
|
||||
@@ -180,8 +190,8 @@ List<LinearGradient> getGradientsScrolled(
|
||||
? iscurrentDay
|
||||
? LinearGradient(
|
||||
colors: [
|
||||
Color(paletteWeather.secondary.get(15)),
|
||||
Color(paletteWeather.secondary.get(15))
|
||||
Color(paletteWeather.secondary.get(18)),
|
||||
Color(paletteWeather.secondary.get(18))
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
@@ -196,8 +206,8 @@ List<LinearGradient> getGradientsScrolled(
|
||||
stops: [0, 0.5])
|
||||
: LinearGradient(
|
||||
colors: [
|
||||
Color(paletteWeather.secondary.get(20)),
|
||||
Color(paletteWeather.secondary.get(20))
|
||||
Color(paletteWeather.secondary.get(18)),
|
||||
Color(paletteWeather.secondary.get(18))
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
@@ -239,7 +249,10 @@ List<LinearGradient> getGradientsScrolled(
|
||||
stops: [0, 0.5],
|
||||
)
|
||||
: LinearGradient(
|
||||
colors: [Color(0xFF004a76), Color(0xFF004a76)],
|
||||
colors: [
|
||||
Color.fromARGB(255, 0, 51, 92),
|
||||
Color.fromARGB(255, 0, 51, 92)
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: [0, 0.5]),
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'dart:ui' as ui;
|
||||
import 'services/widget_service.dart';
|
||||
import 'widget_background.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
|
||||
final CorePalette paletteStartScreen = CorePalette.of(
|
||||
const Color.fromARGB(255, 255, 196, 0).toARGB32(),
|
||||
@@ -32,6 +33,8 @@ final CorePalette paletteStartScreen = CorePalette.of(
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> workerUpdateWidget() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
await updateHomeWidget(null, updatedFromHome: false);
|
||||
}
|
||||
|
||||
@@ -68,6 +71,7 @@ const easySupportedLocales = [
|
||||
Locale('zh', 'CN'),
|
||||
Locale('zh', 'TW'),
|
||||
Locale('ca', 'ES'),
|
||||
Locale('bg', 'BG')
|
||||
];
|
||||
|
||||
final flutterSupportedLocales =
|
||||
@@ -94,6 +98,8 @@ void main() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
|
||||
await Hive.openBox('changelogs');
|
||||
|
||||
// widget------
|
||||
|
||||
bool useBackgroundUpdates = prefs.getBool("useBackgroundUpdates") ?? false;
|
||||
|
||||
+275
-6
@@ -5,13 +5,18 @@ import '../utils/open_links.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:expressive_loading_indicator/expressive_loading_indicator.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorTheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
@@ -31,10 +36,93 @@ class AboutPage extends StatelessWidget {
|
||||
Text(
|
||||
"WeatherMaster",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 24),
|
||||
),
|
||||
CheckUpdateButton()
|
||||
Row(
|
||||
spacing: 2,
|
||||
children: [
|
||||
CheckUpdateButton(),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(28)),
|
||||
),
|
||||
builder: (context) {
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: 0.4,
|
||||
minChildSize: 0.4,
|
||||
maxChildSize: 0.9,
|
||||
snap: true,
|
||||
builder: (context, scrollController) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 65,
|
||||
elevation: 1,
|
||||
automaticallyImplyLeading: false,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(28),
|
||||
topRight: Radius.circular(28))),
|
||||
title: Text("Changelog"),
|
||||
actions: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(Icons.close)),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colorTheme.surfaceContainerLow,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10,
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ChangelogSheet(
|
||||
scrollController: scrollController),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(3),
|
||||
topRight: Radius.circular(50),
|
||||
bottomLeft: Radius.circular(3),
|
||||
bottomRight: Radius.circular(50)),
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(Size(0, 30)),
|
||||
iconAlignment: IconAlignment.end,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: Text("Whats new",
|
||||
style: TextStyle(fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
)
|
||||
]),
|
||||
Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
@@ -42,8 +130,8 @@ class AboutPage extends StatelessWidget {
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(50)),
|
||||
child: Image.asset(
|
||||
"assets/weather-icons/new_icon.png",
|
||||
width: 80,
|
||||
height: 80,
|
||||
width: 76,
|
||||
height: 76,
|
||||
))
|
||||
],
|
||||
),
|
||||
@@ -420,12 +508,21 @@ class _CheckUpdateButtonState extends State<CheckUpdateButton> {
|
||||
Symbols.refresh,
|
||||
weight: 700,
|
||||
),
|
||||
label: Text(isChecking ? 'checking'.tr() : currentVersion,
|
||||
style: TextStyle(fontWeight: FontWeight.w700)),
|
||||
label:
|
||||
Text(currentVersion, style: TextStyle(fontWeight: FontWeight.w700)),
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(50),
|
||||
topRight: Radius.circular(3),
|
||||
bottomLeft: Radius.circular(50),
|
||||
bottomRight: Radius.circular(3)),
|
||||
),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(Size(0, 30)),
|
||||
iconAlignment: IconAlignment.end,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
@@ -433,3 +530,175 @@ class _CheckUpdateButtonState extends State<CheckUpdateButton> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChangelogService {
|
||||
final String githubRepo = "PranshulGG/WeatherMaster";
|
||||
final Box _box = Hive.box('changelogs');
|
||||
|
||||
Future<List<Map<String, dynamic>>> getChangelogs() async {
|
||||
final now = DateTime.now();
|
||||
final lastFetch = _box.get('lastFetch') as DateTime?;
|
||||
|
||||
if (lastFetch != null &&
|
||||
now.difference(lastFetch) < const Duration(hours: 48)) {
|
||||
final cachedData =
|
||||
List<Map<String, dynamic>>.from(_box.get('data', defaultValue: []));
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
final url = Uri.parse("https://api.github.com/repos/$githubRepo/releases");
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Failed to fetch releases");
|
||||
}
|
||||
|
||||
final releases = jsonDecode(response.body) as List<dynamic>;
|
||||
|
||||
final parsed = releases
|
||||
.where((r) => r['prerelease'] == false)
|
||||
.take(10)
|
||||
.map((r) => {
|
||||
"tag": r["tag_name"],
|
||||
"name": r["name"] ?? "",
|
||||
"body": r["body"] ?? "",
|
||||
"published_at": r["published_at"],
|
||||
})
|
||||
.toList();
|
||||
|
||||
await _box.put('data', parsed);
|
||||
await _box.put('lastFetch', now);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
class ChangelogSheet extends StatefulWidget {
|
||||
final ScrollController scrollController;
|
||||
const ChangelogSheet({super.key, required this.scrollController});
|
||||
|
||||
@override
|
||||
State<ChangelogSheet> createState() => _ChangelogSheetState();
|
||||
}
|
||||
|
||||
class _ChangelogSheetState extends State<ChangelogSheet> {
|
||||
final service = ChangelogService();
|
||||
late Future<List<Map<String, dynamic>>> _future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = service.getChangelogs();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(
|
||||
child: ExpressiveLoadingIndicator(
|
||||
activeSize: 38,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
));
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text("Error loading changelog"));
|
||||
}
|
||||
|
||||
final releases = snapshot.data as List<Map<String, dynamic>>;
|
||||
if (releases.isEmpty) {
|
||||
return const Center(child: Text("No changelogs available"));
|
||||
}
|
||||
|
||||
final buffer = StringBuffer("");
|
||||
for (final release in releases) {
|
||||
buffer.writeln("# ${release['tag']}");
|
||||
buffer.writeln(release['body']);
|
||||
buffer.writeln("\n---\n");
|
||||
}
|
||||
|
||||
final colorTheme = Theme.of(context).colorScheme;
|
||||
|
||||
return ListView.builder(
|
||||
controller: widget.scrollController,
|
||||
itemCount: releases.length,
|
||||
itemBuilder: (context, index) {
|
||||
final release = releases[index];
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: colorTheme.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 6),
|
||||
Row(spacing: 2.7, children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10, right: 10, bottom: 3, top: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: colorTheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Text(
|
||||
release['tag'],
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (index == 0)
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10, right: 10, bottom: 3, top: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: colorTheme.tertiary,
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(
|
||||
"Latest",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onTertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
SizedBox(height: 12),
|
||||
MarkdownBody(
|
||||
data: release['body'],
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context))
|
||||
.copyWith(
|
||||
blockquote: TextStyle(
|
||||
color: colorTheme.onSurfaceVariant,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
blockquoteDecoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: colorTheme.outline, width: 4)),
|
||||
),
|
||||
),
|
||||
onTapLink: (text, href, title) {
|
||||
if (href != null) openLink(href);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class _BackgroundUpdatesPageState extends State<BackgroundUpdatesPage> {
|
||||
}
|
||||
|
||||
final optionsInterval = {
|
||||
30: "30 ${"minutes".tr()}",
|
||||
15: "30 ${"minutes".tr()}",
|
||||
60: "1 ${"hours".tr()}",
|
||||
90: "1.5 ${"hours".tr()}",
|
||||
120: "2 ${"hours".tr()}",
|
||||
@@ -151,8 +151,7 @@ class _BackgroundUpdatesPageState extends State<BackgroundUpdatesPage> {
|
||||
title: BatteryOptWidget(), // 0%
|
||||
),
|
||||
SettingSwitchTile(
|
||||
title: Text(
|
||||
"Show a snack message when a new version is released"),
|
||||
title: Text("new_version_snack_message".tr()),
|
||||
toggled: showNewVerNotification,
|
||||
onChanged: (value) {
|
||||
PreferencesHelper.setBool(
|
||||
|
||||
@@ -958,7 +958,6 @@ class ConditionsWidgetsForecast extends StatelessWidget {
|
||||
crossAxisCount: 2,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(top: 8.5),
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
children: [
|
||||
|
||||
@@ -95,47 +95,59 @@ class _ExtendWidgetState extends State<ExtendWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
final colorTheme = Theme.of(context).colorScheme;
|
||||
|
||||
if (_showLoader) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ExpressiveLoadingIndicator(
|
||||
color: colorTheme.primary,
|
||||
activeSize: 48,
|
||||
),
|
||||
));
|
||||
}
|
||||
// if (_showLoader) {
|
||||
// return Scaffold(
|
||||
// body: Center(
|
||||
// child: ExpressiveLoadingIndicator(
|
||||
// color: colorTheme.primary,
|
||||
// activeSize: 48,
|
||||
// ),
|
||||
// ));
|
||||
// }
|
||||
return Scaffold(
|
||||
body: CustomScrollView(slivers: [
|
||||
SliverAppBar.large(
|
||||
titleSpacing: 0,
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Row(
|
||||
spacing: 5,
|
||||
children: [Icon(iconData), Text(extendedTitle)],
|
||||
),
|
||||
expandedTitleScale: 1.3,
|
||||
titlePadding: EdgeInsets.all(16),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
expandedHeight: 100,
|
||||
scrolledUnderElevation: 1,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.close,
|
||||
weight: 600,
|
||||
)),
|
||||
SizedBox(width: 5)
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: child,
|
||||
)
|
||||
]));
|
||||
body: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
switchInCurve: Curves.easeIn,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
child: _showLoader
|
||||
? Center(
|
||||
key: const ValueKey('loader'),
|
||||
child: ExpressiveLoadingIndicator(
|
||||
color: colorTheme.primary,
|
||||
activeSize: 48,
|
||||
),
|
||||
)
|
||||
: CustomScrollView(key: const ValueKey('content'), slivers: [
|
||||
SliverAppBar.large(
|
||||
titleSpacing: 0,
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Row(
|
||||
spacing: 5,
|
||||
children: [Icon(iconData), Text(extendedTitle)],
|
||||
),
|
||||
expandedTitleScale: 1.3,
|
||||
titlePadding: EdgeInsets.all(16),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
expandedHeight: 100,
|
||||
scrolledUnderElevation: 1,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.close,
|
||||
weight: 600,
|
||||
)),
|
||||
SizedBox(width: 5)
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: child,
|
||||
)
|
||||
])));
|
||||
}
|
||||
|
||||
Widget buildHumidityExtended() {
|
||||
|
||||
+121
-27
@@ -123,7 +123,7 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
bool showAlertsPref = PreferencesHelper.getBool("showAlerts") ?? true;
|
||||
bool widgetsUpdated = false;
|
||||
bool? iscurrentDay = false;
|
||||
|
||||
bool? _cachedIsShowFrog;
|
||||
bool? lastIsDay;
|
||||
|
||||
final WeatherFroggyManager _weatherManager = WeatherFroggyManager();
|
||||
@@ -132,6 +132,9 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
bool _isLoadingFroggy = true;
|
||||
bool layoutCreated = false;
|
||||
|
||||
bool isFirstAppBuild = true;
|
||||
bool onLoadForceCall = false;
|
||||
|
||||
double? lat;
|
||||
double? lon;
|
||||
|
||||
@@ -251,20 +254,30 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
isHomeLocation = false;
|
||||
}
|
||||
|
||||
final hasInternet = await hasRealInternet();
|
||||
final hasInternet = await hasNetworkConnection();
|
||||
|
||||
if (!hasInternet) {
|
||||
_isAppFullyLoaded = true;
|
||||
SnackUtil.showSnackBar(
|
||||
context: context, message: "network_unavailable".tr());
|
||||
if (!_istriggeredFromLocations) {
|
||||
_isAppFullyLoaded = true;
|
||||
} else {}
|
||||
|
||||
if (isFirstAppBuild) {
|
||||
SnackUtil.showSnackBar(
|
||||
context: context, message: "network_unavailable".tr());
|
||||
isFirstAppBuild = false;
|
||||
}
|
||||
} else if (lastUpdated != null) {
|
||||
final lastUpdateTime = DateTime.tryParse(lastUpdated);
|
||||
final now = DateTime.now();
|
||||
if (lastUpdateTime != null &&
|
||||
now.difference(lastUpdateTime).inMinutes < 450) {
|
||||
_isAppFullyLoaded = true;
|
||||
if (!_istriggeredFromLocations) {
|
||||
_isAppFullyLoaded = true;
|
||||
}
|
||||
isFirstAppBuild = false;
|
||||
} else {
|
||||
checkAndUpdateHomeLocation();
|
||||
isFirstAppBuild = false;
|
||||
}
|
||||
}
|
||||
return json.decode(cached);
|
||||
@@ -306,7 +319,7 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
}
|
||||
|
||||
Future<void> _refreshWeatherData() async {
|
||||
final hasInternet = await hasRealInternet();
|
||||
final hasInternet = await hasNetworkConnection();
|
||||
|
||||
if (isViewLocation) {
|
||||
if (!mounted) return;
|
||||
@@ -353,8 +366,24 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
}
|
||||
|
||||
final weatherService = WeatherService();
|
||||
final result = await weatherService.fetchWeather(lat!, lon!,
|
||||
locationName: cacheKey, context: context);
|
||||
Map<String, dynamic>? result;
|
||||
try {
|
||||
result = await weatherService.fetchWeather(lat!, lon!,
|
||||
locationName: cacheKey, context: context);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isAppFullyLoaded = true;
|
||||
});
|
||||
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('data_fetch_error'.tr()),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
return;
|
||||
@@ -460,9 +489,27 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
await saveLocation(saved);
|
||||
|
||||
final weatherService = WeatherService();
|
||||
await weatherService.fetchWeather(currentLat, currentLon,
|
||||
locationName: currentCacheKey, context: context);
|
||||
try {
|
||||
await weatherService.fetchWeather(
|
||||
currentLat,
|
||||
currentLon,
|
||||
locationName: currentCacheKey,
|
||||
context: context,
|
||||
);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isAppFullyLoaded = true;
|
||||
});
|
||||
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('data_fetch_error'.tr()),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isAppFullyLoaded = false;
|
||||
_istriggeredFromLocations = true;
|
||||
@@ -657,14 +704,14 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
opacity: val,
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: colorTheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: ExpressiveLoadingIndicator(
|
||||
color: colorTheme.primary,
|
||||
activeSize: 48,
|
||||
activeSize: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -748,7 +795,13 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
// cloudy
|
||||
isLight
|
||||
? paletteWeather.secondary.get(98)
|
||||
: paletteWeather.secondary.get(useDarkerBackground ? 2 : 8),
|
||||
: paletteWeather.secondary.get(useDarkerBackground
|
||||
? 2
|
||||
: !isShowFrog
|
||||
? iscurrentDay!
|
||||
? 8
|
||||
: 3
|
||||
: 8),
|
||||
|
||||
// overcast
|
||||
isLight
|
||||
@@ -845,10 +898,24 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
daily['precipitation_probability_max'];
|
||||
final List<dynamic> dailyWeatherCodes = daily['weather_code'];
|
||||
|
||||
void maybeUpdateWeatherAnimation(Map<String, dynamic> current) {
|
||||
void maybeUpdateWeatherAnimation(Map<String, dynamic> current,
|
||||
{isForce = false}) {
|
||||
final int weatherCode = current['weather_code'];
|
||||
final int isDay = current['is_day'];
|
||||
|
||||
if (isForce) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
weatherAnimationWidget =
|
||||
WeatherConditionAnimationMapper.build(
|
||||
weatherCode: weatherCode,
|
||||
isDay: isDay,
|
||||
context: context,
|
||||
setFullDisplay: !isShowFrog);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (_cachedWeatherCode != weatherCode || _cachedIsDay != isDay) {
|
||||
weatherAnimationWidget = WeatherConditionAnimationMapper.build(
|
||||
weatherCode: weatherCode,
|
||||
@@ -858,9 +925,22 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
|
||||
_cachedWeatherCode = weatherCode;
|
||||
_cachedIsDay = isDay;
|
||||
_cachedIsShowFrog = isShowFrog;
|
||||
onLoadForceCall = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (onLoadForceCall) {
|
||||
if (_cachedIsShowFrog != isShowFrog) {
|
||||
maybeUpdateWeatherAnimation(current, isForce: true);
|
||||
print('called');
|
||||
|
||||
_cachedIsShowFrog = isShowFrog;
|
||||
}
|
||||
} else {
|
||||
onLoadForceCall = true;
|
||||
}
|
||||
|
||||
final Map<String, (DateTime, DateTime)> daylightMap = {
|
||||
for (int i = 0; i < dailyDates.length; i++)
|
||||
dailyDates[i]: (
|
||||
@@ -1340,18 +1420,32 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
await Future.delayed(Duration(milliseconds: 300));
|
||||
if (result != null) {
|
||||
if (result['viewLocaton'] == true) {
|
||||
Map<String, dynamic>? result;
|
||||
|
||||
final weatherService = WeatherService();
|
||||
final result =
|
||||
await weatherService.fetchWeather(
|
||||
PreferencesHelper.getJson(
|
||||
'selectedViewLocation')?['lat'],
|
||||
PreferencesHelper.getJson(
|
||||
'selectedViewLocation')?['lon'],
|
||||
locationName:
|
||||
"${PreferencesHelper.getJson('selectedViewLocation')?['city']}, ${PreferencesHelper.getJson('selectedViewLocation')?['country']}",
|
||||
context: context,
|
||||
isOnlyView: true,
|
||||
);
|
||||
try {
|
||||
result = await weatherService.fetchWeather(
|
||||
lat!, lon!,
|
||||
locationName: cacheKey,
|
||||
context: context);
|
||||
} catch (e) {
|
||||
result = null;
|
||||
setState(() {
|
||||
_isAppFullyLoaded = true;
|
||||
});
|
||||
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content:
|
||||
Text('data_fetch_error'.tr()),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
} else {
|
||||
setState(() {
|
||||
@@ -1429,7 +1523,7 @@ class _WeatherHomeState extends State<WeatherHome> {
|
||||
currentLastUpdated: formattedTime,
|
||||
),
|
||||
WeatherFrogIconWidget(iconUrl: _iconUrlFroggy),
|
||||
const SizedBox(height: 13.8),
|
||||
const SizedBox(height: 14),
|
||||
SizedBox(
|
||||
width: isFoldableLayout(context) ? 500 : null,
|
||||
height: null,
|
||||
|
||||
@@ -161,7 +161,7 @@ class TranslationProgressService {
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse(
|
||||
'https://api.crowdin.com/api/v2/projects/$projectId/languages/progress?limit=31&offset=0'),
|
||||
'https://api.crowdin.com/api/v2/projects/$projectId/languages/progress?limit=32&offset=0'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $apiToken',
|
||||
'Content-Type': 'application/json',
|
||||
@@ -293,6 +293,9 @@ class _LanguageTileState extends State<LanguageTile> {
|
||||
title: Text(
|
||||
widget.languageName['native']!,
|
||||
style: TextStyle(
|
||||
fontFamily: widget.languageName['native'] == "English"
|
||||
? 'FlexFontEn'
|
||||
: 'DefaultFont',
|
||||
color: _isSelected
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
@@ -419,6 +422,9 @@ Map<String, String> getLanguageNames(Locale locale) {
|
||||
if (lang == 'ca' && country == 'ES') {
|
||||
return {'native': 'Català', 'english': 'Catalan'};
|
||||
}
|
||||
if (lang == 'bg' && country == 'BG') {
|
||||
return {'native': 'Български', 'english': 'Bulgarian'};
|
||||
}
|
||||
if (lang == 'de' && country == 'DE') {
|
||||
return {'native': 'Deutsch', 'english': 'German'};
|
||||
}
|
||||
@@ -482,10 +488,10 @@ Map<String, String> getLanguageNames(Locale locale) {
|
||||
if (lang == 'sl' && country == 'SI') {
|
||||
return {'native': 'Slovenščina', 'english': 'Slovenian'};
|
||||
}
|
||||
if (lang == 'sr' && country == 'CS') {
|
||||
if (lang == 'sr' && country == 'SP') {
|
||||
return {'native': 'Српски (Ћирилица)', 'english': 'Serbian (Cyrillic)'};
|
||||
}
|
||||
if (lang == 'sr' && country == 'SP') {
|
||||
if (lang == 'sr' && country == 'CS') {
|
||||
return {'native': 'Српски', 'english': 'Serbian'};
|
||||
}
|
||||
if (lang == 'sv' && country == 'SE') {
|
||||
@@ -554,7 +560,8 @@ String getLanguageCodeCrodwin(Locale locale) {
|
||||
'tr', // Turkish
|
||||
'uk', // Ukrainian
|
||||
'vi', // Vietnamese
|
||||
'ca'
|
||||
'ca',
|
||||
'bg'
|
||||
].contains(lang)) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../utils/condition_label_map.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
import '../widgets/dialog.dart';
|
||||
import 'package:expressive_loading_indicator/expressive_loading_indicator.dart';
|
||||
|
||||
class LocationsScreen extends StatefulWidget {
|
||||
const LocationsScreen({super.key});
|
||||
@@ -28,12 +29,21 @@ final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
|
||||
class _LocationsScreenState extends State<LocationsScreen> {
|
||||
List<SavedLocation> savedLocations = [];
|
||||
bool _isFirstBuild = true;
|
||||
bool _showLoader = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
loadSavedLocations();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_showLoader = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> loadSavedLocations() async {
|
||||
@@ -159,9 +169,13 @@ class _LocationsScreenState extends State<LocationsScreen> {
|
||||
),
|
||||
);
|
||||
},
|
||||
child: isEditing
|
||||
? buildReorderableListView(key: ValueKey('reorderable'))
|
||||
: buildDismissibleListView(key: ValueKey('dismissible')),
|
||||
child: _showLoader
|
||||
? Center(
|
||||
key: const ValueKey('loader'),
|
||||
)
|
||||
: isEditing
|
||||
? buildReorderableListView(key: ValueKey('reorderable'))
|
||||
: buildDismissibleListView(key: ValueKey('dismissible')),
|
||||
),
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.centerFloat,
|
||||
@@ -903,7 +917,7 @@ class _FadeInListItemState extends State<FadeInListItem> {
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedOpacity(
|
||||
opacity: opacity,
|
||||
duration: const Duration(milliseconds: 700),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: widget.child,
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/saved_location.dart';
|
||||
import '../services/fetch_data.dart';
|
||||
import '../utils/condition_label_map.dart';
|
||||
import 'package:expressive_loading_indicator/expressive_loading_indicator.dart';
|
||||
|
||||
enum GeoProvider { nominatim, geonames, openMeteo }
|
||||
@@ -75,7 +74,7 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
|
||||
Future<void> loadSavedProvider() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final index = prefs.getInt('geo_provider_index') ?? 0;
|
||||
final index = prefs.getInt('geo_provider_index') ?? 1;
|
||||
setState(() {
|
||||
selectedProvider = GeoProvider.values[index];
|
||||
});
|
||||
@@ -87,6 +86,7 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
}
|
||||
|
||||
Future<void> searchLocation(String input) async {
|
||||
input = input.trim();
|
||||
if (input.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
@@ -172,51 +172,103 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
|
||||
void openProviderDialog() async {
|
||||
void openProviderSheet() async {
|
||||
GeoProvider tempProvider = selectedProvider;
|
||||
final colorTheme = Theme.of(context).colorScheme;
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
final confirmed = await showModalBottomSheet<bool>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: colorTheme.surfaceContainerLow,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
),
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
title: Text("search_provider".tr()),
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 0, right: 0, top: 16, bottom: 5),
|
||||
content: StatefulBuilder(
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 10),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: GeoProvider.values.map((provider) {
|
||||
return RadioListTile<GeoProvider>(
|
||||
dense: true,
|
||||
title: Text(
|
||||
providerLabels[provider]!,
|
||||
style: TextStyle(fontSize: 15),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Text(
|
||||
"search_provider".tr(),
|
||||
style: TextStyle(fontSize: 22),
|
||||
),
|
||||
value: provider,
|
||||
groupValue: tempProvider,
|
||||
onChanged: (val) => setState(() => tempProvider = val!),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
SizedBox(height: 15),
|
||||
...GeoProvider.values.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final provider = entry.value;
|
||||
final isFirst = index == 0;
|
||||
final isLast = index == GeoProvider.values.length - 1;
|
||||
final isSelected = tempProvider == provider;
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 18, right: 18, bottom: 2),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: isSelected
|
||||
? BorderRadiusGeometry.circular(50)
|
||||
: BorderRadius.only(
|
||||
topRight:
|
||||
Radius.circular(isFirst ? 18 : 2.6),
|
||||
topLeft:
|
||||
Radius.circular(isFirst ? 18 : 2.6),
|
||||
bottomLeft:
|
||||
Radius.circular(isLast ? 18 : 2.6),
|
||||
bottomRight:
|
||||
Radius.circular(isLast ? 18 : 2.6))),
|
||||
tileColor: colorTheme.surfaceContainerLowest,
|
||||
dense: true,
|
||||
selected: isSelected,
|
||||
selectedTileColor: colorTheme.primaryContainer,
|
||||
title: Text(
|
||||
providerLabels[provider]!,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: isSelected
|
||||
? colorTheme.onPrimaryContainer
|
||||
: null,
|
||||
),
|
||||
),
|
||||
onTap: () => setState(() => tempProvider = provider),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
SizedBox(height: 13),
|
||||
// Divider(),
|
||||
// SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(
|
||||
'cancel'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600),
|
||||
)),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(
|
||||
'save'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600),
|
||||
)),
|
||||
],
|
||||
))
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".tr(),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"save".tr(),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -251,7 +303,7 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
title: TextField(
|
||||
style: TextStyle(fontSize: 16, color: colorTheme.onSurface),
|
||||
onChanged: (value) => query = value,
|
||||
onSubmitted: searchLocation,
|
||||
onSubmitted: (value) => searchLocation(value.trim()),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
@@ -339,7 +391,6 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
color: colorTheme.primary,
|
||||
)),
|
||||
);
|
||||
|
||||
final location = results[index];
|
||||
final lat = double.tryParse(
|
||||
location['lat'] ?? '') ??
|
||||
@@ -354,17 +405,33 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
city: location['city'] ?? '',
|
||||
country: location['country'] ?? '',
|
||||
);
|
||||
|
||||
saveLocation(saved);
|
||||
final cacheKey =
|
||||
"${saved.city}_${saved.country}"
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '_');
|
||||
final weatherService = WeatherService();
|
||||
await weatherService.fetchWeather(
|
||||
lat, lon,
|
||||
locationName: cacheKey,
|
||||
context: context);
|
||||
try {
|
||||
await weatherService.fetchWeather(
|
||||
lat, lon,
|
||||
locationName: cacheKey,
|
||||
context: context);
|
||||
} catch (e) {
|
||||
Navigator.pop(context);
|
||||
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content:
|
||||
Text('data_fetch_error'.tr()),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
saveLocation(saved);
|
||||
|
||||
final prefs =
|
||||
await SharedPreferences.getInstance();
|
||||
@@ -455,13 +522,30 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
)),
|
||||
);
|
||||
|
||||
saveLocation(saved);
|
||||
final cacheKey = "${saved.city}_${saved.country}"
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '_');
|
||||
final weatherService = WeatherService();
|
||||
await weatherService.fetchWeather(lat, lon,
|
||||
locationName: cacheKey, context: context);
|
||||
try {
|
||||
await weatherService.fetchWeather(lat, lon,
|
||||
locationName: cacheKey, context: context);
|
||||
} catch (e) {
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('data_fetch_error'.tr()),
|
||||
duration: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
saveLocation(saved);
|
||||
|
||||
// final weatherService = WeatherService();
|
||||
// await weatherService.fetchWeather(lat, lon,
|
||||
// locationName: cacheKey, context: context);
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString =
|
||||
@@ -541,7 +625,7 @@ class _SearchLocationsScreenState extends State<SearchLocationsScreen> {
|
||||
],
|
||||
),
|
||||
FloatingActionButton(
|
||||
onPressed: openProviderDialog,
|
||||
onPressed: openProviderSheet,
|
||||
child: Icon(Icons.tune),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -214,7 +214,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
isLight ? Color(0xffe6deff) : Color(0xff493e76),
|
||||
isLight ? Color(0xff493e76) : Color(0xffe6deff)),
|
||||
title: Text('${"about".tr()} WeatherMaster'),
|
||||
description: Text('learn_about_app'.tr()),
|
||||
description:
|
||||
Text('Changelog, Version, License, and More'),
|
||||
onTap: () {
|
||||
Navigator.of(
|
||||
context,
|
||||
|
||||
+109
-96
@@ -59,117 +59,130 @@ class WeatherService {
|
||||
'https://api.weatherapi.com/v1/astronomy.json?key=${dotenv.env['API_KEY_WEATHERAPI']!.toString()}&q=$lat,$lon');
|
||||
|
||||
// Prepare list of HTTP requests
|
||||
final requests = <Future<http.Response>>[
|
||||
http.get(uri),
|
||||
http.get(airQualityUri),
|
||||
http.get(astronomyUri),
|
||||
];
|
||||
try {
|
||||
final requests = <Future<http.Response>>[
|
||||
http.get(uri),
|
||||
http.get(airQualityUri),
|
||||
http.get(astronomyUri),
|
||||
];
|
||||
|
||||
final responses = await Future.wait(requests);
|
||||
final responses = await Future.wait(requests);
|
||||
|
||||
// Parse responses
|
||||
final weatherData = json.decode(responses[0].body) as Map<String, dynamic>;
|
||||
final airQualityData =
|
||||
json.decode(responses[1].body) as Map<String, dynamic>;
|
||||
final astronomyData =
|
||||
json.decode(responses[2].body) as Map<String, dynamic>;
|
||||
for (var response in responses) {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw Exception(
|
||||
'HTTP request failed with status: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
// Parse responses
|
||||
final weatherData =
|
||||
json.decode(responses[0].body) as Map<String, dynamic>;
|
||||
final airQualityData =
|
||||
json.decode(responses[1].body) as Map<String, dynamic>;
|
||||
final astronomyData =
|
||||
json.decode(responses[2].body) as Map<String, dynamic>;
|
||||
|
||||
// Check if we need fallback data for missing fields
|
||||
Map<String, dynamic> finalWeatherData = weatherData;
|
||||
if (selectedModel != "best_match" && _hasIncompleteData(weatherData)) {
|
||||
finalWeatherData = await _fetchWithFallback(
|
||||
lat, lon, timezone, weatherData, selectedModel);
|
||||
}
|
||||
|
||||
finalWeatherData['current'] = sanitizeCurrent(finalWeatherData['current']);
|
||||
finalWeatherData['hourly'] = sanitizeHourly(finalWeatherData['hourly']);
|
||||
finalWeatherData['daily'] = sanitizeDaily(finalWeatherData['daily']);
|
||||
|
||||
if (finalWeatherData['error'] == true ||
|
||||
airQualityData['error'] == true ||
|
||||
astronomyData['error'] == true) {
|
||||
final reason = finalWeatherData['reason'] ??
|
||||
airQualityData['reason'] ??
|
||||
astronomyData['reason'] ??
|
||||
'Unknown error';
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
duration: Duration(seconds: 10),
|
||||
content: Text("$reason. Please change your model"),
|
||||
action: SnackBarAction(
|
||||
label: 'Change model',
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const MeteoModelsPage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
// Check if we need fallback data for missing fields
|
||||
Map<String, dynamic> finalWeatherData = weatherData;
|
||||
if (selectedModel != "best_match" && _hasIncompleteData(weatherData)) {
|
||||
finalWeatherData = await _fetchWithFallback(
|
||||
lat, lon, timezone, weatherData, selectedModel);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
finalWeatherData['current'] =
|
||||
sanitizeCurrent(finalWeatherData['current']);
|
||||
finalWeatherData['hourly'] = sanitizeHourly(finalWeatherData['hourly']);
|
||||
finalWeatherData['daily'] = sanitizeDaily(finalWeatherData['daily']);
|
||||
|
||||
final combinedDataForView = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'astronomy': astronomyData,
|
||||
};
|
||||
if (finalWeatherData['error'] == true ||
|
||||
airQualityData['error'] == true ||
|
||||
astronomyData['error'] == true) {
|
||||
final reason = finalWeatherData['reason'] ??
|
||||
airQualityData['reason'] ??
|
||||
astronomyData['reason'] ??
|
||||
'Unknown error';
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
duration: Duration(seconds: 10),
|
||||
content: Text("$reason. Please change your model"),
|
||||
action: SnackBarAction(
|
||||
label: 'Change model',
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const MeteoModelsPage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final nowForView = DateTime.now().toIso8601String();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isOnlyView) {
|
||||
log("Only viewing data for $key. Not saving to cache.");
|
||||
return {
|
||||
'data': combinedDataForView,
|
||||
'last_updated': nowForView,
|
||||
'from_cache': false,
|
||||
final combinedDataForView = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'astronomy': astronomyData,
|
||||
};
|
||||
}
|
||||
|
||||
final existingJson = box.get(key);
|
||||
if (existingJson != null) {
|
||||
final cachedMap = json.decode(existingJson);
|
||||
final cachedData = cachedMap['data'];
|
||||
final lastUpdated = cachedMap['last_updated'];
|
||||
final nowForView = DateTime.now().toIso8601String();
|
||||
|
||||
if (json.encode(cachedData['current']) ==
|
||||
json.encode(finalWeatherData['current']) &&
|
||||
json.encode(cachedData['air_quality'] ?? {}) ==
|
||||
json.encode(airQualityData) &&
|
||||
json.encode(cachedData['astronomy'] ?? {}) ==
|
||||
json.encode(astronomyData)) {
|
||||
log("Hive: No update needed for $key");
|
||||
if (isOnlyView) {
|
||||
log("Only viewing data for $key. Not saving to cache.");
|
||||
return {
|
||||
'data': cachedData,
|
||||
'last_updated': lastUpdated,
|
||||
'from_cache': true,
|
||||
'data': combinedDataForView,
|
||||
'last_updated': nowForView,
|
||||
'from_cache': false,
|
||||
};
|
||||
}
|
||||
|
||||
final existingJson = box.get(key);
|
||||
if (existingJson != null) {
|
||||
final cachedMap = json.decode(existingJson);
|
||||
final cachedData = cachedMap['data'];
|
||||
final lastUpdated = cachedMap['last_updated'];
|
||||
|
||||
if (json.encode(cachedData['current']) ==
|
||||
json.encode(finalWeatherData['current']) &&
|
||||
json.encode(cachedData['air_quality'] ?? {}) ==
|
||||
json.encode(airQualityData) &&
|
||||
json.encode(cachedData['astronomy'] ?? {}) ==
|
||||
json.encode(astronomyData)) {
|
||||
log("Hive: No update needed for $key");
|
||||
return {
|
||||
'data': cachedData,
|
||||
'last_updated': lastUpdated,
|
||||
'from_cache': true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
final now = DateTime.now().toIso8601String();
|
||||
final combinedData = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'astronomy': astronomyData,
|
||||
};
|
||||
|
||||
final wrappedData = {
|
||||
'data': combinedData,
|
||||
'last_updated': now,
|
||||
};
|
||||
|
||||
await box.put(key, json.encode(wrappedData));
|
||||
log("Hive: Updated cache for $key at $now");
|
||||
|
||||
return {
|
||||
'data': combinedData,
|
||||
'last_updated': now,
|
||||
'from_cache': false,
|
||||
};
|
||||
} catch (e) {
|
||||
// This will propagate the error to the caller
|
||||
throw Exception('WeatherService.fetchWeather failed: $e');
|
||||
}
|
||||
|
||||
final now = DateTime.now().toIso8601String();
|
||||
final combinedData = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'astronomy': astronomyData,
|
||||
};
|
||||
|
||||
final wrappedData = {
|
||||
'data': combinedData,
|
||||
'last_updated': now,
|
||||
};
|
||||
|
||||
await box.put(key, json.encode(wrappedData));
|
||||
log("Hive: Updated cache for $key at $now");
|
||||
|
||||
return {
|
||||
'data': combinedData,
|
||||
'last_updated': now,
|
||||
'from_cache': false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Check if weather data has missing or incomplete fields
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'open_links.dart';
|
||||
import '../utils/condition_label_map.dart';
|
||||
@@ -8,7 +9,7 @@ Widget homeBottomBar(BuildContext context, bool isLight) {
|
||||
color: textColor,
|
||||
fontSize: 18,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationThickness: 3,
|
||||
decorationThickness: context.locale.languageCode == "en" ? 2 : 3,
|
||||
);
|
||||
|
||||
return RepaintBoundary(
|
||||
|
||||
+77
-65
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'is_online.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class WeatherFroggyManager {
|
||||
List<String> sunnyFrog = [];
|
||||
@@ -14,103 +15,100 @@ class WeatherFroggyManager {
|
||||
List<String> clearNightFrog = [];
|
||||
List<String> partlyCloudyNightFrog = [];
|
||||
|
||||
|
||||
Future<void> initializeIcons() async {
|
||||
final online = await hasRealInternet();
|
||||
if (online) {
|
||||
_setOnlineIcons();
|
||||
|
||||
} else {
|
||||
_setOfflineIcons();
|
||||
}
|
||||
|
||||
Connectivity().onConnectivityChanged.listen((event) async {
|
||||
if (await hasRealInternet()) {
|
||||
Future<void> initializeIcons() async {
|
||||
const testUrl =
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-creek-swimming.png?ref_type=heads";
|
||||
final online = await urlIsReachable(testUrl);
|
||||
if (online) {
|
||||
_setOnlineIcons();
|
||||
} else {
|
||||
_setOfflineIcons();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Connectivity().onConnectivityChanged.listen((event) async {
|
||||
if (await hasNetworkConnection() && await urlIsReachable(testUrl)) {
|
||||
_setOnlineIcons();
|
||||
} else {
|
||||
_setOfflineIcons();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _setOnlineIcons() {
|
||||
sunnyFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-creek-swimming.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-home-laundry_c.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-orchard-picking.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-home-laundry_f.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-creek-swimming.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-home-laundry_c.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-orchard-picking.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/01-sunny/01-sunny-home-laundry_f.png?ref_type=heads"
|
||||
];
|
||||
|
||||
mostlySunnyFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-citypark-picnic.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-beach-sunscreen.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-home-laundry.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-beach-sandcastle.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-rooftop-pinacolada.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-citypark-picnic.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-beach-sunscreen.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-home-laundry.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-beach-sandcastle.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/02-mostly-sunny/02-mostly-sunny-rooftop-pinacolada.png?ref_type=heads"
|
||||
];
|
||||
|
||||
partlyCloudyFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-field-hiking_f.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-home-flowers.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-creek-feet.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-field-biking_c.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-citypark-ukelele.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-field-hiking_f.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-home-flowers.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-creek-feet.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-field-biking_c.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/03-partly-cloudy-day/03-partly-cloudy-day-citypark-ukelele.png?ref_type=heads"
|
||||
];
|
||||
|
||||
overcastFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/09-cloudy/09-cloudy-home-flowers.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/09-cloudy/09-cloudy-orchard-watching.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/04-mostly-cloudy-day/04-mostly-cloudy-day-home-flowers.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/09-cloudy/09-cloudy-home-flowers.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/09-cloudy/09-cloudy-orchard-watching.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/04-mostly-cloudy-day/04-mostly-cloudy-day-home-flowers.png?ref_type=heads"
|
||||
];
|
||||
|
||||
fogFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-bridge.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-fruit-stand.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-mountain.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-pier.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-field-lantern.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-bridge.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-fruit-stand.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-mountain.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-pier.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/26-haze-fog-dust-smoke/26-haze-fog-dust-smoke-field-lantern.png?ref_type=heads"
|
||||
];
|
||||
|
||||
rainFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-rain-creek-leaf.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-rain-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/04-mostly-cloudy-day/04-mostly-cloudy-day-orchard-treeswing_f.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/12-heavy-rain/12-heavy-rain-busstop-umbrella.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/12-heavy-rain/12-heavy-rain-cafe-sitting-singing.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-shower-rain-field-leaf.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-rain-creek-leaf.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-rain-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/04-mostly-cloudy-day/04-mostly-cloudy-day-orchard-treeswing_f.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/12-heavy-rain/12-heavy-rain-busstop-umbrella.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/12-heavy-rain/12-heavy-rain-cafe-sitting-singing.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/11-rain/11-shower-rain-field-leaf.png?ref_type=heads"
|
||||
];
|
||||
|
||||
snowFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-citypark-snowman.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-home-shoveling.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-creek-iceskating.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/17-heavy-snow-blizzard/17-heavy-snow-blizzard-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/16-blowing-snow/16-blowing-snow-field-snowman.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-citypark-snowman.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-home-shoveling.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/15-snow-showers-snow/15-snow-showers-snow-creek-iceskating.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/17-heavy-snow-blizzard/17-heavy-snow-blizzard-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/16-blowing-snow/16-blowing-snow-field-snowman.png?ref_type=heads"
|
||||
];
|
||||
|
||||
thunderStormFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-cafe-looking-outside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-busstop-newspaper.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-home-inside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-cafe-looking-outside.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/22-iso-thunderstorms/22-iso-thunderstorms-busstop-newspaper.png?ref_type=heads"
|
||||
];
|
||||
|
||||
clearNightFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-creek-stars.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-field-lanterns.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-orchard-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-home-lounging.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-hills-telescope.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-creek-stars.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-field-lanterns.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-orchard-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-home-lounging.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/05-clear/05-clear-hills-telescope.png?ref_type=heads"
|
||||
];
|
||||
|
||||
partlyCloudyNightFrog = [
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-creek-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-field-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-hills-smores.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-orchard-eating.png?ref_type=heads"
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-creek-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-field-fireflies.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-hills-smores.png?ref_type=heads",
|
||||
"https://gitlab.com/bignutty/google-weather-icons/-/raw/main/froggie/v2/mobile/07-partly-cloudy-night/07-partly-cloudy-night-orchard-eating.png?ref_type=heads"
|
||||
];
|
||||
|
||||
|
||||
}
|
||||
|
||||
void _setOfflineIcons() {
|
||||
@@ -126,7 +124,7 @@ Future<void> initializeIcons() async {
|
||||
partlyCloudyNightFrog = ['assets/froggie/02n.png'];
|
||||
}
|
||||
|
||||
final Random _random = Random();
|
||||
final Random _random = Random();
|
||||
|
||||
String getFroggieIcon(int iconCode, bool isDay) {
|
||||
if (isDay) {
|
||||
@@ -140,7 +138,8 @@ Future<void> initializeIcons() async {
|
||||
return _randomIcon(overcastFrog);
|
||||
} else if ([45, 48].contains(iconCode)) {
|
||||
return _randomIcon(fogFrog);
|
||||
} else if ([51, 53, 55, 56, 57, 61, 63, 65, 80, 81, 82].contains(iconCode)) {
|
||||
} else if ([51, 53, 55, 56, 57, 61, 63, 65, 80, 81, 82]
|
||||
.contains(iconCode)) {
|
||||
return _randomIcon(rainFrog);
|
||||
} else if ([66, 67, 71, 73, 75, 77, 85, 86].contains(iconCode)) {
|
||||
return _randomIcon(snowFrog);
|
||||
@@ -152,11 +151,12 @@ Future<void> initializeIcons() async {
|
||||
return _randomIcon(clearNightFrog);
|
||||
} else if ([1, 2].contains(iconCode)) {
|
||||
return _randomIcon(partlyCloudyNightFrog);
|
||||
} else if (iconCode == 3){
|
||||
} else if (iconCode == 3) {
|
||||
return _randomIcon(overcastFrog);
|
||||
} else if ([45, 48].contains(iconCode)) {
|
||||
return _randomIcon(fogFrog);
|
||||
} else if ([51, 53, 55, 56, 57, 61, 63, 65, 80, 81, 82].contains(iconCode)) {
|
||||
} else if ([51, 53, 55, 56, 57, 61, 63, 65, 80, 81, 82]
|
||||
.contains(iconCode)) {
|
||||
return _randomIcon(rainFrog);
|
||||
} else if ([66, 67, 71, 73, 75, 77, 85, 86].contains(iconCode)) {
|
||||
return _randomIcon(snowFrog);
|
||||
@@ -173,3 +173,15 @@ Future<void> initializeIcons() async {
|
||||
return list[_random.nextInt(list.length)];
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> urlIsReachable(String url,
|
||||
{Duration timeout = const Duration(seconds: 5)}) async {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
final request = await HttpClient().headUrl(uri).timeout(timeout);
|
||||
final response = await request.close();
|
||||
return response.statusCode >= 200 && response.statusCode < 400;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+10
-35
@@ -1,39 +1,14 @@
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
||||
// Checks for actual internet access
|
||||
Future<bool> hasRealInternet() async {
|
||||
final testUrls = [
|
||||
'http://connect.rom.miui.com/generate_204', // MIUI
|
||||
'http://captive.apple.com', // Apple
|
||||
'http://example.com', // Lightweight
|
||||
'http://baidu.com', // China
|
||||
'http://clients3.google.com/generate_204', // Android
|
||||
'http://www.msftconnecttest.com/connecttest.txt', // Windows
|
||||
'http://www.gstatic.com/generate_204', // Google lightweight
|
||||
'https://example.com', // HTTPS fallback
|
||||
];
|
||||
Future<bool> hasNetworkConnection() async {
|
||||
final result = await Connectivity().checkConnectivity();
|
||||
|
||||
try {
|
||||
final dnsResult = await InternetAddress.lookup('example.com')
|
||||
.timeout(const Duration(seconds: 3));
|
||||
if (dnsResult.isEmpty || dnsResult[0].rawAddress.isEmpty) return false;
|
||||
|
||||
for (final url in testUrls) {
|
||||
try {
|
||||
final response =
|
||||
await http.get(Uri.parse(url)).timeout(const Duration(seconds: 3));
|
||||
if (response.statusCode == 204 ||
|
||||
response.statusCode == 200 ||
|
||||
response.statusCode == 302) {
|
||||
return true;
|
||||
}
|
||||
} catch (_) {
|
||||
// try next
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
if (result is List) {
|
||||
final first = result.isNotEmpty ? result.first : ConnectivityResult.none;
|
||||
return first == ConnectivityResult.mobile ||
|
||||
first == ConnectivityResult.wifi;
|
||||
} else {
|
||||
return result == ConnectivityResult.mobile ||
|
||||
result == ConnectivityResult.wifi;
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import 'services/fetch_data.dart';
|
||||
import 'utils/preferences_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'utils/unit_converter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'utils/unit_converter.dart';
|
||||
import 'utils/condition_label_map.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
@@ -14,9 +14,6 @@ Future<void> updateHomeWidget(weather, {bool updatedFromHome = false}) async {
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
|
||||
await PreferencesHelper.init();
|
||||
await HomeWidget.setAppGroupId('com.pranshulgg.weather_master_app');
|
||||
|
||||
@@ -289,6 +286,8 @@ Future<void> updateHomeWidget(weather, {bool updatedFromHome = false}) async {
|
||||
name: 'clockDateWidgetProvider', iOSName: null);
|
||||
await HomeWidget.updateWidget(
|
||||
name: 'DateCurrentWidgetProvider', iOSName: null);
|
||||
await HomeWidget.updateWidget(
|
||||
name: 'ClockHourlyWidgetProvider', iOSName: null);
|
||||
|
||||
print('[WidgetUpdate] Called updateWidget');
|
||||
} catch (e, stack) {
|
||||
|
||||
@@ -295,7 +295,7 @@ class AnimatedTemperature extends StatelessWidget {
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Theme.of(context).colorScheme.inverseSurface
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
fontSize: isShowFrog ? 60 : 130,
|
||||
fontSize: isShowFrog ? 65 : 136,
|
||||
fontWeight: isShowFrog ? null : FontWeight.bold,
|
||||
height: isShowFrog ? 1.3 : 1,
|
||||
),
|
||||
@@ -370,7 +370,7 @@ class _WeatherTopCardStateVertical extends State<WeatherTopCardVertical> {
|
||||
final colorTheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@@ -392,6 +392,7 @@ class _WeatherTopCardStateVertical extends State<WeatherTopCardVertical> {
|
||||
height: 6,
|
||||
),
|
||||
Row(
|
||||
spacing: 3,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PreferencesHelper.getBool("useTempAnimation") == false
|
||||
@@ -401,13 +402,13 @@ class _WeatherTopCardStateVertical extends State<WeatherTopCardVertical> {
|
||||
Brightness.light
|
||||
? Theme.of(context).colorScheme.inverseSurface
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
fontSize: 130,
|
||||
fontSize: 136,
|
||||
fontWeight: isShowFrog ? null : FontWeight.bold,
|
||||
height: 1.3))
|
||||
: AnimatedTemperature(
|
||||
targetTemp: convertedTemp.toDouble()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 40),
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: SvgPicture.asset(
|
||||
WeatherIconMapper.getIcon(
|
||||
widget.currentWeatherIconCode, widget.currentisDay),
|
||||
@@ -418,7 +419,7 @@ class _WeatherTopCardStateVertical extends State<WeatherTopCardVertical> {
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
height: 10,
|
||||
),
|
||||
Text(
|
||||
"${'feels_like'.tr()} ${widget.currentFeelsLike == 0000 ? '--' : '$convertedfeelsLikeTemp°'}",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
@@ -0,0 +1,16 @@
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 11ms
|
||||
[gap of 87ms]
|
||||
create-initial-cxx-model completed in 98ms
|
||||
create_cxx_tasks completed in 102ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-variant-model 19ms
|
||||
[gap of 65ms]
|
||||
create-initial-cxx-model completed in 88ms
|
||||
create_cxx_tasks completed in 90ms
|
||||
|
||||
+67
@@ -675,3 +675,70 @@ create_cxx_tasks
|
||||
create-initial-cxx-model 83ms
|
||||
create_cxx_tasks completed in 86ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 32ms
|
||||
create-variant-model 13ms
|
||||
create-ARMEABI_V7A-model 36ms
|
||||
[gap of 76ms]
|
||||
create-ARM64_V8A-model 12ms
|
||||
[gap of 13ms]
|
||||
create-initial-cxx-model completed in 186ms
|
||||
[gap of 22ms]
|
||||
create_cxx_tasks completed in 215ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model 86ms
|
||||
create_cxx_tasks completed in 91ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 32ms
|
||||
create-variant-model 15ms
|
||||
create-ARMEABI_V7A-model 43ms
|
||||
[gap of 15ms]
|
||||
create-X86_64-model 10ms
|
||||
[gap of 24ms]
|
||||
create-X86-model 13ms
|
||||
[gap of 29ms]
|
||||
create-ARM64_V8A-model 15ms
|
||||
[gap of 10ms]
|
||||
create-X86_64-model 13ms
|
||||
create-initial-cxx-model completed in 228ms
|
||||
[gap of 28ms]
|
||||
create_cxx_tasks completed in 263ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model 111ms
|
||||
create_cxx_tasks completed in 116ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 39ms
|
||||
create-variant-model 16ms
|
||||
create-ARMEABI_V7A-model 44ms
|
||||
[gap of 110ms]
|
||||
create-initial-cxx-model completed in 215ms
|
||||
[gap of 26ms]
|
||||
create_cxx_tasks completed in 251ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 13ms
|
||||
[gap of 18ms]
|
||||
create-ARM64_V8A-model 10ms
|
||||
create-X86_64-model 10ms
|
||||
[gap of 33ms]
|
||||
create-X86-model 11ms
|
||||
create-module-model 15ms
|
||||
create-ARMEABI_V7A-model 10ms
|
||||
create-X86-model 13ms
|
||||
create-initial-cxx-model completed in 175ms
|
||||
create_cxx_tasks completed in 182ms
|
||||
|
||||
Reference in new issue
Block a user