Compare commits
8 Commits
09e904f52f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 58578c734e | |||
| 76f2c0ad58 | |||
| cfa61e1a8a | |||
| 22b863f2bf | |||
| 4569c33430 | |||
| bd50859e73 | |||
| 0b34a4b74b | |||
| 403639088f |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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++";
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 521 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -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',
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
167
lib/screens/settings/alert.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
188
lib/screens/settings/data_source.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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':
|
||||
'DietaryGuard’s data comes from the USDA public API, so you need to configure an API key. But don’t worry, it’s completely free! Check our wiki to learn how to get an API key.',
|
||||
'fdcApiCredit':
|
||||
'DietaryGuard’s 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)',
|
||||
};
|
||||
|
||||
@@ -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 条)',
|
||||
};
|
||||
|
||||
@@ -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 ?? ''}',
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.9 KiB |
224
pubspec.lock
@@ -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"
|
||||
|
||||
27
pubspec.yaml
@@ -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
|
||||
|
||||
BIN
web/favicon.png
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
@@ -32,4 +32,4 @@
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 10 KiB |