improved welcome page
This commit is contained in:
parent
c002291adf
commit
995242f239
4 changed files with 202 additions and 66 deletions
|
@ -37,6 +37,8 @@ class AvesApp extends StatefulWidget {
|
|||
class _AvesAppState extends State<AvesApp> {
|
||||
Future<void> _appSetup;
|
||||
|
||||
static const accentColor = Colors.indigoAccent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -49,8 +51,10 @@ class _AvesAppState extends State<AvesApp> {
|
|||
title: 'Aves',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
accentColor: Colors.indigoAccent,
|
||||
accentColor: accentColor,
|
||||
scaffoldBackgroundColor: Colors.grey[900],
|
||||
buttonColor: accentColor,
|
||||
toggleableActiveColor: accentColor,
|
||||
tooltipTheme: const TooltipThemeData(
|
||||
verticalOffset: 32,
|
||||
),
|
||||
|
|
|
@ -4,6 +4,9 @@ Aves is an open-source gallery and metadata explorer app allowing you to access
|
|||
|
||||
You must use the app for legal, authorized and acceptable purposes.
|
||||
|
||||
# Disclaimer
|
||||
This app is released "as-is", without any warranty, responsibility or liability. Use of the app is at your own risk.
|
||||
|
||||
# Privacy policy
|
||||
Aves does not collect any personal data in its standard use. We never have access to your photos and videos. This also means that we cannot get them back for you if you delete them without backing them up.
|
||||
|
||||
|
|
59
lib/widgets/common/labeled_checkbox.dart
Normal file
59
lib/widgets/common/labeled_checkbox.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LabeledCheckbox extends StatefulWidget {
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final String text;
|
||||
|
||||
const LabeledCheckbox({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@required this.onChanged,
|
||||
@required this.text,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LabeledCheckboxState createState() => _LabeledCheckboxState();
|
||||
}
|
||||
|
||||
class _LabeledCheckboxState extends State<LabeledCheckbox> {
|
||||
TapGestureRecognizer _tapRecognizer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tapRecognizer = TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
debugPrint('tapped');
|
||||
widget.onChanged(!widget.value);
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tapRecognizer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Checkbox(
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: widget.text,
|
||||
recognizer: _tapRecognizer,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,8 +2,10 @@ import 'package:aves/main.dart';
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/model/terms.dart';
|
||||
import 'package:aves/widgets/common/aves_logo.dart';
|
||||
import 'package:aves/widgets/common/labeled_checkbox.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class WelcomePage extends StatefulWidget {
|
||||
|
@ -18,7 +20,6 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accentColor = Theme.of(context).accentColor;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
|
@ -26,75 +27,144 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const AvesLogo(size: 64),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Welcome to Aves',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Concourse',
|
||||
children: _toStaggeredList(
|
||||
duration: const Duration(milliseconds: 375),
|
||||
childAnimationBuilder: (child) => SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.white10,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
child: MarkdownBody(
|
||||
data: termsAndConditions,
|
||||
onTapLink: (url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
), // const Text('Terms terms terms'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Checkbox(
|
||||
value: _hasAcceptedTerms,
|
||||
onChanged: (v) => setState(() => _hasAcceptedTerms = v),
|
||||
activeColor: accentColor,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: 'I accept the Terms of Service',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
RaisedButton(
|
||||
color: accentColor,
|
||||
child: const Text('Continue'),
|
||||
onPressed: _hasAcceptedTerms
|
||||
? () {
|
||||
settings.hasAcceptedTerms = true;
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const HomePage(),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
children: _buildChildren(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildChildren(BuildContext context) {
|
||||
return [
|
||||
..._buildTop(context),
|
||||
Flexible(child: _buildTerms()),
|
||||
..._buildBottomControls(context),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildTop(BuildContext context) {
|
||||
const message = Text(
|
||||
'Welcome to Aves',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontFamily: 'Concourse',
|
||||
),
|
||||
);
|
||||
return [
|
||||
...(MediaQuery.of(context).orientation == Orientation.portrait
|
||||
? [
|
||||
const AvesLogo(size: 64),
|
||||
const SizedBox(height: 16),
|
||||
message,
|
||||
]
|
||||
: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const AvesLogo(size: 48),
|
||||
const SizedBox(width: 16),
|
||||
message,
|
||||
],
|
||||
)
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildBottomControls(BuildContext context) {
|
||||
final checkbox = LabeledCheckbox(
|
||||
value: _hasAcceptedTerms,
|
||||
onChanged: (v) => setState(() => _hasAcceptedTerms = v),
|
||||
text: 'I agree to the terms and conditions',
|
||||
);
|
||||
final button = RaisedButton(
|
||||
child: const Text('Continue'),
|
||||
onPressed: _hasAcceptedTerms
|
||||
? () {
|
||||
settings.hasAcceptedTerms = true;
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const HomePage(),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
: null,
|
||||
);
|
||||
return MediaQuery.of(context).orientation == Orientation.portrait
|
||||
? [
|
||||
checkbox,
|
||||
button,
|
||||
]
|
||||
: [
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
checkbox,
|
||||
const Spacer(),
|
||||
button,
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _buildTerms() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.white10,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Markdown(
|
||||
data: termsAndConditions,
|
||||
// TODO TLAD make it selectable when this fix (in 1.18.0-6.0.pre) lands on stable: https://github.com/flutter/flutter/pull/54479
|
||||
selectable: false,
|
||||
onTapLink: (url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
shrinkWrap: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// workaround to handle `Flexible` widgets,
|
||||
// because `AnimationConfiguration.toStaggeredList` does not,
|
||||
// as of flutter_staggered_animations v0.1.2,
|
||||
static List<Widget> _toStaggeredList({
|
||||
Duration duration,
|
||||
Duration delay,
|
||||
@required Widget Function(Widget) childAnimationBuilder,
|
||||
@required List<Widget> children,
|
||||
}) =>
|
||||
children
|
||||
.asMap()
|
||||
.map((index, widget) {
|
||||
var child = widget is Flexible ? widget.child : widget;
|
||||
child = AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: duration,
|
||||
child: childAnimationBuilder(child),
|
||||
);
|
||||
child = widget is Flexible ? Flexible(child: child) : child;
|
||||
return MapEntry(
|
||||
index,
|
||||
child,
|
||||
);
|
||||
})
|
||||
.values
|
||||
.toList();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue