Compare commits

..

8 Commits

Author SHA1 Message Date
58578c734e 🍱 Update app icon 2024-08-15 23:00:25 +08:00
76f2c0ad58 🐛 Fixes no api key hint 2024-08-15 22:53:49 +08:00
cfa61e1a8a Support named alert config 2024-08-15 22:50:00 +08:00
22b863f2bf 🌐 Complete english translations 2024-08-15 21:24:41 +08:00
4569c33430 Search hint 2024-08-15 21:20:07 +08:00
bd50859e73 💄 Finishing up 2024-08-15 21:12:23 +08:00
0b34a4b74b 🚸 Increase page size of once query 2024-08-15 20:13:21 +08:00
403639088f Editable search data collections 2024-08-15 20:10:07 +08:00
65 changed files with 921 additions and 267 deletions

View File

@@ -6,7 +6,7 @@ plugins {
}
android {
namespace = "com.example.dietary_guard"
namespace = "dev.solsynth.dietaryGuard"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -20,8 +20,7 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.dietary_guard"
applicationId = "dev.solsynth.dietaryGuard"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion

View File

@@ -2,7 +2,7 @@
<application
android:label="dietary_guard"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -1,29 +1,48 @@
PODS:
- Flutter (1.0.0)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite:
:path: ".symlinks/plugins/sqflite/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

View File

@@ -542,7 +542,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -599,7 +599,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -25,14 +25,22 @@ class FoodDataController extends GetxController {
await _prefs.setString("data_fdc_api_key", value);
}
List<String>? getDataCollections() {
return _prefs.getStringList("data_enabled_collections");
}
Future<void> setDataCollections(List<String> value) async {
await _prefs.setStringList("data_enabled_collections", value);
}
Future<FoodDataQueryResponse> searchFood(String probe) async {
final client = Dio();
final resp = await client.get(
'https://api.nal.usda.gov/fdc/v1/foods/search',
queryParameters: {
'query': probe,
'dataType': 'Foundation',
'pageSize': 25,
'dataType': getDataCollections()?.join(','),
'pageSize': 100,
'pageNumber': 1,
'sortBy': 'dataType.keyword',
'sortOrder': 'asc',

View File

@@ -2,6 +2,8 @@ import 'package:dietary_guard/controllers/alert.dart';
import 'package:dietary_guard/controllers/food_data.dart';
import 'package:dietary_guard/screens/query.dart';
import 'package:dietary_guard/screens/settings.dart';
import 'package:dietary_guard/screens/settings/alert.dart';
import 'package:dietary_guard/screens/settings/data_source.dart';
import 'package:dietary_guard/shells/nav_shell.dart';
import 'package:dietary_guard/translations.dart';
import 'package:flutter/material.dart';
@@ -26,6 +28,16 @@ final router = GoRouter(routes: [
name: "settings",
builder: (context, state) => const SettingsScreen(),
),
GoRoute(
path: "/settings/alerts",
name: "settingsAlert",
builder: (context, state) => const AlertSettingsScreen(),
),
GoRoute(
path: "/settings/data-source",
name: "settingsDataSource",
builder: (context, state) => const DataSourceSettingsScreen(),
),
],
),
]);

View File

@@ -1,15 +1,20 @@
import 'package:dietary_guard/models/food_data.dart';
class AlertConfiguration {
String name;
int nutrientId;
double maxValue;
double minValue;
AlertConfiguration({
required this.name,
required this.nutrientId,
required this.maxValue,
required this.minValue,
});
Map<String, dynamic> toJson() => {
'name': name,
'nutrient_id': nutrientId,
'min_value': minValue,
'max_value': maxValue,
@@ -17,6 +22,7 @@ class AlertConfiguration {
factory AlertConfiguration.fromJson(Map<String, dynamic> json) =>
AlertConfiguration(
name: json['name'],
nutrientId: json['nutrient_id'],
minValue: json['min_value'],
maxValue: json['max_value'],
@@ -25,18 +31,18 @@ class AlertConfiguration {
class AlertDetectResult {
AlertConfiguration config;
FoodNutrient? nutrient;
String name;
String? unitName;
double? current;
double? difference;
bool isOutOfRange;
bool isUndetected;
AlertDetectResult({
required this.config,
required this.nutrient,
required this.name,
required this.unitName,
required this.current,
required this.difference,
required this.isOutOfRange,
required this.isUndetected,

View File

@@ -98,7 +98,7 @@ class Nutrients {
}
class FoodSearchCriteria {
List<Type?> dataType;
List<FoodType?> dataType;
String query;
String generalSearchInput;
int pageNumber;
@@ -107,7 +107,7 @@ class FoodSearchCriteria {
int numberOfResultsPerPage;
int pageSize;
bool requireAllWords;
List<Type?> foodTypes;
List<FoodType?> foodTypes;
FoodSearchCriteria({
required this.dataType,
@@ -124,7 +124,7 @@ class FoodSearchCriteria {
factory FoodSearchCriteria.fromJson(Map<String, dynamic> json) =>
FoodSearchCriteria(
dataType: List<Type>.from(
dataType: List<FoodType?>.from(
json["dataType"]?.map((x) => typeValues.map[x]) ?? List.empty()),
query: json["query"],
generalSearchInput: json["generalSearchInput"],
@@ -134,7 +134,7 @@ class FoodSearchCriteria {
numberOfResultsPerPage: json["numberOfResultsPerPage"],
pageSize: json["pageSize"],
requireAllWords: json["requireAllWords"],
foodTypes: List<Type>.from(
foodTypes: List<FoodType?>.from(
json["foodTypes"]?.map((x) => typeValues.map[x]) ?? List.empty()),
);
@@ -154,20 +154,20 @@ class FoodSearchCriteria {
};
}
enum Type { FOUNDATION, SR_LEGACY }
enum FoodType { FOUNDATION, SR_LEGACY }
final typeValues =
EnumValues({"Foundation": Type.FOUNDATION, "SR Legacy": Type.SR_LEGACY});
final typeValues = EnumValues(
{"Foundation": FoodType.FOUNDATION, "SR Legacy": FoodType.SR_LEGACY});
class FoodData {
int fdcId;
String description;
String? commonNames;
String? additionalDescriptions;
Type? dataType;
FoodType? dataType;
int? ndbNumber;
DateTime publishedDate;
FoodCategory? foodCategory;
String? foodCategory;
DateTime? mostRecentAcquisitionDate;
String allHighlightFields;
double score;
@@ -208,7 +208,7 @@ class FoodData {
dataType: typeValues.map[json["dataType"]],
ndbNumber: json["ndbNumber"],
publishedDate: DateTime.parse(json["publishedDate"]),
foodCategory: foodCategoryValues.map[json["foodCategory"]],
foodCategory: json["foodCategory"],
mostRecentAcquisitionDate: json["mostRecentAcquisitionDate"] == null
? null
: DateTime.parse(json["mostRecentAcquisitionDate"]),
@@ -237,7 +237,7 @@ class FoodData {
"ndbNumber": ndbNumber,
"publishedDate":
"${publishedDate.year.toString().padLeft(4, '0')}-${publishedDate.month.toString().padLeft(2, '0')}-${publishedDate.day.toString().padLeft(2, '0')}",
"foodCategory": foodCategoryValues.reverse[foodCategory],
"foodCategory": foodCategory,
"mostRecentAcquisitionDate":
"${mostRecentAcquisitionDate!.year.toString().padLeft(4, '0')}-${mostRecentAcquisitionDate!.month.toString().padLeft(2, '0')}-${mostRecentAcquisitionDate!.day.toString().padLeft(2, '0')}",
"allHighlightFields": allHighlightFields,
@@ -255,11 +255,6 @@ class FoodData {
};
}
enum FoodCategory { DAIRY_AND_EGG_PRODUCTS }
final foodCategoryValues =
EnumValues({"Dairy and Egg Products": FoodCategory.DAIRY_AND_EGG_PRODUCTS});
class FoodNutrient {
int nutrientId;
String nutrientName;

View File

@@ -28,13 +28,19 @@ class _FoodDetailsScreenState extends State<FoodDetailsScreen> {
if (alert.configuration.isEmpty) return;
for (final item in alert.configuration) {
bool isUndetected = true;
bool isOutOfRange = false;
double? difference;
String name = 'undetected'.tr;
String? unitName;
FoodNutrient? current;
for (final nutrient in widget.item.foodNutrients) {
if (item.nutrientId != nutrient.nutrientId) continue;
bool isOutOfRange = false;
double? difference;
bool isUndetected = false;
name = nutrient.nutrientName;
unitName = unitNameValues.reverse[nutrient.unitName];
if (nutrient.value != null) {
final value = nutrient.value ?? 0;
current = nutrient;
final value = nutrient.value!;
if (value > item.maxValue) {
difference = value - item.maxValue;
isOutOfRange = true;
@@ -42,20 +48,19 @@ class _FoodDetailsScreenState extends State<FoodDetailsScreen> {
difference = value - item.minValue;
isOutOfRange = true;
}
} else {
isUndetected = true;
isUndetected = false;
}
_alertDetectResult.add(AlertDetectResult(
config: item,
name: nutrient.nutrientName,
unitName: unitNameValues.reverse[nutrient.unitName],
current: nutrient.value,
difference: difference,
isOutOfRange: isOutOfRange,
isUndetected: isUndetected,
));
}
_alertDetectResult.add(AlertDetectResult(
config: item,
nutrient: current,
name: name,
unitName: unitName,
difference: difference,
isOutOfRange: isOutOfRange,
isUndetected: isUndetected,
));
}
}

View File

@@ -15,7 +15,9 @@ class QueryScreen extends StatefulWidget {
class _QueryScreenState extends State<QueryScreen> {
bool _isLoading = false;
bool _hasApiKey = true;
int? _totalCount;
List<FoodData> _foodData = List.empty();
Future<void> _searchFood(String probe) async {
@@ -24,11 +26,15 @@ class _QueryScreenState extends State<QueryScreen> {
setState(() => _isLoading = true);
final FoodDataController data = Get.find();
if (data.getApiKey() == null) return;
if (data.getApiKey() == null || data.getApiKey()!.isEmpty) {
setState(() => _hasApiKey = false);
return;
}
final result = await data.searchFood(probe);
setState(() {
_totalCount = result.totalHits;
_foodData = result.foods;
_isLoading = false;
});
@@ -38,7 +44,11 @@ class _QueryScreenState extends State<QueryScreen> {
void initState() {
super.initState();
final FoodDataController data = Get.find();
data.initialize(context);
data.initialize(context).then((_) {
setState(() {
_hasApiKey = data.getApiKey() != null && data.getApiKey()!.isNotEmpty;
});
});
}
@override
@@ -48,6 +58,14 @@ class _QueryScreenState extends State<QueryScreen> {
child: SafeArea(
child: Column(
children: [
if (!_hasApiKey)
Text('searchNoKeyHint'.tr).paddingSymmetric(vertical: 8)
else if (_totalCount != null)
Text('searchResultHint'
.trParams({'count': _totalCount.toString()}))
.paddingSymmetric(vertical: 8)
else
Text('searchHint'.tr).paddingSymmetric(vertical: 8),
SearchBar(
padding: const WidgetStatePropertyAll<EdgeInsets>(
EdgeInsets.symmetric(horizontal: 16.0),
@@ -75,7 +93,7 @@ class _QueryScreenState extends State<QueryScreen> {
const EdgeInsets.symmetric(horizontal: 24),
title: Text(item.description),
subtitle: Text(
'${DateFormat("yyyy-MM-dd").format(item.mostRecentAcquisitionDate ?? item.publishedDate)} ${foodCategoryValues.reverse[item.foodCategory] ?? ''}',
'${DateFormat("yyyy-MM-dd").format(item.mostRecentAcquisitionDate ?? item.publishedDate)} ${item.foodCategory ?? ''}',
),
onTap: () => open(),
),

View File

@@ -1,71 +1,13 @@
import 'package:dietary_guard/controllers/alert.dart';
import 'package:dietary_guard/controllers/food_data.dart';
import 'package:dietary_guard/models/alert_configuration.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SettingsScreen extends StatefulWidget {
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
final TextEditingController _fdcApiKeyController = TextEditingController();
List<AlertConfiguration> _currentAlerts = List.empty(growable: true);
void _addAlert() {
setState(() {
_currentAlerts.add(AlertConfiguration(
nutrientId: 0,
maxValue: 0,
minValue: 0,
));
});
}
Future<void> _applySettings() async {
final FoodDataController data = Get.find();
await data.setApiKey(_fdcApiKeyController.text);
final AlertController alert = Get.find();
await alert.setAlertConfiguration(_currentAlerts);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('settingsApplied'.tr),
));
}
@override
void initState() {
final FoodDataController data = Get.find();
_fdcApiKeyController.text = data.getApiKey() ?? '';
final AlertController alert = Get.find();
_currentAlerts = List.from(alert.configuration, growable: true);
super.initState();
}
@override
void dispose() {
_fdcApiKeyController.dispose();
super.dispose();
}
Widget _buildSectionHeader(String title) {
return Container(
color: Theme.of(context).colorScheme.secondaryContainer,
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 8),
child: Text(
title,
style: Theme.of(context).textTheme.bodyLarge,
),
).marginOnly(bottom: 16);
}
@override
Widget build(BuildContext context) {
return Material(
@@ -76,92 +18,65 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
body: ListView(
children: [
_buildSectionHeader('settingsAlertSection'.tr),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...(_currentAlerts.map((x) => Row(
children: [
Expanded(
child: TextFormField(
initialValue: x.nutrientId.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertNutrientId".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.nutrientId = int.tryParse(value) ?? 0;
},
),
),
const SizedBox(width: 6),
Expanded(
child: TextFormField(
initialValue: x.maxValue.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertMaxValue".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.maxValue = double.tryParse(value) ?? 0;
},
),
),
const SizedBox(width: 6),
Expanded(
child: TextFormField(
initialValue: x.minValue.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertMinValue".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.minValue = double.tryParse(value) ?? 0;
},
),
),
],
))),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
label: Text('newAlert'.tr),
onPressed: () => _addAlert(),
),
],
).paddingSymmetric(vertical: 12, horizontal: 24),
],
).paddingSymmetric(horizontal: 24),
_buildSectionHeader('settingsDataSection'.tr),
TextField(
controller: _fdcApiKeyController,
obscureText: true,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
label: Text("FDC API Key"),
isDense: false,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
).paddingSymmetric(horizontal: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
label: Text('apply'.tr),
onPressed: () => _applySettings(),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: Image.asset("assets/icon.png", width: 64, height: 64),
).paddingAll(8),
const SizedBox(height: 12),
Text(
'appName'.tr,
style: Theme.of(context).textTheme.headlineMedium,
),
Text('appDescription'.tr),
FutureBuilder(
future: PackageInfo.fromPlatform(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('loading'.tr);
}
return Text(
'v${snapshot.data!.version} (${snapshot.data!.buildNumber})',
style: GoogleFonts.ibmPlexMono(fontSize: 12),
);
},
),
],
).paddingSymmetric(vertical: 12, horizontal: 24),
).paddingOnly(left: 24, right: 24, bottom: 16),
const Divider(height: 1, thickness: 0.3).paddingOnly(
bottom: 16,
top: 8,
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Icons.chevron_right),
title: Text('settingsAlertSection'.tr),
onTap: () {
GoRouter.of(context).pushNamed('settingsAlert');
},
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Icons.chevron_right),
title: Text('settingsDataSection'.tr),
onTap: () {
GoRouter.of(context).pushNamed('settingsDataSource');
},
),
const Divider(height: 1, thickness: 0.3)
.paddingOnly(top: 16, bottom: 8),
Center(
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Text('appCopyright'.tr, textAlign: TextAlign.center)
.paddingAll(8),
onTap: () {
launchUrlString("https://solsynth.dev");
},
),
),
],
),
),

View File

@@ -0,0 +1,167 @@
import 'package:dietary_guard/controllers/alert.dart';
import 'package:dietary_guard/models/alert_configuration.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AlertSettingsScreen extends StatefulWidget {
const AlertSettingsScreen({super.key});
@override
State<AlertSettingsScreen> createState() => _AlertSettingsScreenState();
}
class _AlertSettingsScreenState extends State<AlertSettingsScreen> {
List<AlertConfiguration> _currentAlerts = List.empty(growable: true);
void _addAlert() {
setState(() {
_currentAlerts.add(AlertConfiguration(
name: 'Alert #${_currentAlerts.length}',
nutrientId: 0,
maxValue: 0,
minValue: 0,
));
});
}
void _removeAlert(AlertConfiguration item) {
setState(() {
_currentAlerts.remove(item);
});
}
Future<void> _applySettings() async {
final AlertController alert = Get.find();
await alert.setAlertConfiguration(_currentAlerts);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('settingsApplied'.tr),
));
}
@override
void initState() {
final AlertController alert = Get.find();
_currentAlerts = List.from(alert.configuration, growable: true);
super.initState();
}
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
title: Text('alertSettings'.tr),
),
body: ListView(
children: [
..._currentAlerts.map(
(x) => Card(
child: Column(
children: [
Row(
children: [
Expanded(
child: TextFormField(
initialValue: x.name,
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertName".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.name = value;
},
),
),
const SizedBox(width: 6),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_removeAlert(x);
},
),
],
),
const SizedBox(height: 12),
TextFormField(
initialValue: x.nutrientId.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertNutrientId".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.nutrientId = int.tryParse(value) ?? 0;
},
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextFormField(
initialValue: x.maxValue.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertMaxValue".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.maxValue = double.tryParse(value) ?? 0;
},
),
),
const SizedBox(width: 6),
Expanded(
child: TextFormField(
initialValue: x.minValue.toString(),
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("alertMinValue".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onChanged: (value) {
x.minValue = double.tryParse(value) ?? 0;
},
),
),
],
),
],
).paddingAll(16),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
icon: const Icon(Icons.save, size: 16),
label: Text('apply'.tr),
onPressed: () => _applySettings(),
),
ElevatedButton.icon(
style: const ButtonStyle(
foregroundColor: WidgetStatePropertyAll(Colors.teal),
),
icon: const Icon(Icons.add, size: 16),
label: Text('newAlert'.tr),
onPressed: () => _addAlert(),
),
],
).paddingSymmetric(vertical: 4),
],
).paddingSymmetric(horizontal: 12),
),
);
}
}

View File

@@ -0,0 +1,188 @@
import 'package:dietary_guard/controllers/food_data.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DataSourceSettingsScreen extends StatefulWidget {
const DataSourceSettingsScreen({super.key});
@override
State<DataSourceSettingsScreen> createState() =>
_DataSourceSettingsScreenState();
}
class _DataSourceSettingsScreenState extends State<DataSourceSettingsScreen> {
final TextEditingController _fdcApiKeyController = TextEditingController();
final List<(String, String, String)> _dataCollectionList = [
(
"Foundation",
"dataCollectionFoundation".tr,
"dataCollectionFoundationDescription".tr,
),
(
"Branded",
"dataCollectionBranded".tr,
"dataCollectionBrandedDescription".tr,
),
(
"Survey (FNDDS)",
"dataCollectionSurvey".tr,
"dataCollectionSurveyDescription".tr,
),
(
"SR Legacy",
"dataCollectionLegacy".tr,
"dataCollectionLegacyDescription".tr,
),
];
List<String> _enabledDataCollections = List.empty(growable: true);
Future<void> _applySettings() async {
final FoodDataController data = Get.find();
await data.setApiKey(_fdcApiKeyController.text);
await data.setDataCollections(_enabledDataCollections);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('settingsApplied'.tr),
));
}
@override
void initState() {
final FoodDataController data = Get.find();
_fdcApiKeyController.text = data.getApiKey() ?? '';
_enabledDataCollections = List.from(
data.getDataCollections() ?? List.empty(),
growable: true,
);
super.initState();
}
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
title: Text('dataSourceSettings'.tr),
),
body: ListView(
children: [
const SizedBox(height: 8),
DropdownButtonFormField2<String>(
isExpanded: true,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
hint: Text(
'dataCollectionSelection'.tr,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
items: _dataCollectionList.map((item) {
return DropdownMenuItem(
value: item.$1,
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final isSelected =
_enabledDataCollections.contains(item.$1);
return InkWell(
onTap: () {
isSelected
? _enabledDataCollections.remove(item.$1)
: _enabledDataCollections.add(item.$1);
setState(() {});
menuSetState(() {});
},
child: ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 16.0),
leading: isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
title: Text(item.$2),
subtitle: Text(item.$3),
),
);
},
),
);
}).toList(),
value: _enabledDataCollections.isEmpty
? null
: _enabledDataCollections.last,
onChanged: (value) {},
selectedItemBuilder: (context) {
return _dataCollectionList.map(
(item) {
return Text(
_enabledDataCollections.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
);
},
).toList();
},
buttonStyleData: const ButtonStyleData(height: 20),
menuItemStyleData: const MenuItemStyleData(
height: 80,
padding: EdgeInsets.zero,
),
),
const SizedBox(height: 16),
TextField(
controller: _fdcApiKeyController,
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("fdcApiKey".tr),
isDense: true,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
Text(
'fdcApiKeyHint'.tr,
style: TextStyle(color: _unFocusColor),
).paddingOnly(
left: 8,
right: 8,
top: 8,
),
const Divider(height: 1, thickness: 0.3).paddingSymmetric(
vertical: 8,
),
Text(
'fdcApiCredit'.tr,
style: TextStyle(color: _unFocusColor),
).paddingSymmetric(horizontal: 8),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.save, size: 16),
label: Text('apply'.tr),
onPressed: () => _applySettings(),
),
],
).paddingSymmetric(vertical: 12),
],
).paddingSymmetric(horizontal: 24),
),
);
}
}

View File

@@ -1,16 +1,53 @@
const i18nEnglish = {
'appName': 'DietaryGuard',
'appDescription': 'Your Healthy Dietary Guard',
'appCopyright': 'Copyright © 2024 Solsynth LLC\nOriginal Author @littlesheep',
'query': 'Query',
'settings': 'Settings',
'preparingData': 'Preparing data...',
'settingsApplied': 'Settings Applied',
'preparingData': 'Preparing data',
'settingsApplied': 'Settings applied',
'settingsDataSection': 'Data Source',
'settingsAlertSection': 'Alert',
'dataSourceSettings': 'Data Source Settings',
'alertSettings': 'Alert Configuration',
'newAlert': 'New Alert',
'apply': 'Apply',
'searchHistoryNotIncluded': 'Search History not Included, yet',
'searchHistoryNotIncluded': 'Search history not implemented yet',
'nutrients': 'Nutrients',
'alertNutrientId': 'Nutrient ID',
'alertMaxValue': 'Max',
'alertMinValue': 'Min',
'alertMaxValue': 'Alert Max Value',
'alertMinValue': 'Alert Min Value',
'alertName': 'Alert Name',
'alerts': 'Alerts',
'alertOutOfRange':
'@count alert rules triggered, click the button on the right for details',
'alertUnclear':
'@count alert rules lack supporting data, click the button on the right for details',
'alertEmpty': 'No alert rules configured, go to settings to add rules',
'alertSafe': 'No alert rules triggered, safe to consume',
'alertDetectResult': 'Alert Match Details',
'undetected': 'Not Detected',
'dataCollectionSelection': 'Search Data Collection Range',
'dataCollectionFoundation': 'Foundation Data Collection',
'dataCollectionFoundationDescription':
'Includes food raw materials, with a smaller range but comprehensive data accuracy',
'dataCollectionBranded': 'Branded Data Collection',
'dataCollectionBrandedDescription':
'Includes various branded processed foods',
'dataCollectionSurvey': 'Survey Data Collection',
'dataCollectionSurveyDescription':
'Uses data from the "What We Eat in America" journal',
'dataCollectionLegacy': 'Legacy Data Collection',
'dataCollectionLegacyDescription':
'Historical data from analysis, calculations, and public literature',
'fdcApiKey': 'USDA Food Data Central API Key',
'fdcApiKeyHint':
'DietaryGuards data comes from the USDA public API, so you need to configure an API key. But dont worry, its completely free! Check our wiki to learn how to get an API key.',
'fdcApiCredit':
'DietaryGuards food data comes from the U.S. Department of Agriculture, Agricultural Research Service, Beltsville Human Nutrition Research Center. FoodData Central. We appreciate their generous contribution of food data released into the public domain.',
'loading': 'Loading',
'searchHint': 'Type keywords below to search',
'searchNoKeyHint':
'No API key, please first set up the API key in "Settings" > "Data Source"',
'searchResultHint': '@count records matched (showing 100)',
};

View File

@@ -1,10 +1,15 @@
const i18nSimplifiedChinese = {
'appName': '膳食卫士',
'appDescription': '您的健康膳食小卫士',
'appCopyright': '版权所有 © 2024 索尔辛茨实业有限公司\n作者 @littlesheep',
'query': '查询',
'settings': '设置',
'preparingData': '准备数据中…',
'settingsApplied': '设置已应用',
'settingsDataSection': '数据源',
'settingsAlertSection': '危险告警',
'dataSourceSettings': '数据源设置',
'alertSettings': '危险告警配置',
'newAlert': '新告警',
'apply': '应用',
'searchHistoryNotIncluded': '搜索记录还没实现',
@@ -12,10 +17,30 @@ const i18nSimplifiedChinese = {
'alertNutrientId': '营养物质编号',
'alertMaxValue': '告警上限',
'alertMinValue': '告警下限',
'alertName': '告警规则名',
'alerts': '告警',
'alertOutOfRange': '有 @count 项告警规则触发,点击右侧按钮了解详情',
'alertUnclear': '有 @count 项告警规则并无数据支持,点击右侧按钮了解详情',
'alertEmpty': '无告警规则配置,前往设置添加规则',
'alertSafe': '无告警规则触发,可安心食用',
'alertDetectResult': '告警匹配详情',
'undetected': '未检出',
'dataCollectionSelection': '搜索数据集范围',
'dataCollectionFoundation': '基础数据集',
'dataCollectionFoundationDescription': '包含食品原材料等,范围较小,但数据准确检测方面全',
'dataCollectionBranded': '品牌数据集',
'dataCollectionBrandedDescription': '包含各种品牌精加工食品',
'dataCollectionSurvey': '调查数据集',
'dataCollectionSurveyDescription': '使用在 We Eat in America 期刊上的数据',
'dataCollectionLegacy': '归档数据集',
'dataCollectionLegacyDescription': '来自分析、计算和公开文献的历史数据',
'fdcApiKey': 'USDA 食品数据中心 API 令牌',
'fdcApiKeyHint':
'DietaryGuard 的数据来自于美国农业部公开 API因此你需要配置一个 API 令牌,但是别担心,这是完全免费的,查看我们的维基了解如何获取一个 API 令牌。',
'fdcApiCredit':
'DietaryGuard 的食品数据来源于 U.S. Department of Agriculture, Agricultural Research Service, Beltsville Human Nutrition Research Center. FoodData Central. 在此感谢他们慷慨贡献的食品数据并发布在公有领域。',
'loading': '加载中',
'searchHint': '在下方键入关键词来搜索',
'searchNoKeyHint': '无 API 令牌,请先在「设置」>「数据源」设置 API 密钥',
'searchResultHint': '共命中 @count 条记录(显示前 100 条)',
};

View File

@@ -60,13 +60,13 @@ class AlertDetectResultDialog extends StatelessWidget {
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(item.name),
Text(item.config.name),
const SizedBox(width: 6),
Badge(label: Text('#${item.config.nutrientId}'))
],
),
subtitle: Text(
'${item.current} ${(item.difference ?? 0) > 0 ? '' : ''}${item.difference?.abs()} ${item.unitName}',
'${item.nutrient?.value ?? 'undetected'.tr} ${(item.difference ?? 0) > 0 ? '' : ''}${item.difference?.abs().toPrecision(2) ?? '-'} ${item.unitName ?? ''}',
),
);
},

View File

@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -5,10 +5,16 @@
import FlutterMacOS
import Foundation
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -1,68 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
"info": {
"version": 1,
"author": "xcode"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
"images": [
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_16.png",
"scale": "1x"
},
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "2x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "1x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_64.png",
"scale": "2x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_128.png",
"scale": "1x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "2x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "1x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "2x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "1x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_1024.png",
"scale": "2x"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -9,6 +9,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.11"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
async:
dependency: transitive
description:
@@ -33,6 +49,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@@ -49,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.5"
cupertino_icons:
dependency: "direct main"
description:
@@ -73,6 +113,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1
url: "https://pub.dev"
source: hosted
version: "2.3.9"
fake_async:
dependency: transitive
description:
@@ -102,6 +150,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -136,6 +192,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.2.3"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
http:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_parser:
dependency: transitive
description:
@@ -144,6 +216,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
intl:
dependency: "direct main"
description:
@@ -152,6 +232,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
@@ -216,6 +304,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.15.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev"
source: hosted
version: "8.0.2"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
url: "https://pub.dev"
source: hosted
version: "3.0.1"
path:
dependency: transitive
description:
@@ -224,6 +328,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
url: "https://pub.dev"
source: hosted
version: "2.2.10"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
@@ -248,6 +376,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
@@ -405,6 +541,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
url: "https://pub.dev"
source: hosted
version: "6.3.9"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
vector_math:
dependency: transitive
description:
@@ -429,6 +629,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
win32:
dependency: transitive
description:
name: win32
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.4"
xdg_directories:
dependency: transitive
description:
@@ -437,6 +645,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.22.0"

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 1.0.0+2
environment:
sdk: ^3.5.0
@@ -42,6 +42,10 @@ dependencies:
dio: ^5.6.0
intl: ^0.19.0
animations: ^2.0.11
dropdown_button2: ^2.3.9
package_info_plus: ^8.0.2
google_fonts: ^6.2.1
url_launcher: ^6.3.0
dev_dependencies:
flutter_test:
@@ -53,10 +57,27 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_launcher_icons: ^0.13.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "assets/icon.png"
min_sdk_android: 21 # android min sdk min:16, default 21
web:
generate: true
image_path: "assets/icon.png"
windows:
generate: true
image_path: "assets/icon.png"
icon_size: 256
macos:
generate: true
image_path: "assets/icon.png"
# The following section is specific to Flutter packages.
flutter:
@@ -66,8 +87,8 @@ flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - assets/data/
assets:
- assets/icon.png
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -32,4 +32,4 @@
"purpose": "maskable"
}
]
}
}

View File

@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 10 KiB