✨ Setup data
This commit is contained in:
		
							
								
								
									
										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': '搜索记录还没实现', | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user