✨ Setup data
This commit is contained in:
parent
cb011ddcf9
commit
7797c1b635
File diff suppressed because one or more lines are too long
@ -3,20 +3,27 @@ PODS:
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
|
||||
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
|
||||
|
||||
|
46
lib/controllers/food_data.dart
Normal file
46
lib/controllers/food_data.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:dietary_guard/models/food_data.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class FoodDataController extends GetxController {
|
||||
RxBool isReady = false.obs;
|
||||
|
||||
late final SharedPreferences _prefs;
|
||||
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
if (isReady.value) return;
|
||||
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
|
||||
isReady.value = true;
|
||||
}
|
||||
|
||||
String? getApiKey() {
|
||||
return _prefs.getString("data_fdc_api_key");
|
||||
}
|
||||
|
||||
Future<void> setApiKey(String value) async {
|
||||
await _prefs.setString("data_fdc_api_key", 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,
|
||||
'pageNumber': 1,
|
||||
'sortBy': 'dataType.keyword',
|
||||
'sortOrder': 'asc',
|
||||
'api_key': getApiKey(),
|
||||
},
|
||||
);
|
||||
|
||||
final result = FoodDataQueryResponse.fromJson(resp.data);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
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/shells/nav_shell.dart';
|
||||
import 'package:dietary_guard/translations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -42,9 +44,24 @@ class MyApp extends StatelessWidget {
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
themeMode: ThemeMode.system,
|
||||
translations: AppTranslations(),
|
||||
onInit: () => _initializeProviders(context),
|
||||
);
|
||||
}
|
||||
|
||||
void _initializeProviders(BuildContext context) async {
|
||||
Get.put(FoodDataController());
|
||||
}
|
||||
}
|
||||
|
402
lib/models/food_data.dart
Normal file
402
lib/models/food_data.dart
Normal file
@ -0,0 +1,402 @@
|
||||
class FoodDataQueryResponse {
|
||||
int totalHits;
|
||||
int currentPage;
|
||||
int totalPages;
|
||||
List<int> pageList;
|
||||
FoodSearchCriteria foodSearchCriteria;
|
||||
List<FoodData> foods;
|
||||
Aggregations aggregations;
|
||||
|
||||
FoodDataQueryResponse({
|
||||
required this.totalHits,
|
||||
required this.currentPage,
|
||||
required this.totalPages,
|
||||
required this.pageList,
|
||||
required this.foodSearchCriteria,
|
||||
required this.foods,
|
||||
required this.aggregations,
|
||||
});
|
||||
|
||||
factory FoodDataQueryResponse.fromJson(Map<String, dynamic> json) =>
|
||||
FoodDataQueryResponse(
|
||||
totalHits: json["totalHits"],
|
||||
currentPage: json["currentPage"],
|
||||
totalPages: json["totalPages"],
|
||||
pageList: List<int>.from(json["pageList"].map((x) => x)),
|
||||
foodSearchCriteria:
|
||||
FoodSearchCriteria.fromJson(json["foodSearchCriteria"]),
|
||||
foods:
|
||||
List<FoodData>.from(json["foods"].map((x) => FoodData.fromJson(x))),
|
||||
aggregations: Aggregations.fromJson(json["aggregations"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"totalHits": totalHits,
|
||||
"currentPage": currentPage,
|
||||
"totalPages": totalPages,
|
||||
"pageList": List<dynamic>.from(pageList.map((x) => x)),
|
||||
"foodSearchCriteria": foodSearchCriteria.toJson(),
|
||||
"foods": List<dynamic>.from(foods.map((x) => x.toJson())),
|
||||
"aggregations": aggregations.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class Aggregations {
|
||||
DataType dataType;
|
||||
Nutrients nutrients;
|
||||
|
||||
Aggregations({
|
||||
required this.dataType,
|
||||
required this.nutrients,
|
||||
});
|
||||
|
||||
factory Aggregations.fromJson(Map<String, dynamic> json) => Aggregations(
|
||||
dataType: DataType.fromJson(json["dataType"]),
|
||||
nutrients: Nutrients.fromJson(json["nutrients"]),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"dataType": dataType.toJson(),
|
||||
"nutrients": nutrients.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class DataType {
|
||||
int branded;
|
||||
int surveyFndds;
|
||||
int srLegacy;
|
||||
int foundation;
|
||||
|
||||
DataType({
|
||||
required this.branded,
|
||||
required this.surveyFndds,
|
||||
required this.srLegacy,
|
||||
required this.foundation,
|
||||
});
|
||||
|
||||
factory DataType.fromJson(Map<String, dynamic> json) => DataType(
|
||||
branded: json["Branded"],
|
||||
surveyFndds: json["Survey (FNDDS)"],
|
||||
srLegacy: json["SR Legacy"],
|
||||
foundation: json["Foundation"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"Branded": branded,
|
||||
"Survey (FNDDS)": surveyFndds,
|
||||
"SR Legacy": srLegacy,
|
||||
"Foundation": foundation,
|
||||
};
|
||||
}
|
||||
|
||||
class Nutrients {
|
||||
Nutrients();
|
||||
|
||||
factory Nutrients.fromJson(Map<String, dynamic> json) => Nutrients();
|
||||
|
||||
Map<String, dynamic> toJson() => {};
|
||||
}
|
||||
|
||||
class FoodSearchCriteria {
|
||||
List<Type> dataType;
|
||||
String query;
|
||||
String generalSearchInput;
|
||||
int pageNumber;
|
||||
String sortBy;
|
||||
String sortOrder;
|
||||
int numberOfResultsPerPage;
|
||||
int pageSize;
|
||||
bool requireAllWords;
|
||||
List<Type> foodTypes;
|
||||
|
||||
FoodSearchCriteria({
|
||||
required this.dataType,
|
||||
required this.query,
|
||||
required this.generalSearchInput,
|
||||
required this.pageNumber,
|
||||
required this.sortBy,
|
||||
required this.sortOrder,
|
||||
required this.numberOfResultsPerPage,
|
||||
required this.pageSize,
|
||||
required this.requireAllWords,
|
||||
required this.foodTypes,
|
||||
});
|
||||
|
||||
factory FoodSearchCriteria.fromJson(Map<String, dynamic> json) =>
|
||||
FoodSearchCriteria(
|
||||
dataType:
|
||||
List<Type>.from(json["dataType"].map((x) => typeValues.map[x]!)),
|
||||
query: json["query"],
|
||||
generalSearchInput: json["generalSearchInput"],
|
||||
pageNumber: json["pageNumber"],
|
||||
sortBy: json["sortBy"],
|
||||
sortOrder: json["sortOrder"],
|
||||
numberOfResultsPerPage: json["numberOfResultsPerPage"],
|
||||
pageSize: json["pageSize"],
|
||||
requireAllWords: json["requireAllWords"],
|
||||
foodTypes:
|
||||
List<Type>.from(json["foodTypes"].map((x) => typeValues.map[x]!)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"dataType":
|
||||
List<dynamic>.from(dataType.map((x) => typeValues.reverse[x])),
|
||||
"query": query,
|
||||
"generalSearchInput": generalSearchInput,
|
||||
"pageNumber": pageNumber,
|
||||
"sortBy": sortBy,
|
||||
"sortOrder": sortOrder,
|
||||
"numberOfResultsPerPage": numberOfResultsPerPage,
|
||||
"pageSize": pageSize,
|
||||
"requireAllWords": requireAllWords,
|
||||
"foodTypes":
|
||||
List<dynamic>.from(foodTypes.map((x) => typeValues.reverse[x])),
|
||||
};
|
||||
}
|
||||
|
||||
enum Type { FOUNDATION, SR_LEGACY }
|
||||
|
||||
final typeValues =
|
||||
EnumValues({"Foundation": Type.FOUNDATION, "SR Legacy": Type.SR_LEGACY});
|
||||
|
||||
class FoodData {
|
||||
int fdcId;
|
||||
String description;
|
||||
String commonNames;
|
||||
String additionalDescriptions;
|
||||
Type dataType;
|
||||
int ndbNumber;
|
||||
DateTime publishedDate;
|
||||
FoodCategory? foodCategory;
|
||||
DateTime? mostRecentAcquisitionDate;
|
||||
String allHighlightFields;
|
||||
double score;
|
||||
List<dynamic> microbes;
|
||||
List<FoodNutrient> foodNutrients;
|
||||
List<dynamic> finalFoodInputFoods;
|
||||
List<dynamic> foodMeasures;
|
||||
List<dynamic> foodAttributes;
|
||||
List<dynamic> foodAttributeTypes;
|
||||
List<dynamic> foodVersionIds;
|
||||
|
||||
FoodData({
|
||||
required this.fdcId,
|
||||
required this.description,
|
||||
required this.commonNames,
|
||||
required this.additionalDescriptions,
|
||||
required this.dataType,
|
||||
required this.ndbNumber,
|
||||
required this.publishedDate,
|
||||
required this.foodCategory,
|
||||
this.mostRecentAcquisitionDate,
|
||||
required this.allHighlightFields,
|
||||
required this.score,
|
||||
required this.microbes,
|
||||
required this.foodNutrients,
|
||||
required this.finalFoodInputFoods,
|
||||
required this.foodMeasures,
|
||||
required this.foodAttributes,
|
||||
required this.foodAttributeTypes,
|
||||
required this.foodVersionIds,
|
||||
});
|
||||
|
||||
factory FoodData.fromJson(Map<String, dynamic> json) => FoodData(
|
||||
fdcId: json["fdcId"],
|
||||
description: json["description"],
|
||||
commonNames: json["commonNames"],
|
||||
additionalDescriptions: json["additionalDescriptions"],
|
||||
dataType: typeValues.map[json["dataType"]]!,
|
||||
ndbNumber: json["ndbNumber"],
|
||||
publishedDate: DateTime.parse(json["publishedDate"]),
|
||||
foodCategory: foodCategoryValues.map[json["foodCategory"]],
|
||||
mostRecentAcquisitionDate: json["mostRecentAcquisitionDate"] == null
|
||||
? null
|
||||
: DateTime.parse(json["mostRecentAcquisitionDate"]),
|
||||
allHighlightFields: json["allHighlightFields"],
|
||||
score: json["score"]?.toDouble(),
|
||||
microbes: List<dynamic>.from(json["microbes"].map((x) => x)),
|
||||
foodNutrients: List<FoodNutrient>.from(
|
||||
json["foodNutrients"].map((x) => FoodNutrient.fromJson(x))),
|
||||
finalFoodInputFoods:
|
||||
List<dynamic>.from(json["finalFoodInputFoods"].map((x) => x)),
|
||||
foodMeasures: List<dynamic>.from(json["foodMeasures"].map((x) => x)),
|
||||
foodAttributes:
|
||||
List<dynamic>.from(json["foodAttributes"].map((x) => x)),
|
||||
foodAttributeTypes:
|
||||
List<dynamic>.from(json["foodAttributeTypes"].map((x) => x)),
|
||||
foodVersionIds:
|
||||
List<dynamic>.from(json["foodVersionIds"].map((x) => x)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"fdcId": fdcId,
|
||||
"description": description,
|
||||
"commonNames": commonNames,
|
||||
"additionalDescriptions": additionalDescriptions,
|
||||
"dataType": typeValues.reverse[dataType],
|
||||
"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],
|
||||
"mostRecentAcquisitionDate":
|
||||
"${mostRecentAcquisitionDate!.year.toString().padLeft(4, '0')}-${mostRecentAcquisitionDate!.month.toString().padLeft(2, '0')}-${mostRecentAcquisitionDate!.day.toString().padLeft(2, '0')}",
|
||||
"allHighlightFields": allHighlightFields,
|
||||
"score": score,
|
||||
"microbes": List<dynamic>.from(microbes.map((x) => x)),
|
||||
"foodNutrients":
|
||||
List<dynamic>.from(foodNutrients.map((x) => x.toJson())),
|
||||
"finalFoodInputFoods":
|
||||
List<dynamic>.from(finalFoodInputFoods.map((x) => x)),
|
||||
"foodMeasures": List<dynamic>.from(foodMeasures.map((x) => x)),
|
||||
"foodAttributes": List<dynamic>.from(foodAttributes.map((x) => x)),
|
||||
"foodAttributeTypes":
|
||||
List<dynamic>.from(foodAttributeTypes.map((x) => x)),
|
||||
"foodVersionIds": List<dynamic>.from(foodVersionIds.map((x) => x)),
|
||||
};
|
||||
}
|
||||
|
||||
enum FoodCategory { DAIRY_AND_EGG_PRODUCTS }
|
||||
|
||||
final foodCategoryValues =
|
||||
EnumValues({"Dairy and Egg Products": FoodCategory.DAIRY_AND_EGG_PRODUCTS});
|
||||
|
||||
class FoodNutrient {
|
||||
int nutrientId;
|
||||
String nutrientName;
|
||||
String nutrientNumber;
|
||||
UnitName unitName;
|
||||
DerivationCode? derivationCode;
|
||||
String? derivationDescription;
|
||||
int? derivationId;
|
||||
double? value;
|
||||
int? foodNutrientSourceId;
|
||||
String? foodNutrientSourceCode;
|
||||
FoodNutrientSourceDescription? foodNutrientSourceDescription;
|
||||
int rank;
|
||||
int indentLevel;
|
||||
int foodNutrientId;
|
||||
int? dataPoints;
|
||||
double? min;
|
||||
double? max;
|
||||
double? median;
|
||||
|
||||
FoodNutrient({
|
||||
required this.nutrientId,
|
||||
required this.nutrientName,
|
||||
required this.nutrientNumber,
|
||||
required this.unitName,
|
||||
this.derivationCode,
|
||||
this.derivationDescription,
|
||||
this.derivationId,
|
||||
this.value,
|
||||
this.foodNutrientSourceId,
|
||||
this.foodNutrientSourceCode,
|
||||
this.foodNutrientSourceDescription,
|
||||
required this.rank,
|
||||
required this.indentLevel,
|
||||
required this.foodNutrientId,
|
||||
this.dataPoints,
|
||||
this.min,
|
||||
this.max,
|
||||
this.median,
|
||||
});
|
||||
|
||||
factory FoodNutrient.fromJson(Map<String, dynamic> json) => FoodNutrient(
|
||||
nutrientId: json["nutrientId"],
|
||||
nutrientName: json["nutrientName"],
|
||||
nutrientNumber: json["nutrientNumber"],
|
||||
unitName: unitNameValues.map[json["unitName"]]!,
|
||||
derivationCode: derivationCodeValues.map[json["derivationCode"]]!,
|
||||
derivationDescription: json["derivationDescription"],
|
||||
derivationId: json["derivationId"],
|
||||
value: json["value"]?.toDouble(),
|
||||
foodNutrientSourceId: json["foodNutrientSourceId"],
|
||||
foodNutrientSourceCode: json["foodNutrientSourceCode"],
|
||||
foodNutrientSourceDescription: foodNutrientSourceDescriptionValues
|
||||
.map[json["foodNutrientSourceDescription"]]!,
|
||||
rank: json["rank"],
|
||||
indentLevel: json["indentLevel"],
|
||||
foodNutrientId: json["foodNutrientId"],
|
||||
dataPoints: json["dataPoints"],
|
||||
min: json["min"]?.toDouble(),
|
||||
max: json["max"]?.toDouble(),
|
||||
median: json["median"]?.toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"nutrientId": nutrientId,
|
||||
"nutrientName": nutrientName,
|
||||
"nutrientNumber": nutrientNumber,
|
||||
"unitName": unitNameValues.reverse[unitName],
|
||||
"derivationCode": derivationCodeValues.reverse[derivationCode],
|
||||
"derivationDescription": derivationDescription,
|
||||
"derivationId": derivationId,
|
||||
"value": value,
|
||||
"foodNutrientSourceId": foodNutrientSourceId,
|
||||
"foodNutrientSourceCode": foodNutrientSourceCode,
|
||||
"foodNutrientSourceDescription": foodNutrientSourceDescriptionValues
|
||||
.reverse[foodNutrientSourceDescription],
|
||||
"rank": rank,
|
||||
"indentLevel": indentLevel,
|
||||
"foodNutrientId": foodNutrientId,
|
||||
"dataPoints": dataPoints,
|
||||
"min": min,
|
||||
"max": max,
|
||||
"median": median,
|
||||
};
|
||||
}
|
||||
|
||||
enum DerivationCode { A, AS, BFFN, BFNN, BFZN, CAZN, LC, NC, NR, T, Z }
|
||||
|
||||
final derivationCodeValues = EnumValues({
|
||||
"A": DerivationCode.A,
|
||||
"AS": DerivationCode.AS,
|
||||
"BFFN": DerivationCode.BFFN,
|
||||
"BFNN": DerivationCode.BFNN,
|
||||
"BFZN": DerivationCode.BFZN,
|
||||
"CAZN": DerivationCode.CAZN,
|
||||
"LC": DerivationCode.LC,
|
||||
"NC": DerivationCode.NC,
|
||||
"NR": DerivationCode.NR,
|
||||
"T": DerivationCode.T,
|
||||
"Z": DerivationCode.Z
|
||||
});
|
||||
|
||||
enum FoodNutrientSourceDescription {
|
||||
ANALYTICAL_OR_DERIVED_FROM_ANALYTICAL,
|
||||
ASSUMED_ZERO,
|
||||
CALCULATED_FROM_NUTRIENT_LABEL_BY_NDL,
|
||||
CALCULATED_OR_IMPUTED
|
||||
}
|
||||
|
||||
final foodNutrientSourceDescriptionValues = EnumValues({
|
||||
"Analytical or derived from analytical":
|
||||
FoodNutrientSourceDescription.ANALYTICAL_OR_DERIVED_FROM_ANALYTICAL,
|
||||
"Assumed zero": FoodNutrientSourceDescription.ASSUMED_ZERO,
|
||||
"Calculated from nutrient label by NDL":
|
||||
FoodNutrientSourceDescription.CALCULATED_FROM_NUTRIENT_LABEL_BY_NDL,
|
||||
"Calculated or imputed": FoodNutrientSourceDescription.CALCULATED_OR_IMPUTED
|
||||
});
|
||||
|
||||
enum UnitName { G, IU, KCAL, K_J, MG, UG }
|
||||
|
||||
final unitNameValues = EnumValues({
|
||||
"G": UnitName.G,
|
||||
"IU": UnitName.IU,
|
||||
"KCAL": UnitName.KCAL,
|
||||
"kJ": UnitName.K_J,
|
||||
"MG": UnitName.MG,
|
||||
"UG": UnitName.UG
|
||||
});
|
||||
|
||||
class EnumValues<T> {
|
||||
Map<String, T> map;
|
||||
late Map<T, String> reverseMap;
|
||||
|
||||
EnumValues(this.map);
|
||||
|
||||
Map<T, String> get reverse {
|
||||
reverseMap = map.map((k, v) => MapEntry(v, k));
|
||||
return reverseMap;
|
||||
}
|
||||
}
|
@ -1,10 +1,84 @@
|
||||
import 'package:dietary_guard/controllers/food_data.dart';
|
||||
import 'package:dietary_guard/models/food_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class QueryScreen extends StatelessWidget {
|
||||
class QueryScreen extends StatefulWidget {
|
||||
const QueryScreen({super.key});
|
||||
|
||||
@override
|
||||
State<QueryScreen> createState() => _QueryScreenState();
|
||||
}
|
||||
|
||||
class _QueryScreenState extends State<QueryScreen> {
|
||||
bool _isLoading = false;
|
||||
|
||||
int _totalCount = 0;
|
||||
List<FoodData> _foodData = List.empty();
|
||||
|
||||
Future<void> _searchFood(String probe) async {
|
||||
if (_isLoading) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
final FoodDataController data = Get.find();
|
||||
if (data.getApiKey() == null) return;
|
||||
|
||||
final result = await data.searchFood(probe);
|
||||
|
||||
setState(() {
|
||||
_totalCount = result.totalHits;
|
||||
_foodData = result.foods;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final FoodDataController data = Get.find();
|
||||
data.initialize(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox();
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
SearchBar(
|
||||
padding: const WidgetStatePropertyAll<EdgeInsets>(
|
||||
EdgeInsets.symmetric(horizontal: 16.0),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
_searchFood(value);
|
||||
},
|
||||
leading: const Icon(Icons.search),
|
||||
).paddingSymmetric(horizontal: 24),
|
||||
if (_isLoading)
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 3),
|
||||
).paddingSymmetric(vertical: 16)
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _foodData.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = _foodData[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text(item.description),
|
||||
subtitle: Text(
|
||||
DateFormat("yyyy-MM-dd").format(item.publishedDate),
|
||||
),
|
||||
);
|
||||
},
|
||||
).paddingOnly(top: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,70 @@
|
||||
import 'package:dietary_guard/controllers/food_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final TextEditingController _fdcApiKeyController = TextEditingController();
|
||||
|
||||
Future<void> _applySettings() async {
|
||||
final FoodDataController data = Get.find();
|
||||
await data.setApiKey(_fdcApiKeyController.text);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('settingsApplied'.tr),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final FoodDataController data = Get.find();
|
||||
_fdcApiKeyController.text = data.getApiKey() ?? '';
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fdcApiKeyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('settings'.tr),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _fdcApiKeyController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
label: Text("FDC API Key"),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
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,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class Destination {
|
||||
@ -21,8 +22,8 @@ class _NavShellState extends State<NavShell> {
|
||||
int _focusDestination = 0;
|
||||
|
||||
final List<Destination> _allDestinations = <Destination>[
|
||||
const Destination('Query', 'query', Icons.search),
|
||||
const Destination('Settings', 'settings', Icons.settings)
|
||||
Destination('query'.tr, 'query', Icons.search),
|
||||
Destination('settings'.tr, 'settings', Icons.settings)
|
||||
];
|
||||
|
||||
@override
|
||||
|
11
lib/translations.dart
Normal file
11
lib/translations.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:dietary_guard/translations/en_us.dart';
|
||||
import 'package:dietary_guard/translations/zh_cn.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AppTranslations extends Translations {
|
||||
@override
|
||||
Map<String, Map<String, String>> get keys => {
|
||||
'en_US': i18nEnglish,
|
||||
'zh_CN': i18nSimplifiedChinese,
|
||||
};
|
||||
}
|
8
lib/translations/en_us.dart
Normal file
8
lib/translations/en_us.dart
Normal file
@ -0,0 +1,8 @@
|
||||
const i18nEnglish = {
|
||||
'query': 'Query',
|
||||
'settings': 'Settings',
|
||||
'preparingData': 'Preparing data...',
|
||||
'settingsApplied': 'Settings Applied',
|
||||
'apply': 'Apply',
|
||||
'searchHistoryNotIncluded': 'Search History not Included, yet',
|
||||
};
|
8
lib/translations/zh_cn.dart
Normal file
8
lib/translations/zh_cn.dart
Normal file
@ -0,0 +1,8 @@
|
||||
const i18nSimplifiedChinese = {
|
||||
'query': '查询',
|
||||
'settings': '设置',
|
||||
'preparingData': '准备数据中…',
|
||||
'settingsApplied': '设置已应用',
|
||||
'apply': '应用',
|
||||
'searchHistoryNotIncluded': '搜索记录还没实现',
|
||||
};
|
@ -6,7 +6,9 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import shared_preferences_foundation
|
||||
import sqflite
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
}
|
||||
|
64
pubspec.lock
64
pubspec.lock
@ -49,6 +49,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.6.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -112,6 +128,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.3"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -293,6 +325,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3+1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: c5e5b2a142a893a752cb36ae5888680248686725a54afceff31f9a3a76bc53c2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -317,6 +365,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -333,6 +389,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -38,6 +38,9 @@ dependencies:
|
||||
get: ^4.6.6
|
||||
go_router: ^14.2.3
|
||||
shared_preferences: ^2.3.1
|
||||
sqflite: ^2.3.3+1
|
||||
dio: ^5.6.0
|
||||
intl: ^0.19.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -62,8 +65,8 @@ flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/data/
|
||||
# assets:
|
||||
# - assets/data/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
Loading…
Reference in New Issue
Block a user