Scanner popup + list config + check internet

This commit is contained in:
Fransolet Thomas 2022-08-18 18:12:45 +02:00
parent 00a42010b4
commit 5ce0ec4f80
12 changed files with 298 additions and 170 deletions

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"qr_code_scanner","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\qr_code_scanner-1.0.0\\\\","native_build":true,"dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"qr_code_scanner","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\qr_code_scanner-1.0.0\\\\","native_build":true,"dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"macos":[{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"qr_code_scanner","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2022-08-12 17:52:32.581414","version":"3.0.3"} {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"qr_code_scanner","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\qr_code_scanner-1.0.0\\\\","native_build":true,"dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"qr_code_scanner","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\qr_code_scanner-1.0.0\\\\","native_build":true,"dependencies":[]},{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"macos":[{"name":"sqflite","path":"C:\\\\Users\\\\thoma\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-2.0.3+1\\\\","native_build":true,"dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"qr_code_scanner","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2022-08-18 17:59:37.167796","version":"3.0.3"}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/constants.dart';
class Loading extends StatelessWidget {
const Loading({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 85.0,
width: 85.0,
child: const Center(
child: Text("Loading.."),
),
);
}
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Screens/Scanner/scanner.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'ScannerDialog.dart'; import 'ScannerDialog.dart';

View File

@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Screens/Scanner/scanner.dart';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Screens/Article/article.dart'; import 'package:mymuseum_visitapp/Screens/Article/article.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart';
@ -36,89 +34,153 @@ class _ScannerTESTState extends State<ScannerTEST> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
return Container( return Container(
height: size.height *0.6, height: size.height *0.5,
width: size.width *0.9, width: size.width *0.9,
color: Colors.blueAccent, child: Stack(
child: Column( children: [
children: <Widget>[ Center(
Expanded(flex: 4, child: _buildQrView(context)), child: ClipRRect(
Expanded( borderRadius: BorderRadius.circular(10.0),
flex: 1, child: _buildQrView(context),
child: FittedBox( )
fit: BoxFit.contain, ),
child: Column( Positioned(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, top: 0,
children: <Widget>[ right: 0,
Row( child: Container(
mainAxisAlignment: MainAxisAlignment.center, width: 45,
crossAxisAlignment: CrossAxisAlignment.center, height: 45,
children: <Widget>[ decoration: BoxDecoration(
Container( shape: BoxShape.rectangle,
margin: const EdgeInsets.all(8), color: kMainColor,
child: ElevatedButton( borderRadius: BorderRadius.circular(20.0),
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Icon(Icons.flash_on);
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
print(controller);
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
), ),
margin: const EdgeInsets.all(8),
child: InkWell(
onTap: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return const Icon(Icons.flash_on, color: Colors.white);
},
)),
), ),
) ),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: kMainColor,
borderRadius: BorderRadius.circular(20.0),
),
margin: const EdgeInsets.all(8),
child: InkWell(
onTap: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
return const Icon(Icons.flip_camera_android, color: Colors.white);
},
)),
/*child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Icon(Icons.flip_camera_android, color: Colors.white);
},
)*/),
),
/*Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Icon(Icons.flash_on);
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
print(controller);
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),*/
], ],
), ),
); );
@ -136,7 +198,7 @@ class _ScannerTESTState extends State<ScannerTEST> {
key: qrKey, key: qrKey,
onQRViewCreated: _onQRViewCreated, onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape( overlay: QrScannerOverlayShape(
borderColor: Colors.blueAccent, borderColor: kMainColor,
borderRadius: 10, borderRadius: 10,
borderLength: 25, borderLength: 25,
borderWidth: 5, borderWidth: 5,
@ -203,8 +265,8 @@ class _ScannerTESTState extends State<ScannerTEST> {
showScannerDialog (String question, BuildContext context) { showScannerDialog (String question, BuildContext context) {
showDialog( showDialog(
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) => const AlertDialog(
shape: const RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)) borderRadius: BorderRadius.all(Radius.circular(10.0))
), ),
content: ScannerTEST( content: ScannerTEST(

View File

@ -0,0 +1,10 @@
import 'dart:io';
Future<bool> hasNetwork() async {
try {
final result = await InternetAddress.lookup('example.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} on SocketException catch (_) {
return false;
}
}

View File

@ -2,7 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart'; import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart'; import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Screens/Scanner/scanner.dart'; import 'package:mymuseum_visitapp/Screens/Scanner/scanner_old.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
class ArticlePage extends StatefulWidget { class ArticlePage extends StatefulWidget {

View File

@ -1,14 +1,18 @@
import 'dart:io';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_api/api.dart'; import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart'; import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/Loading.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart'; import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Components/ScannerDialog.dart'; import 'package:mymuseum_visitapp/Helpers/networkCheck.dart';
import 'package:mymuseum_visitapp/Screens/Scanner/scanner.dart';
import 'package:mymuseum_visitapp/Screens/Visit/visit.dart'; import 'package:mymuseum_visitapp/Screens/Visit/visit.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/client.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key); const HomePage({Key? key}) : super(key: key);
@ -18,11 +22,13 @@ class HomePage extends StatefulWidget {
} }
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
List<ConfigurationDTO> configurations = [ConfigurationDTO(label: "Test 0", isOffline: false), ConfigurationDTO(label: "Test 1", isOffline: true), ConfigurationDTO(label: "Test 2", isOffline: true)]; //List<ConfigurationDTO> configurations = [ConfigurationDTO(label: "Test 0", isOffline: false), ConfigurationDTO(label: "Test 1", isOffline: true), ConfigurationDTO(label: "Test 2", isOffline: true)];
List<ConfigurationDTO> configurations = [];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
title: "Home page - liste parcours", title: "Home page - liste parcours",
@ -32,84 +38,112 @@ class _HomePageState extends State<HomePage> {
child: Container( child: Container(
width: size.width, width: size.width,
height: size.height, height: size.height,
child: GridView.builder( child: FutureBuilder(
shrinkWrap: true, future: getConfiguration(appContext.clientAPI),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1), builder: (context, AsyncSnapshot<dynamic> snapshot) {
itemCount: configurations.length, if (snapshot.connectionState == ConnectionState.done) {
itemBuilder: (BuildContext context, int index) { configurations = List<ConfigurationDTO>.from(snapshot.data).where((configuration) => configuration.isMobile!).toList();
return InkWell( return RefreshIndicator (
onTap: () { onRefresh: () {
setState(() { setState(() {});
//sectionSelected = snapshot.data[index]; return Future(() => null);
print("config selected"); },
getConfiguration(); color: kSecondColor,
child: ListView.builder(
shrinkWrap: true, //I've set this as true here depending on what your listview content is
//physics: NeverScrollableScrollPhysics(),//This prevents scrolling, but may inhibit refresh indicator, remove as you need
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() {
print(configurations[index].label);
Navigator.of(context).pushReplacement(MaterialPageRoute( Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const VisitPage(configurationId: "TODO"), builder: (context) => VisitPage(configurationId: configurations[index].id!),
)); ));
}); });
}, },
child: Container( child: Container(
decoration: boxDecoration(configurations[index], false), height: size.height*0.15,
padding: const EdgeInsets.all(25), decoration: boxDecoration(configurations[index], false),
margin: const EdgeInsets.symmetric(vertical: 25, horizontal: 25), margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Align( child: Stack(
alignment: Alignment.bottomRight, children: [
child: FractionallySizedBox( Align(
heightFactor: 0.4, alignment: Alignment.topLeft,
child: Column( child: Padding(
children: [ padding: const EdgeInsets.only(top: 20, left: 10),
Align( child: AutoSizeText(
alignment: Alignment.centerRight, configurations[index].label!,
child: AutoSizeText( style: const TextStyle(fontSize: kMenuTitleDetailSize),
configurations[index].label!, maxLines: 1,
style: const TextStyle(fontSize: kMenuTitleDetailSize), ),
maxLines: 1, ),
), ),
), if(configurations[index].isOffline!) // TODO check if already downloaded
Align( Positioned(
alignment: Alignment.centerRight, bottom: 0,
child: AutoSizeText( right: 0,
configurations[index].isOffline.toString(), child: Container(
style: const TextStyle(fontSize: kMenuDescriptionDetailSize, fontFamily: ""), width: 45,
maxLines: 1, height: 45,
), decoration: BoxDecoration(
), shape: BoxShape.rectangle,
], color: kMainColor,
) borderRadius: BorderRadius.circular(20.0),
), ),
margin: const EdgeInsets.all(8),
child: InkWell(
onTap: () async {
print("TODO download");
},
child: Icon(Icons.download_outlined, color: Colors.white),
),
/*AutoSizeText(
configurations[index].isOffline.toString(),
style: const TextStyle(fontSize: kMenuDescriptionDetailSize, fontFamily: ""),
maxLines: 1,
),*/
)
)
],
),
),
);
},
itemCount: configurations.length
), ),
), );
); } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else {
return Center(
child: Container(
height: size.height * 0.15,
child: Loading()
)
);
}
} }
), ),
), ),
), ),
floatingActionButton: const ScannerBouton(isReplacement: false), floatingActionButton: const ScannerBouton(isReplacement: false),
//floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat, //floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat,
/*bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.qr_code_scanner),
label: 'Scanner',
),
],
selectedItemColor: kMainRed,
onTap: _onItemTapped,
),*/
); );
} }
Future<List<ConfigurationDTO>?> getConfiguration() async { Future<List<ConfigurationDTO>?> getConfiguration(Client client) async {
try { try {
var client = new Client("http://192.168.31.140:8089"); bool isOnline = await hasNetwork();
List<ConfigurationDTO>? configurations = await client.configurationApi!.configurationGet(); if(isOnline) {
print(configurations); //var client = new Client("http://192.168.31.140:8089"); // TODO REMOVE
return configurations; List<ConfigurationDTO>? configurations = await client.configurationApi!.configurationGet();
print(configurations);
return configurations;
} else {
return []; // TODO return local list..
}
} catch (e) { } catch (e) {
print(e); print(e);
print("IN CATCH"); print("IN CATCH");
@ -119,15 +153,15 @@ class _HomePageState extends State<HomePage> {
boxDecoration(ConfigurationDTO configuration, bool isSelected) { // TODO to change boxDecoration(ConfigurationDTO configuration, bool isSelected) { // TODO to change
return BoxDecoration( return BoxDecoration(
color: Colors.cyan, color: kSecondColor,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(30.0), borderRadius: BorderRadius.circular(20.0),
boxShadow: const [ boxShadow: const [
BoxShadow( BoxShadow(
color: kBackgroundSecondGrey, color: kSecondColor,
spreadRadius: 0.5, spreadRadius: 0.15,
blurRadius: 5, blurRadius: 3.5,
offset: Offset(0, 1.5), // changes position of shadow offset: Offset(0, 1), // changes position of shadow
), ),
], ],
); );

View File

@ -7,7 +7,7 @@ import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Screens/Article/article.dart'; import 'package:mymuseum_visitapp/Screens/Article/article.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart';
// NOT USED ANYMORE
class ScannerPage extends StatefulWidget { class ScannerPage extends StatefulWidget {
const ScannerPage({Key? key}) : super(key: key); const ScannerPage({Key? key}) : super(key: key);

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:manager_api/api.dart'; import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart'; import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart'; import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Screens/Scanner/scanner.dart'; import 'package:mymuseum_visitapp/Screens/Scanner/scanner_old.dart';
import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/client.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';

View File

@ -6,7 +6,7 @@ import 'Models/visitContext.dart';
class AppContext with ChangeNotifier { class AppContext with ChangeNotifier {
VisitAppContext _visitContext; VisitAppContext _visitContext;
Client clientAPI = Client(""); Client clientAPI = Client("http://192.168.31.140:8089");
AppContext(this._visitContext); AppContext(this._visitContext);

View File

@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
// Colors - TO FILL WIT CORRECT COLOR // Colors - TO FILL WIT CORRECT COLOR
const kBackgroundColor = Color(0xFFFFFFFF); const kBackgroundColor = Color(0xFFFFFFFF);
const kMainColor = Color(0xFF8b0000); const kMainColor = Color(0xFF306bac);
const kSecondColor = Color(0xFF309cb0);
const kConfigurationColor = Color(0xFF2F4858);
const List<String> languages = ["FR", "NL", "EN", "DE"]; // hmmmm depends on config.. const List<String> languages = ["FR", "NL", "EN", "DE"]; // hmmmm depends on config..
@ -31,8 +35,8 @@ const kDescriptionSize = 25.0;
const kSectionTitleDetailSize = 50.0; const kSectionTitleDetailSize = 50.0;
const kSectionDescriptionDetailSize = 35.0; const kSectionDescriptionDetailSize = 35.0;
const kMenuTitleDetailSize = 45.0; const kMenuTitleDetailSize = 30.0;
const kMenuDescriptionDetailSize = 28.0; const kMenuDescriptionDetailSize = 18.0;
const kNoneInfoOrIncorrect = 35.0; const kNoneInfoOrIncorrect = 35.0;

View File

@ -4,7 +4,7 @@ import 'package:mymuseum_visitapp/Screens/Home/home.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'Models/visitContext.dart'; import 'Models/visitContext.dart';
import 'Screens/Scanner/scanner.dart'; import 'Screens/Scanner/scanner_old.dart';
import 'app_context.dart'; import 'app_context.dart';
import 'constants.dart'; import 'constants.dart';