Introduce PageView with manual switching of dates
1. Show PerDate widgets inside of an PageView 2. Introduce GoRouter so we can intercept back button taps with BackButtonListener 3. Implement rudimentary navigation 4. Fix bug that still showed a spinner event when the barcode was not found.
This commit is contained in:
parent
970cea8ba7
commit
cb18e1d1f0
@ -94,15 +94,25 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
}
|
||||
|
||||
if (response.status == FoodFactResponseStatus.barcodeNotFound) {
|
||||
List<FoodEntryState> listWithEntryRemoved =
|
||||
List.from(state.foodEntries);
|
||||
listWithEntryRemoved
|
||||
.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
|
||||
emit(PageState(
|
||||
foodEntries: state.foodEntries,
|
||||
foodEntries: listWithEntryRemoved,
|
||||
errorString: "Barcode konnte nicht gefunden werden."));
|
||||
return;
|
||||
}
|
||||
if (response.status ==
|
||||
FoodFactResponseStatus.foodFactServerNotReachable) {
|
||||
List<FoodEntryState> listWithEntryRemoved =
|
||||
List.from(state.foodEntries);
|
||||
listWithEntryRemoved
|
||||
.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
|
||||
emit(PageState(
|
||||
foodEntries: state.foodEntries,
|
||||
foodEntries: listWithEntryRemoved,
|
||||
errorString: "OpenFoodFacts-Server konnte nicht erreicht werden."));
|
||||
return;
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
import 'package:calorimeter/perdate/perdate_widget.dart';
|
||||
import 'package:calorimeter/perdate/perdate_pageview.dart';
|
||||
import 'package:calorimeter/storage/storage.dart';
|
||||
import 'package:calorimeter/utils/settings_bloc.dart';
|
||||
import 'package:calorimeter/utils/theme_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
var storage = await FoodStorage.create();
|
||||
await storage.buildFoodLookupDatabase();
|
||||
var kcalLimit = await storage.readLimit();
|
||||
@ -56,8 +60,25 @@ class MainApp extends StatelessWidget {
|
||||
newBrightness = Brightness.dark;
|
||||
}
|
||||
|
||||
return MaterialApp(
|
||||
home: PerDateWidget(date: DateTime.now()),
|
||||
return MaterialApp.router(
|
||||
routerConfig: GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) {
|
||||
return PerDatePageview(
|
||||
initalDate: DateTime.now().copyWith(
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
microsecond: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
|
76
lib/perdate/perdate_pageview.dart
Normal file
76
lib/perdate/perdate_pageview.dart
Normal file
@ -0,0 +1,76 @@
|
||||
import 'package:calorimeter/perdate/perdate_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PerDatePageview extends StatefulWidget {
|
||||
// this is the date for which the PerDate widget will be shown on screen
|
||||
// left of it will be yesterday's PerDate widget
|
||||
// right of it will be tomorrow's PerDate widget
|
||||
final DateTime initalDate;
|
||||
const PerDatePageview({required this.initalDate, super.key});
|
||||
|
||||
@override
|
||||
State<PerDatePageview> createState() => _PerDatePageviewState();
|
||||
}
|
||||
|
||||
class _PerDatePageviewState extends State<PerDatePageview> {
|
||||
late PageController pageController;
|
||||
late DateTime displayedDate;
|
||||
late List<int> visitedIndexes = [];
|
||||
final int initialOffset = 36500000;
|
||||
|
||||
//TODO: that is just ugly
|
||||
bool backButtonWasPressed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
pageController = PageController(initialPage: initialOffset);
|
||||
displayedDate = widget.initalDate;
|
||||
visitedIndexes.add(initialOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BackButtonListener(
|
||||
onBackButtonPressed: () async {
|
||||
if (visitedIndexes.length == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visitedIndexes.removeLast();
|
||||
|
||||
backButtonWasPressed = true;
|
||||
pageController.jumpToPage(visitedIndexes.last);
|
||||
|
||||
return true;
|
||||
},
|
||||
child: PageView.builder(
|
||||
reverse: true,
|
||||
controller: pageController,
|
||||
onPageChanged: (value) {
|
||||
if (backButtonWasPressed) {
|
||||
backButtonWasPressed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
visitedIndexes.add(value);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
var dateToBuildWidgetFor =
|
||||
displayedDate.subtract(Duration(days: index - initialOffset));
|
||||
|
||||
return PerDateWidget(
|
||||
key: ValueKey(dateToBuildWidgetFor.toString()),
|
||||
date: dateToBuildWidgetFor,
|
||||
onDateSelected: (dateSelected) {
|
||||
if (dateSelected == null) return;
|
||||
|
||||
var diff = dateSelected.difference(dateToBuildWidgetFor);
|
||||
var newIndex = index - diff.inDays;
|
||||
|
||||
pageController.jumpToPage(newIndex);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -15,13 +15,16 @@ import 'package:provider/provider.dart';
|
||||
|
||||
class PerDateWidget extends StatefulWidget {
|
||||
final DateTime date;
|
||||
const PerDateWidget({super.key, required this.date});
|
||||
final Function(DateTime?) onDateSelected;
|
||||
const PerDateWidget(
|
||||
{super.key, required this.date, required this.onDateSelected});
|
||||
|
||||
@override
|
||||
State<PerDateWidget> createState() => _PerDateWidgetState();
|
||||
}
|
||||
|
||||
class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
class _PerDateWidgetState extends State<PerDateWidget>
|
||||
with AutomaticKeepAliveClientMixin<PerDateWidget> {
|
||||
late FoodStorage storage;
|
||||
late Future<List<FoodEntryState>> entriesFuture;
|
||||
List<FoodEntryState> entries = [];
|
||||
@ -36,8 +39,15 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return FutureBuilder(
|
||||
future: entriesFuture,
|
||||
builder: (context, snapshot) {
|
||||
@ -85,7 +95,7 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
CalendarFloatingButton(
|
||||
startFromDate: widget.date,
|
||||
onDateSelected: (dateSelected) {
|
||||
_onDateSelected(dateSelected);
|
||||
widget.onDateSelected(dateSelected);
|
||||
},
|
||||
),
|
||||
]),
|
||||
@ -106,26 +116,8 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
..showSnackBar(snackbar);
|
||||
}
|
||||
|
||||
void _onDateSelected(DateTime date) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return PerDateWidget(date: date);
|
||||
},
|
||||
),
|
||||
).then((val) {
|
||||
setState(
|
||||
() {
|
||||
entriesFuture = storage.getEntriesForDate(widget.date);
|
||||
entriesFuture.then(
|
||||
(val) {
|
||||
entries = val;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
class ErrorSnackbar extends SnackBar {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||
@ -171,13 +170,11 @@ class FoodStorage {
|
||||
|
||||
for (var entry in entriesForDate) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||
}
|
||||
}
|
||||
|
||||
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||
}
|
||||
|
||||
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
|
@ -1,215 +0,0 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class FoodStorage {
|
||||
static late FoodStorage _instance;
|
||||
late String path;
|
||||
final Map<String, int> _foodLookupDatabase = {};
|
||||
|
||||
FoodStorage._create();
|
||||
|
||||
static Future<FoodStorage> create() async {
|
||||
var storage = FoodStorage._create();
|
||||
|
||||
Directory dir = Directory('');
|
||||
|
||||
if (UniversalPlatform.isDesktop) {
|
||||
dir = await getApplicationCacheDirectory();
|
||||
} else if (UniversalPlatform.isAndroid) {
|
||||
dir = await getApplicationDocumentsDirectory();
|
||||
}
|
||||
|
||||
storage.path = dir.path;
|
||||
_instance = storage;
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static FoodStorage getInstance() => _instance;
|
||||
|
||||
Future<List<FoodEntryState>> getEntriesForDate(DateTime date) async {
|
||||
List<FoodEntryState> entries = [];
|
||||
var filePath = '$path/${date.year}/${date.month}/${date.day}';
|
||||
|
||||
var file = File(filePath);
|
||||
var exists = await file.exists();
|
||||
|
||||
if (!exists) return [];
|
||||
|
||||
var lines = await file.readAsLines();
|
||||
|
||||
for (var line in lines) {
|
||||
<<<<<<< HEAD
|
||||
var fields = line.splitWithIgnore(',', ignoreIn: '"');
|
||||
var entry = FoodEntry(
|
||||
name: fields[1].replaceAll('"', ""),
|
||||
mass: double.parse(fields[2]),
|
||||
kcalPerMass: double.parse(fields[3]));
|
||||
=======
|
||||
var fields = line.split(',');
|
||||
var entry = FoodEntryState(
|
||||
name: fields[1],
|
||||
mass: int.parse(fields[2]),
|
||||
kcalPerMass: int.parse(fields[3]),
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
>>>>>>> 7921f09 (wip)
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
Future<void> writeEntriesForDate(
|
||||
DateTime date, List<FoodEntryState> foodEntries) async {
|
||||
var filePath = '$path/${date.year}/${date.month}/${date.day}';
|
||||
var file = File(filePath);
|
||||
|
||||
var exists = await file.exists();
|
||||
|
||||
if (!exists) {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
|
||||
String fullString = '';
|
||||
for (var entry in foodEntries) {
|
||||
fullString += '${entry.toString()}\n';
|
||||
}
|
||||
|
||||
await file.writeAsString(fullString);
|
||||
}
|
||||
|
||||
Future<void> updateLimit(double limit) async {
|
||||
var filePath = '$path/limit';
|
||||
var file = File(filePath);
|
||||
|
||||
var exists = await file.exists();
|
||||
if (!exists) {
|
||||
await file.create();
|
||||
}
|
||||
|
||||
await file.writeAsString(limit.toString());
|
||||
}
|
||||
|
||||
Future<double> readLimit() async {
|
||||
var filePath = '$path/limit';
|
||||
var file = File(filePath);
|
||||
var exists = await file.exists();
|
||||
|
||||
if (!exists) {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
var line = await file.readAsLines();
|
||||
|
||||
double limit;
|
||||
try {
|
||||
limit = double.parse(line[0]);
|
||||
} catch (e) {
|
||||
limit = 2000;
|
||||
}
|
||||
|
||||
return limit;
|
||||
}
|
||||
|
||||
Future<String> readBrightness() async {
|
||||
var filePath = '$path/brightness';
|
||||
var file = File(filePath);
|
||||
var exists = await file.exists();
|
||||
|
||||
if (!exists) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
var line = await file.readAsLines();
|
||||
|
||||
if (line.isEmpty || (line[0] != 'dark' && line[0] != 'light')) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
return line[0];
|
||||
}
|
||||
|
||||
Future<void> writeBrightness(String brightness) async {
|
||||
var filePath = '$path/brightness';
|
||||
var file = File(filePath);
|
||||
var exists = await file.exists();
|
||||
|
||||
if (!exists) {
|
||||
file.create();
|
||||
}
|
||||
|
||||
await file.writeAsString(brightness);
|
||||
}
|
||||
|
||||
Future<void> buildFoodLookupDatabase() async {
|
||||
// get a list of dates of the last 365 days
|
||||
var dates = List<DateTime>.generate(365, (idx) {
|
||||
var durationToPast = Duration(days: idx);
|
||||
return DateTime.now().subtract(durationToPast);
|
||||
});
|
||||
|
||||
for (var date in dates.reversed) {
|
||||
addFoodEntryToLookupDatabaseFor(date);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addFoodEntryToLookupDatabaseFor(DateTime date) async {
|
||||
var entriesForDate = await getEntriesForDate(date);
|
||||
|
||||
for (var entry in entriesForDate) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
}
|
||||
}
|
||||
|
||||
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
}
|
||||
|
||||
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
}
|
||||
|
||||
extension SplitWithIgnore on String {
|
||||
List<String> splitWithIgnore(String delimiter, {String? ignoreIn}) {
|
||||
List<String> parts = [];
|
||||
|
||||
if (ignoreIn == null) {
|
||||
return split(delimiter);
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
int indexCharAfterDelimiter = 0;
|
||||
bool inIgnore = false;
|
||||
for (var rune in runes) {
|
||||
var char = String.fromCharCode(rune);
|
||||
|
||||
index += 1;
|
||||
|
||||
if (char == ignoreIn) {
|
||||
inIgnore = !inIgnore;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inIgnore) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char == delimiter) {
|
||||
parts.add(substring(indexCharAfterDelimiter, index));
|
||||
indexCharAfterDelimiter = index + 1;
|
||||
}
|
||||
|
||||
if (index + 1 == length) {
|
||||
parts.add(substring(indexCharAfterDelimiter, length));
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class CalendarFloatingButton extends StatelessWidget {
|
||||
final DateTime startFromDate;
|
||||
final Function(DateTime) onDateSelected;
|
||||
final Function(DateTime?) onDateSelected;
|
||||
|
||||
const CalendarFloatingButton(
|
||||
{super.key, required this.startFromDate, required this.onDateSelected});
|
||||
@ -17,12 +17,12 @@ class CalendarFloatingButton extends StatelessWidget {
|
||||
initialDate: startFromDate,
|
||||
currentDate: DateTime.now(),
|
||||
firstDate: DateTime.now().subtract(const Duration(days: 365 * 10)),
|
||||
lastDate: DateTime.now(),
|
||||
lastDate: DateTime.now().add(const Duration(days: 365 * 10)),
|
||||
);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
onDateSelected(datePicked ?? DateTime.now());
|
||||
onDateSelected(datePicked);
|
||||
},
|
||||
heroTag: "calendarFAB",
|
||||
child: const Icon(Icons.today),
|
||||
|
47
pubspec.lock
47
pubspec.lock
@ -183,18 +183,18 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||
sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
version: "0.14.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -205,6 +205,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -221,6 +226,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -305,10 +318,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -353,10 +366,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "2.0.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -401,10 +414,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||
sha256: f7544c346a0742aee1450f9e5c0f5269d7c602b9c95fdbcd9fb8f5b1df13b1cc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.10"
|
||||
version: "2.2.11"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -646,10 +659,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.5.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -678,10 +691,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -710,10 +723,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -732,4 +745,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.5.3 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
@ -20,12 +20,13 @@ dependencies:
|
||||
barcode_scan2: ^4.3.3
|
||||
provider: ^6.1.2
|
||||
test: ^1.25.7
|
||||
go_router: ^14.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^4.0.0
|
||||
flutter_launcher_icons: "^0.13.1"
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
Loading…
Reference in New Issue
Block a user