From 088c3d995307cf719765339d2edf8730010e04ec Mon Sep 17 00:00:00 2001 From: Pranshul Date: Fri, 24 Oct 2025 16:07:57 +0530 Subject: [PATCH] added a fix #672 --- lib/screens/about_page.dart | 208 +++++++++++++++++++++++++++++++++++- lib/widget_background.dart | 13 ++- 2 files changed, 210 insertions(+), 11 deletions(-) diff --git a/lib/screens/about_page.dart b/lib/screens/about_page.dart index 78c82ed04..57c45fa00 100644 --- a/lib/screens/about_page.dart +++ b/lib/screens/about_page.dart @@ -1,7 +1,4 @@ -import 'dart:math'; - import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; @@ -12,6 +9,12 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:expressive_loading_indicator/expressive_loading_indicator.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; class AboutPage extends StatefulWidget { const AboutPage({super.key}); @@ -297,7 +300,23 @@ class _AboutPageState extends State { }, ); }, - ) + ), + ListTile( + minTileHeight: 65, + leading: CircleAvatar( + radius: 23, + child: Icon(Symbols.favorite), + ), + title: Text("Contributors"), + subtitle: Text("Translators"), + onTap: () { + TranslatorsDialog.show( + context, + projectId: '741419', + apiToken: dotenv.env['API_TOKEN']!.toString(), + ); + }, + ), ], ), ), @@ -786,3 +805,184 @@ class _ChangelogSheetState extends State { ); } } + +// CONTRIBUTORS + +class Translator { + final String name; + final String fullName; + final String role; + + Translator({required this.name, required this.fullName, required this.role}); + + factory Translator.fromJson(Map json) { + return Translator( + name: json['username'] ?? '', + fullName: json['fullName'] ?? '', + role: json['role'] ?? '', + ); + } +} + +class TranslatorsDialog { + static const String cacheKey = 'translatorsData'; + + static Future> fetchTranslators( + {required String projectId, required String apiToken}) async { + try { + final prefs = await SharedPreferences.getInstance(); + + final savedData = prefs.getString(cacheKey); + if (savedData != null) { + final cached = json.decode(savedData); + final timestamp = cached['timestamp'] as int; + final translatorsData = cached['translators'] as List; + + if (DateTime.now() + .difference(DateTime.fromMillisecondsSinceEpoch(timestamp)) + .inHours < + 24) { + print('Using cached translators data'); + + return translatorsData.map((e) => Translator.fromJson(e)).toList(); + } + } + + final response = await http.get( + Uri.parse('https://api.crowdin.com/api/v2/projects/$projectId/members'), + headers: { + 'Authorization': 'Bearer $apiToken', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode != 200) { + throw Exception('Error fetching members: ${response.statusCode}'); + } + + final data = json.decode(response.body); + final translators = (data['data'] as List) + .where((member) => member['data']['role'] == 'translator') + .map((member) => Translator( + name: member['data']['username'] ?? '', + fullName: member['data']['fullName'] ?? '', + role: member['data']['role'] ?? '', + )) + .toList(); + + final cacheData = json.encode({ + 'timestamp': DateTime.now().millisecondsSinceEpoch, + 'translators': translators + .map((t) => + {'username': t.name, 'fullName': t.fullName, 'role': t.role}) + .toList(), + }); + + await prefs.setString(cacheKey, cacheData); + + return translators; + } catch (e) { + print('Error fetching translators: $e'); + + final prefs = await SharedPreferences.getInstance(); + final savedData = prefs.getString(cacheKey); + if (savedData != null) { + try { + final cached = json.decode(savedData); + final translatorsData = cached['translators'] as List; + print('Using cached data fallback'); + return translatorsData.map((e) => Translator.fromJson(e)).toList(); + } catch (_) {} + } + + return []; + } + } + + static Future show(BuildContext context, + {required String projectId, required String apiToken}) async { + List translators = []; + bool isLoading = true; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + builder: (context) { + return StatefulBuilder( + builder: (context, setState) { + if (isLoading) { + fetchTranslators(projectId: projectId, apiToken: apiToken) + .then((fetched) { + setState(() { + translators = fetched; + isLoading = false; + }); + }); + } + + final maxHeight = MediaQuery.of(context).size.height * 0.75; + + return Container( + constraints: BoxConstraints( + maxHeight: maxHeight, + ), + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + left: 16, + right: 16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : translators.isEmpty + ? const Center( + child: Text('No translators available.')) + : ListView.builder( + itemCount: translators.length, + itemBuilder: (context, index) { + final t = translators[index]; + final displayName = (t.fullName.isNotEmpty + ? t.fullName + : t.name) + .replaceAll( + RegExp(r'[^\u0000-\uFFFF]'), ''); + final displayRole = (t.role.isNotEmpty + ? t.role[0].toUpperCase() + + t.role.substring(1) + : ''); + return ListTile( + leading: CircleAvatar( + child: Text( + displayName.isNotEmpty + ? displayName[0] + : '?', + ), + ), + title: Text('$displayName (${t.name})'), + subtitle: Text(displayRole), + ); + }, + ), + ), + Divider(), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ), + SizedBox(height: MediaQuery.of(context).padding.bottom + 10), + ], + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/widget_background.dart b/lib/widget_background.dart index 07875587b..5cf8fae6b 100644 --- a/lib/widget_background.dart +++ b/lib/widget_background.dart @@ -171,9 +171,9 @@ Future updateHomeWidget(weather, {bool updatedFromHome = false}) async { locale = Locale(localeString); } - String translationFileName = locale.languageCode; + String translationFileName = locale.languageCode.toLowerCase(); if (locale.countryCode != null && locale.countryCode!.isNotEmpty) { - translationFileName += '-${locale.countryCode}'; + translationFileName += '-${locale.countryCode!.toUpperCase()}'; } Map translations; @@ -182,14 +182,13 @@ Future updateHomeWidget(weather, {bool updatedFromHome = false}) async { final String data = await rootBundle .loadString('assets/translations/$translationFileName.json'); translations = jsonDecode(data); - } catch (e) { + } catch (_) { print( - '[Translation] Could not load $translationFileName.json, falling back to en.json'); - final String data = - await rootBundle.loadString('assets/translations/en.json'); + '[Translation] $translationFileName.json not found, falling back to ${locale.languageCode}.json'); + final String data = await rootBundle + .loadString('assets/translations/${locale.languageCode}.json'); translations = jsonDecode(data); } - final conditionNameDaily = translations[conditionKey] ?? conditionKey; final maxTempFormatted = tempUnit == 'Fahrenheit'