Lots!
New navigation system, new 'about/settings/logout' page, some more types of dialog boxes added, some more minor things
This commit is contained in:
parent
2d0b6230ae
commit
60873a07ef
13 changed files with 1142 additions and 279 deletions
|
@ -6,13 +6,13 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `.symlinks/flutter/ios-profile`)
|
- Flutter (from `.symlinks/flutter/ios`)
|
||||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: ".symlinks/flutter/ios-profile"
|
:path: ".symlinks/flutter/ios"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
|
|
|
@ -281,7 +281,7 @@
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
||||||
"${PODS_ROOT}/../.symlinks/flutter/ios-profile/Flutter.framework",
|
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
|
|
@ -38,6 +38,9 @@ Future<LoginModel> requestLoginAPI(
|
||||||
|
|
||||||
return LoginModel.fromJson(responseJson);
|
return LoginModel.fromJson(responseJson);
|
||||||
} else {
|
} else {
|
||||||
|
debugPrint("Invalid, either creds are wrong or server is down");
|
||||||
|
Navigator.of(context).pushReplacementNamed('/HomePage'); // just here temporarily while server is down
|
||||||
|
|
||||||
final responseJson = json.decode(response.body);
|
final responseJson = json.decode(response.body);
|
||||||
|
|
||||||
saveCurrentLogin(responseJson, body["email"]);
|
saveCurrentLogin(responseJson, body["email"]);
|
||||||
|
|
509
lib/common/functions/customAbout.dart
Normal file
509
lib/common/functions/customAbout.dart
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
//import 'dart:developer' show Timeline, Flow;
|
||||||
|
import 'dart:developer' as dev;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/widgets.dart' hide Flow;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A [ListTile] that shows an about box.
|
||||||
|
///
|
||||||
|
/// This widget is often added to an app's [Drawer]. When tapped it shows
|
||||||
|
/// an about box dialog with [showAboutDialog].
|
||||||
|
///
|
||||||
|
/// The about box will include a button that shows licenses for software used by
|
||||||
|
/// the application. The licenses shown are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
///
|
||||||
|
/// If your application does not have a [Drawer], you should provide an
|
||||||
|
/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
|
||||||
|
class AboutListTile extends StatelessWidget {
|
||||||
|
/// Creates a list tile for showing an about box.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version, icon, and legalese
|
||||||
|
/// values default to the empty string.
|
||||||
|
const AboutListTile({
|
||||||
|
Key key,
|
||||||
|
this.icon = const Icon(null),
|
||||||
|
this.child,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationIcon,
|
||||||
|
this.applicationLegalese,
|
||||||
|
this.aboutBoxChildren,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The icon to show for this drawer item.
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// This is not necessarily the same as the image shown in the dialog box
|
||||||
|
/// itself; which is controlled by the [applicationIcon] property.
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
/// The label to show on this drawer item.
|
||||||
|
///
|
||||||
|
/// Defaults to a text widget that says "About Foo" where "Foo" is the
|
||||||
|
/// application name specified by [applicationName].
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// This string is used in the default label for this drawer item (see
|
||||||
|
/// [child]) and as the caption of the [AboutDialog] that is shown.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// The icon to show next to the application name in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// Typically this will be an [ImageIcon] widget. It should honor the
|
||||||
|
/// [IconTheme]'s [IconThemeData.size].
|
||||||
|
///
|
||||||
|
/// This is not necessarily the same as the icon shown on the drawer item
|
||||||
|
/// itself, which is controlled by the [icon] property.
|
||||||
|
final Widget applicationIcon;
|
||||||
|
|
||||||
|
/// A string to show in small print in the [AboutDialog].
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
/// Widgets to add to the [AboutDialog] after the name, version, and legalese.
|
||||||
|
///
|
||||||
|
/// This could include a link to a Web site, some descriptive text, credits,
|
||||||
|
/// or other information to show in the about box.
|
||||||
|
///
|
||||||
|
/// Defaults to nothing.
|
||||||
|
final List<Widget> aboutBoxChildren;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
return ListTile(
|
||||||
|
leading: icon,
|
||||||
|
title: child ??
|
||||||
|
Text(MaterialLocalizations.of(context).aboutListTileTitle(applicationName ?? _defaultApplicationName(context))),
|
||||||
|
onTap: () {
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
children: aboutBoxChildren,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an [AboutDialog], which describes the application and provides a
|
||||||
|
/// button to show licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// The arguments correspond to the properties on [AboutDialog].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||||
|
/// of calling this directly.
|
||||||
|
///
|
||||||
|
/// If you do not need an about box in your application, you should at least
|
||||||
|
/// provide an affordance to call [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
///
|
||||||
|
/// The `context` argument is passed to [showDialog], the documentation for
|
||||||
|
/// which discusses how it is used.
|
||||||
|
void showAboutDialog({
|
||||||
|
@required BuildContext context,
|
||||||
|
String applicationName,
|
||||||
|
String applicationVersion,
|
||||||
|
Widget applicationIcon,
|
||||||
|
String applicationLegalese,
|
||||||
|
List<Widget> children,
|
||||||
|
}) {
|
||||||
|
assert(context != null);
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AboutDialog(
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a [LicensePage], which shows licenses for software used by the
|
||||||
|
/// application.
|
||||||
|
///
|
||||||
|
/// The arguments correspond to the properties on [LicensePage].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], consider using [AboutListTile] instead
|
||||||
|
/// of calling this directly.
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||||
|
/// [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
void showLicensePage({
|
||||||
|
@required BuildContext context,
|
||||||
|
String applicationName,
|
||||||
|
String applicationVersion,
|
||||||
|
Widget applicationIcon,
|
||||||
|
String applicationLegalese,
|
||||||
|
}) {
|
||||||
|
assert(context != null);
|
||||||
|
Navigator.push(context, MaterialPageRoute<void>(
|
||||||
|
builder: (BuildContext context) => LicensePage(
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An about box. This is a dialog box with the application's icon, name,
|
||||||
|
/// version number, and copyright, plus a button to show licenses for software
|
||||||
|
/// used by the application.
|
||||||
|
///
|
||||||
|
/// To show an [AboutDialog], use [showAboutDialog].
|
||||||
|
///
|
||||||
|
/// If the application has a [Drawer], the [AboutListTile] widget can make the
|
||||||
|
/// process of showing an about dialog simpler.
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
|
||||||
|
/// [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
class AboutDialog extends StatelessWidget {
|
||||||
|
/// Creates an about box.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version, icon, and legalese
|
||||||
|
/// values default to the empty string.
|
||||||
|
const AboutDialog({
|
||||||
|
Key key,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationIcon,
|
||||||
|
this.applicationLegalese,
|
||||||
|
this.children,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// The icon to show next to the application name.
|
||||||
|
///
|
||||||
|
/// By default no icon is shown.
|
||||||
|
///
|
||||||
|
/// Typically this will be an [ImageIcon] widget. It should honor the
|
||||||
|
/// [IconTheme]'s [IconThemeData.size].
|
||||||
|
final Widget applicationIcon;
|
||||||
|
|
||||||
|
/// A string to show in small print.
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
/// Widgets to add to the dialog box after the name, version, and legalese.
|
||||||
|
///
|
||||||
|
/// This could include a link to a Web site, some descriptive text, credits,
|
||||||
|
/// or other information to show in the about box.
|
||||||
|
///
|
||||||
|
/// Defaults to nothing.
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
final String name = applicationName ?? _defaultApplicationName(context);
|
||||||
|
final String version = applicationVersion ?? _defaultApplicationVersion(context);
|
||||||
|
final Widget icon = applicationIcon ?? _defaultApplicationIcon(context);
|
||||||
|
List<Widget> body = <Widget>[];
|
||||||
|
if (icon != null)
|
||||||
|
body.add(IconTheme(data: const IconThemeData(size: 45.0), child: icon));
|
||||||
|
body.add(Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(name, style: Theme.of(context).textTheme.title),
|
||||||
|
Text(version, style: Theme.of(context).textTheme.body1),
|
||||||
|
Text(applicationLegalese ?? '', style: Theme.of(context).textTheme.caption),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
body = <Widget>[
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: body,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (children != null)
|
||||||
|
body.addAll(children);
|
||||||
|
return AlertDialog(
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(children: body),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
|
||||||
|
onPressed: () {
|
||||||
|
showLicensePage(
|
||||||
|
context: context,
|
||||||
|
applicationName: applicationName,
|
||||||
|
applicationVersion: applicationVersion,
|
||||||
|
applicationIcon: applicationIcon,
|
||||||
|
applicationLegalese: applicationLegalese,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page that shows licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// To show a [LicensePage], use [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
|
||||||
|
/// a button that calls [showLicensePage].
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
class LicensePage extends StatefulWidget {
|
||||||
|
/// Creates a page that shows licenses for software used by the application.
|
||||||
|
///
|
||||||
|
/// The arguments are all optional. The application name, if omitted, will be
|
||||||
|
/// derived from the nearest [Title] widget. The version and legalese values
|
||||||
|
/// default to the empty string.
|
||||||
|
///
|
||||||
|
/// The licenses shown on the [LicensePage] are those returned by the
|
||||||
|
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
|
||||||
|
const LicensePage({
|
||||||
|
Key key,
|
||||||
|
this.applicationName,
|
||||||
|
this.applicationVersion,
|
||||||
|
this.applicationLegalese,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The name of the application.
|
||||||
|
///
|
||||||
|
/// Defaults to the value of [Title.title], if a [Title] widget can be found.
|
||||||
|
/// Otherwise, defaults to [Platform.resolvedExecutable].
|
||||||
|
final String applicationName;
|
||||||
|
|
||||||
|
/// The version of this build of the application.
|
||||||
|
///
|
||||||
|
/// This string is shown under the application name.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationVersion;
|
||||||
|
|
||||||
|
/// A string to show in small print.
|
||||||
|
///
|
||||||
|
/// Typically this is a copyright notice.
|
||||||
|
///
|
||||||
|
/// Defaults to the empty string.
|
||||||
|
final String applicationLegalese;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LicensePageState createState() => _LicensePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LicensePageState extends State<LicensePage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Widget> _licenses = <Widget>[];
|
||||||
|
bool _loaded = false;
|
||||||
|
|
||||||
|
Future<void> _initLicenses() async {
|
||||||
|
int debugFlowId = -1;
|
||||||
|
assert(() {
|
||||||
|
final dev.Flow flow = dev.Flow.begin();
|
||||||
|
dev.Timeline.timeSync('_initLicenses()', () { }, flow: flow);
|
||||||
|
debugFlowId = flow.id;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
await for (LicenseEntry license in LicenseRegistry.licenses) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(() {
|
||||||
|
dev.Timeline.timeSync('_initLicenses()', () { }, flow: dev.Flow.step(debugFlowId));
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
final List<LicenseParagraph> paragraphs =
|
||||||
|
await SchedulerBinding.instance.scheduleTask<List<LicenseParagraph>>(
|
||||||
|
license.paragraphs.toList,
|
||||||
|
Priority.animation,
|
||||||
|
debugLabel: 'License',
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_licenses.add(const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 18.0),
|
||||||
|
child: Text(
|
||||||
|
'🍀', // That's U+1F340. Could also use U+2766 (❦) if U+1F340 doesn't work everywhere.
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
_licenses.add(Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(width: 0.0))
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
license.packages.join(', '),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
for (LicenseParagraph paragraph in paragraphs) {
|
||||||
|
if (paragraph.indent == LicenseParagraph.centeredIndent) {
|
||||||
|
_licenses.add(Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
child: Text(
|
||||||
|
paragraph.text,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
assert(paragraph.indent >= 0);
|
||||||
|
_licenses.add(Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
|
||||||
|
child: Text(paragraph.text),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_loaded = true;
|
||||||
|
});
|
||||||
|
assert(() {
|
||||||
|
dev.Timeline.timeSync('Build scheduled', () { }, flow: dev.Flow.end(debugFlowId));
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
final String name = widget.applicationName ?? _defaultApplicationName(context);
|
||||||
|
final String version = widget.applicationVersion ?? _defaultApplicationVersion(context);
|
||||||
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||||
|
final List<Widget> contents = <Widget>[
|
||||||
|
Text(name, style: Theme.of(context).textTheme.headline, textAlign: TextAlign.center),
|
||||||
|
Text(version, style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
|
||||||
|
Container(height: 18.0),
|
||||||
|
Text(widget.applicationLegalese ?? '', style: Theme.of(context).textTheme.caption, textAlign: TextAlign.center),
|
||||||
|
Container(height: 18.0),
|
||||||
|
Text('Powered by Flutter', style: Theme.of(context).textTheme.body1, textAlign: TextAlign.center),
|
||||||
|
Container(height: 24.0),
|
||||||
|
];
|
||||||
|
contents.addAll(_licenses);
|
||||||
|
if (!_loaded) {
|
||||||
|
contents.add(const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 24.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(localizations.licensesPageTitle),
|
||||||
|
),
|
||||||
|
// All of the licenses page text is English. We don't want localized text
|
||||||
|
// or text direction.
|
||||||
|
body: Localizations.override(
|
||||||
|
locale: const Locale('en', 'US'),
|
||||||
|
context: context,
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Scrollbar(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
|
||||||
|
children: contents,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _defaultApplicationName(BuildContext context) {
|
||||||
|
// This doesn't handle the case of the application's title dynamically
|
||||||
|
// changing. In theory, we should make Title expose the current application
|
||||||
|
// title using an InheritedWidget, and so forth. However, in practice, if
|
||||||
|
// someone really wants their application title to change dynamically, they
|
||||||
|
// can provide an explicit applicationName to the widgets defined in this
|
||||||
|
// file, instead of relying on the default.
|
||||||
|
final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
|
||||||
|
return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _defaultApplicationVersion(BuildContext context) {
|
||||||
|
// TODO(ianh): Get this from the embedder somehow.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _defaultApplicationIcon(BuildContext context) {
|
||||||
|
// TODO(ianh): Get this from the embedder somehow.
|
||||||
|
return null;
|
||||||
|
}
|
61
lib/common/functions/feedback.dart
Normal file
61
lib/common/functions/feedback.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final TextEditingController _feedbackController = TextEditingController();
|
||||||
|
|
||||||
|
void feedback(
|
||||||
|
BuildContext context) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text("Feedback"),
|
||||||
|
content: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text("We would love to hear your thoughts."),
|
||||||
|
Text("\nThis section needs to be fixed."),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.fromLTRB(0, 20, 0, 0),
|
||||||
|
width: 200,
|
||||||
|
height: 70,
|
||||||
|
|
||||||
|
child : new TextField(
|
||||||
|
controller: _feedbackController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Enter your feedback here',
|
||||||
|
),
|
||||||
|
// obscureText: true,
|
||||||
|
autocorrect: true,
|
||||||
|
maxLines: 20,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) {
|
||||||
|
submit(_feedbackController.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||||
|
child : new FlatButton(
|
||||||
|
child: new Text("Submit"),
|
||||||
|
onPressed: () {
|
||||||
|
submit(_feedbackController.text);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void submit(String feedback) {
|
||||||
|
debugPrint(feedback);
|
||||||
|
}
|
34
lib/common/functions/showDialogTwoButtons.dart
Normal file
34
lib/common/functions/showDialogTwoButtons.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void showDialogTwoButtons(
|
||||||
|
BuildContext context, String title, String message, String buttonLabel1, String buttonLabel2, Function action) {
|
||||||
|
// flutter defined function
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
// return object of type Dialog
|
||||||
|
return AlertDialog(
|
||||||
|
title: new Text(title),
|
||||||
|
content: new Text(message),
|
||||||
|
actions: <Widget>[
|
||||||
|
// usually buttons at the bottom of the dialog
|
||||||
|
|
||||||
|
new FlatButton(
|
||||||
|
child: new Text(buttonLabel1),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
new FlatButton(
|
||||||
|
child: new Text(buttonLabel2),
|
||||||
|
onPressed: () {
|
||||||
|
action(context);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ class PopupListView {
|
||||||
for (var i = 0; i < options.length; i++) {
|
for (var i = 0; i < options.length; i++) {
|
||||||
dialogOptionsList.add(
|
dialogOptionsList.add(
|
||||||
new SimpleDialogOption(
|
new SimpleDialogOption(
|
||||||
|
// print each iteration to see if any are null
|
||||||
child: Text(options[i]),
|
child: Text(options[i]),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(this.context).pop();
|
Navigator.of(this.context).pop();
|
||||||
|
@ -33,8 +34,6 @@ class PopupListView {
|
||||||
return dialogOptionsList;
|
return dialogOptionsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Widget dialog() {
|
Widget dialog() {
|
||||||
return new SimpleDialog(
|
return new SimpleDialog(
|
||||||
title: Text(listTitle),
|
title: Text(listTitle),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:local_spend/pages/login_page.dart';
|
||||||
import 'package:local_spend/pages/receipt_page.dart';
|
import 'package:local_spend/pages/receipt_page.dart';
|
||||||
import 'package:local_spend/pages/spash_screen.dart';
|
import 'package:local_spend/pages/spash_screen.dart';
|
||||||
import 'package:local_spend/pages/about_screen.dart';
|
import 'package:local_spend/pages/about_screen.dart';
|
||||||
|
import 'package:local_spend/pages/settings.dart';
|
||||||
import 'package:local_spend/config.dart';
|
import 'package:local_spend/config.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ class MyApp extends StatelessWidget {
|
||||||
Locale("en")
|
Locale("en")
|
||||||
],
|
],
|
||||||
|
|
||||||
title: "Splash and Token Authentication",
|
title: "Local Spend Tracker",
|
||||||
theme: new ThemeData(
|
theme: new ThemeData(
|
||||||
primarySwatch: Colors.blueGrey,
|
primarySwatch: Colors.blueGrey,
|
||||||
),
|
),
|
||||||
|
@ -34,6 +35,7 @@ class MyApp extends StatelessWidget {
|
||||||
"/LoginPage": (BuildContext context) => LoginPage(),
|
"/LoginPage": (BuildContext context) => LoginPage(),
|
||||||
"/ReceiptPage": (BuildContext context) => ReceiptPage(),
|
"/ReceiptPage": (BuildContext context) => ReceiptPage(),
|
||||||
"/AboutPage": (BuildContext context) => AboutPage(),
|
"/AboutPage": (BuildContext context) => AboutPage(),
|
||||||
|
"/SettingsPage": (BuildContext context) => SettingsPage(),
|
||||||
},
|
},
|
||||||
home: SplashScreen(),
|
home: SplashScreen(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,10 @@ import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
|
||||||
class AboutPage extends StatefulWidget {
|
class AboutPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_HomePageState createState() => _HomePageState();
|
// _HomePageState createState() => _HomePageState();
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return new _HomePageState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<AboutPage> {
|
class _HomePageState extends State<AboutPage> {
|
||||||
|
@ -17,6 +20,11 @@ class _HomePageState extends State<AboutPage> {
|
||||||
_saveCurrentRoute("/AboutPage");
|
_saveCurrentRoute("/AboutPage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_saveCurrentRoute(String lastRoute) async {
|
_saveCurrentRoute(String lastRoute) async {
|
||||||
SharedPreferences preferences = await SharedPreferences.getInstance();
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
await preferences.setString('LastScreenRoute', lastRoute);
|
await preferences.setString('LastScreenRoute', lastRoute);
|
||||||
|
@ -24,27 +32,36 @@ class _HomePageState extends State<AboutPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PlatformScaffold(
|
return WillPopScope(
|
||||||
appBar: AppBar(
|
onWillPop: () {
|
||||||
title: Text(
|
if (Navigator.canPop(context)) {
|
||||||
"About Page",
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
style: TextStyle(
|
'/HomePage', (Route<dynamic> route) => true);
|
||||||
fontSize: 30,
|
} else {
|
||||||
color: Colors.black),
|
Navigator.of(context).pushReplacementNamed('/HomePage');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: PlatformScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
"About Page",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: BackButton(),
|
||||||
|
centerTitle: true,
|
||||||
|
iconTheme: IconThemeData(color: Colors.black),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
body: Container(
|
||||||
iconTheme: IconThemeData(color: Colors.black),
|
padding: EdgeInsets.all(32.0),
|
||||||
elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 6.0,
|
child: ListView(
|
||||||
),
|
|
||||||
drawer: BasicDrawer(),
|
|
||||||
body: Container(
|
|
||||||
padding: EdgeInsets.all(32.0),
|
|
||||||
child: ListView(
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
InkWell(
|
InkWell(
|
||||||
child: const Center(child: Text
|
child: const Center(child: Text
|
||||||
('Pear Trading',
|
('Pear Trading',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
|
@ -113,6 +130,7 @@ class _HomePageState extends State<AboutPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,94 +1,61 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:local_spend/common/platform/platform_scaffold.dart';
|
import 'package:local_spend/pages/receipt_page.dart';
|
||||||
import 'package:local_spend/common/widgets/basic_drawer.dart';
|
import 'package:local_spend/pages/settings.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
class HomePage extends StatelessWidget {
|
||||||
import 'package:local_spend/common/apifunctions/request_logout_api.dart';
|
static String _title = 'Text here';
|
||||||
import 'package:local_spend/common/functions/get_token.dart';
|
|
||||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
@override
|
||||||
import 'package:local_spend/common/functions/logout.dart';
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: _title,
|
||||||
|
home: HomePageWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePageWidget extends StatefulWidget {
|
||||||
|
HomePageWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
|
||||||
@override
|
@override
|
||||||
_HomePageState createState() => _HomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePageWidget> {
|
||||||
@override
|
int _selectedIndex = 0;
|
||||||
|
static const TextStyle optionStyle =
|
||||||
|
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
|
||||||
|
static List<Widget> _widgetOptions = <Widget>[
|
||||||
|
ReceiptPage(),
|
||||||
|
SettingsPage()
|
||||||
|
];
|
||||||
|
|
||||||
void initState() {
|
void _onItemTapped(int index) {
|
||||||
super.initState();
|
setState(() {
|
||||||
_saveCurrentRoute("/HomePage");
|
_selectedIndex = index;
|
||||||
}
|
});
|
||||||
|
|
||||||
_saveCurrentRoute(String lastRoute) async {
|
|
||||||
SharedPreferences preferences = await SharedPreferences.getInstance();
|
|
||||||
await preferences.setString('LastScreenRoute', lastRoute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PlatformScaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: Center(
|
||||||
title: Text(
|
child: _widgetOptions.elementAt(_selectedIndex),
|
||||||
"Navigation",
|
|
||||||
style: TextStyle(color: Colors.black),
|
|
||||||
),
|
|
||||||
iconTheme: IconThemeData(color: Colors.black),
|
|
||||||
elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 6.0,
|
|
||||||
),
|
),
|
||||||
drawer: BasicDrawer(),
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
body: Container(
|
items: const <BottomNavigationBarItem>[
|
||||||
padding: EdgeInsets.fromLTRB(0, 15, 0, 0),
|
BottomNavigationBarItem(
|
||||||
child: FadeIn(
|
icon: Icon(Icons.receipt),
|
||||||
duration: Duration(milliseconds: 500),
|
title: Text('Submit Receipt'),
|
||||||
curve: Curves.easeIn,
|
|
||||||
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
title: new Center(
|
|
||||||
child: new Text(
|
|
||||||
"Submit Receipt",
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 20.0),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
// debugPrint('$token');
|
|
||||||
Navigator.of(context).pushNamed('/ReceiptPage');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
title: new Center(
|
|
||||||
child: new Text(
|
|
||||||
"About",
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 20.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
SystemChannels.textInput.invokeMethod('TextInput.hide');
|
|
||||||
Navigator.of(context).pushReplacementNamed('/AboutPage');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
title: new Center(
|
|
||||||
child: new Text(
|
|
||||||
"Logout",
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 20.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
logout(context);
|
|
||||||
},
|
|
||||||
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.settings),
|
||||||
|
title: Text('Settings'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
currentIndex: _selectedIndex,
|
||||||
|
selectedItemColor: Colors.blue[400],
|
||||||
|
onTap: _onItemTapped,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ class LoginPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginPageState extends State<LoginPage> {
|
class LoginPageState extends State<LoginPage> {
|
||||||
final TextEditingController _emailController = TextEditingController(/*text: 'test@example.com'*/); // remove
|
final TextEditingController _emailController = TextEditingController(text: 'test@example.com'); // remove
|
||||||
final TextEditingController _passwordController = TextEditingController(/*text: 'abc123'*/); // remove
|
final TextEditingController _passwordController = TextEditingController(text: 'abc123'); // remove
|
||||||
bool _saveLoginDetails = true; // I am extremely sorry for the placement of this variable
|
bool _saveLoginDetails = true; // I am extremely sorry for the placement of this variable
|
||||||
// it will be fixed soon I promise
|
// it will be fixed soon I promise
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,11 @@ import 'package:local_spend/common/widgets/basic_drawer.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:local_spend/pages/settings.dart';
|
||||||
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
|
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
|
||||||
import 'package:local_spend/common/apifunctions/find_organisations.dart';
|
import 'package:local_spend/common/apifunctions/find_organisations.dart';
|
||||||
import 'package:local_spend/common/widgets/popupListView.dart';
|
import 'package:local_spend/common/widgets/popupListView.dart';
|
||||||
|
import 'package:local_spend/common/widgets/labeled_checkbox.dart';
|
||||||
|
|
||||||
const URL = "https://flutter.io/";
|
const URL = "https://flutter.io/";
|
||||||
const demonstration = false;
|
const demonstration = false;
|
||||||
|
@ -30,6 +32,7 @@ class ReceiptPageState extends State<ReceiptPage> {
|
||||||
final TextEditingController _recurringController = TextEditingController();
|
final TextEditingController _recurringController = TextEditingController();
|
||||||
final TextEditingController _typeController = TextEditingController();
|
final TextEditingController _typeController = TextEditingController();
|
||||||
final TextEditingController _orgController = TextEditingController();
|
final TextEditingController _orgController = TextEditingController();
|
||||||
|
bool _recurringCheckbox = false; // have mercy, this will be removed. sorry for this variable's placement...
|
||||||
|
|
||||||
FocusNode focusNode; // added so focus can move automatically
|
FocusNode focusNode; // added so focus can move automatically
|
||||||
|
|
||||||
|
@ -174,223 +177,310 @@ class ReceiptPageState extends State<ReceiptPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var drawer = Drawer();
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () {
|
onWillPop: () {
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
'/HomePage', (Route<dynamic> route) => false);
|
'/LoginPage', (Route<dynamic> route) => false);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pushReplacementNamed('/HomePage');
|
Navigator.of(context).pushReplacementNamed('/LoginPage');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: PlatformScaffold(
|
child: PlatformScaffold(
|
||||||
drawer: BasicDrawer(),
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.blue[400],
|
||||||
title: Text(
|
title: Text(
|
||||||
"Submit Receipt",
|
"Submit Receipt",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 30.0,
|
fontSize: 20,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// leading: BackButton(),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
iconTheme: IconThemeData(color: Colors.black),
|
iconTheme: IconThemeData(color: Colors.black),
|
||||||
),
|
),
|
||||||
|
|
||||||
body: Container(
|
body: Container(
|
||||||
child: Padding(
|
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
|
||||||
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
|
child: ListView(
|
||||||
child: ListView(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Padding(
|
||||||
// Container(
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
// alignment: Alignment.topCenter,
|
child : Text(
|
||||||
// child: Padding(
|
"Time of Transaction",
|
||||||
// padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 15.0),
|
style: TextStyle(
|
||||||
// child: Text(
|
fontSize: 18.0,
|
||||||
// "Required fields are in bold",
|
color: Colors.black,
|
||||||
// style: TextStyle(fontSize: 20.0, color: Colors.black),
|
fontWeight: FontWeight.bold,
|
||||||
// ),
|
|
||||||
// )),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
|
||||||
child : Text(
|
|
||||||
"Time of Transaction",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DateTimePickerFormField(
|
),
|
||||||
inputType: InputType.both,
|
DateTimePickerFormField(
|
||||||
format: DateFormat("dd/MM/yyyy 'at' hh:mm"),
|
inputType: InputType.both,
|
||||||
editable: true,
|
format: DateFormat("dd/MM/yyyy 'at' hh:mm"),
|
||||||
controller: _timeController,
|
editable: true,
|
||||||
|
controller: _timeController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Date/Time of Transaction', hasFloatingPlaceholder: false),
|
||||||
|
onChanged: (dt) => setState(() => date = dt),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
|
child: Text(
|
||||||
|
"Amount",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
|
||||||
|
child: TextField(
|
||||||
|
controller: _amountController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Date/Time of Transaction', hasFloatingPlaceholder: false),
|
hintText: 'Value in £',
|
||||||
onChanged: (dt) => setState(() => date = dt),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
|
||||||
child: Text(
|
|
||||||
"Amount",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
|
|
||||||
child: TextField(
|
|
||||||
controller: _amountController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Value in £',
|
|
||||||
),
|
|
||||||
// obscureText: true,
|
// obscureText: true,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.0,
|
fontSize: 18.0,
|
||||||
color: Colors.grey[800],
|
color: Colors.grey[800],
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
FocusScope.of(context).requestFocus(focusNode);
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
// submitReceipt(_amountController.text, _timeController.text);
|
// submitReceipt(_amountController.text, _timeController.text);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
|
|
||||||
child : Container (
|
child : Container (
|
||||||
height: 22, // this should be the same height as text
|
height: 22, // this should be the same height as text
|
||||||
|
|
||||||
child : ListView(
|
child : ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
||||||
Container(
|
Container(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Organization Name",
|
"Organization Name",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.0,
|
fontSize: 18.0,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
Container(
|
Container(
|
||||||
child : Padding(
|
child : Padding(
|
||||||
padding: EdgeInsets.fromLTRB(5,0,0,4), // sorry about hardcoded constraints
|
padding: EdgeInsets.fromLTRB(5,0,0,4), // sorry about hardcoded constraints
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var organisations = findOrganisations(_orgController.text);
|
var organisations = findOrganisations(_orgController.text);
|
||||||
// some tasty async stuff here yum yum
|
// some tasty async stuff here yum yum
|
||||||
// and a pretty little dialog too yay
|
// and a pretty little dialog too yay (doesn't work)
|
||||||
var choice = organisations.then((data) => listOrganisations(data, context));
|
var choice = organisations.then((data) => listOrganisations(data, context));
|
||||||
|
|
||||||
// choice is a Future<String>
|
// choice is a Future<String>
|
||||||
},
|
},
|
||||||
child: Text("Find",
|
child: Text("Find",
|
||||||
style:
|
style:
|
||||||
TextStyle(color: Colors.blue, fontSize: 18.0)),
|
TextStyle(color: Colors.blue, fontSize: 18.0)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _orgController,
|
controller: _orgController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Eg. Pear Trading',
|
hintText: 'Eg. Pear Trading',
|
||||||
),
|
),
|
||||||
// obscureText: true,
|
// obscureText: true,
|
||||||
autocorrect: true,
|
autocorrect: true,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.0,
|
fontSize: 18.0,
|
||||||
color: Colors.grey[800],
|
color: Colors.grey[800],
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
submitReceipt(_amountController.text,
|
submitReceipt(_amountController.text,
|
||||||
_timeController.text, _orgController.text);
|
_timeController.text, _orgController.text);
|
||||||
// TODO: make sure organisation is valid
|
// TODO: make sure organisation is valid
|
||||||
// TODO: Add 'find organisation' button which displays a dialog to, well, find the organisation's address or manual entry
|
// TODO: Add 'find organisation' button which displays a dialog to, well, find the organisation's address or manual entry
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
|
|
||||||
|
child : Container (
|
||||||
|
height: 18,
|
||||||
|
|
||||||
|
child : ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"Essential",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
child : Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(20.0, 0.0, 0, 0),
|
||||||
|
|
||||||
|
child: Checkbox(value:
|
||||||
|
_essentialController.text.toLowerCase() == 'true',
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
setState(() {
|
||||||
|
_essentialController.text =
|
||||||
|
convertBoolToString(newValue);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
|
|
||||||
|
child : Container (
|
||||||
|
height: 27,
|
||||||
|
// width: 400,
|
||||||
|
|
||||||
|
child : ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"Recurring",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
child : Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(15.0, 0.0, 0, 4),
|
||||||
|
|
||||||
|
child: Checkbox(value:
|
||||||
|
_essentialController.text.toLowerCase() != "none" ||
|
||||||
|
_essentialController.text.toLowerCase() != 'false',
|
||||||
|
onChanged: (bool newValue) {
|
||||||
|
setState(() {
|
||||||
|
var options = new List<String>(7);
|
||||||
|
options[0] = "Daily";
|
||||||
|
options[1] = "Weekly";
|
||||||
|
options[2] = "Fortnightly";
|
||||||
|
options[3] = "Monthly";
|
||||||
|
options[5] = "Quarterly";
|
||||||
|
options[6] = "Yearly";
|
||||||
|
|
||||||
|
var popupListView = new PopupListView(
|
||||||
|
context, options, "Recurring...");
|
||||||
|
|
||||||
|
var dialog = popupListView.dialog();
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return dialog;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
print(popupListView.result);
|
||||||
|
// _recurringController.text =
|
||||||
|
// popupListView.result;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(10, 2, 0, 0),
|
||||||
|
child: Text(
|
||||||
|
convertBoolToString(_essentialController.text.toLowerCase() != "none" ||
|
||||||
|
_essentialController.text.toLowerCase() != 'false'),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
// var options = new List<String>(1);
|
||||||
|
// options[0] = "Weekly";
|
||||||
|
//
|
||||||
|
// var popupListView = new PopupListView(context, options, "Recurring...");
|
||||||
|
//
|
||||||
|
// var dialog = popupListView.dialog();
|
||||||
|
//
|
||||||
|
// showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return dialog;
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// print(popupListView.result);
|
||||||
|
// _recurringController.text = popupListView.result;
|
||||||
|
//
|
||||||
|
// setState(() {
|
||||||
|
// _recurringController.text =
|
||||||
|
// convertBoolToString(newValue);
|
||||||
|
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
submitReceipt(_amountController.text, _timeController.text, _orgController.text);
|
||||||
},
|
},
|
||||||
|
child: Text("SUBMIT",
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Padding(
|
],
|
||||||
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
|
||||||
|
|
||||||
child : Container (
|
|
||||||
height: 18,
|
|
||||||
|
|
||||||
child : ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
child: Text(
|
|
||||||
"Essential Purchase",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.0,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Container(
|
|
||||||
child : Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(20.0, 0.0, 0, 0),
|
|
||||||
|
|
||||||
child: Checkbox(value:
|
|
||||||
_essentialController.text.toLowerCase() == 'true',
|
|
||||||
onChanged: (bool newValue) {
|
|
||||||
setState(() {
|
|
||||||
_essentialController.text =
|
|
||||||
convertBoolToString(newValue);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(0.0, 70.0, 0.0, 0.0),
|
|
||||||
child: Container(
|
|
||||||
height: 65.0,
|
|
||||||
child: RaisedButton(
|
|
||||||
onPressed: () {
|
|
||||||
submitReceipt(_amountController.text, _timeController.text, _orgController.text);
|
|
||||||
},
|
|
||||||
child: Text("SUBMIT",
|
|
||||||
style:
|
|
||||||
TextStyle(color: Colors.white, fontSize: 22.0)),
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
180
lib/pages/settings.dart
Normal file
180
lib/pages/settings.dart
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_spend/common/platform/platform_scaffold.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:local_spend/common/functions/logout.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:local_spend/common/functions/customAbout.dart' as custom;
|
||||||
|
import 'package:local_spend/common/functions/showDialogTwoButtons.dart';
|
||||||
|
import 'package:local_spend/common/functions/feedback.dart';
|
||||||
|
|
||||||
|
const URL = "https://flutter.io/";
|
||||||
|
const demonstration = false;
|
||||||
|
|
||||||
|
class SettingsPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return new SettingsPageState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsPageState extends State<SettingsPage> {
|
||||||
|
FocusNode focusNode; // added so focus can move automatically
|
||||||
|
|
||||||
|
DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_saveCurrentRoute("/SettingsPageState");
|
||||||
|
|
||||||
|
focusNode = FocusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
focusNode.dispose(); //disposes focus node when form disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveCurrentRoute(String lastRoute) async {
|
||||||
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
await preferences.setString('LastPageRoute', lastRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () {
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
'/LoginPage', (Route<dynamic> route) => false);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushReplacementNamed('/LoginPage');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: PlatformScaffold(
|
||||||
|
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.blue[400],
|
||||||
|
title: Text(
|
||||||
|
"Settings",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// leading: BackButton(),
|
||||||
|
centerTitle: true,
|
||||||
|
iconTheme: IconThemeData(color: Colors.black),
|
||||||
|
),
|
||||||
|
|
||||||
|
body: Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0),
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0,25,0.0,0.0),
|
||||||
|
child : Text(
|
||||||
|
"Local Spend Tracker",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22.0,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
//
|
||||||
|
// Padding(
|
||||||
|
// padding: EdgeInsets.fromLTRB(10, 15, 0, 0),
|
||||||
|
// child: Text("helloooo"),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
custom.showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationIcon: new Icon(Icons.receipt),
|
||||||
|
applicationName: "Local Spend Tracker",
|
||||||
|
children: <Widget> [
|
||||||
|
Text("Pear Trading is a commerce company designed to register and monitor money circulating in the local economy."),
|
||||||
|
Text("\nContact at test@example.com or +44(0)1524 64544"),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0,20,0,0),
|
||||||
|
child: InkWell(
|
||||||
|
child: Text
|
||||||
|
('Developed by Shadowcat Systems',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => launch('https://shadow.cat/')
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
child: Text("ABOUT",
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
showDialogTwoButtons(
|
||||||
|
context,
|
||||||
|
"Logout",
|
||||||
|
"Are you sure you want to log out?",
|
||||||
|
"Cancel",
|
||||||
|
"Logout",
|
||||||
|
logout
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text("LOGOUT",
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0),
|
||||||
|
child: Container(
|
||||||
|
height: 65.0,
|
||||||
|
child: RaisedButton(
|
||||||
|
onPressed: () {
|
||||||
|
feedback(context);
|
||||||
|
},
|
||||||
|
child: Text("FEEDBACK",
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white, fontSize: 22.0)),
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue