improved welcome page

This commit is contained in:
Thibault Deckers 2020-06-10 19:19:29 +09:00
parent c002291adf
commit 995242f239
4 changed files with 202 additions and 66 deletions

View file

@ -37,6 +37,8 @@ class AvesApp extends StatefulWidget {
class _AvesAppState extends State<AvesApp> { class _AvesAppState extends State<AvesApp> {
Future<void> _appSetup; Future<void> _appSetup;
static const accentColor = Colors.indigoAccent;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -49,8 +51,10 @@ class _AvesAppState extends State<AvesApp> {
title: 'Aves', title: 'Aves',
theme: ThemeData( theme: ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
accentColor: Colors.indigoAccent, accentColor: accentColor,
scaffoldBackgroundColor: Colors.grey[900], scaffoldBackgroundColor: Colors.grey[900],
buttonColor: accentColor,
toggleableActiveColor: accentColor,
tooltipTheme: const TooltipThemeData( tooltipTheme: const TooltipThemeData(
verticalOffset: 32, verticalOffset: 32,
), ),

View file

@ -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. 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 # 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. 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.

View 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,
),
],
),
);
}
}

View file

@ -2,8 +2,10 @@ import 'package:aves/main.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/model/terms.dart'; import 'package:aves/model/terms.dart';
import 'package:aves/widgets/common/aves_logo.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/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class WelcomePage extends StatefulWidget { class WelcomePage extends StatefulWidget {
@ -18,7 +20,6 @@ class _WelcomePageState extends State<WelcomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final accentColor = Theme.of(context).accentColor;
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
child: Container( child: Container(
@ -26,57 +27,66 @@ class _WelcomePageState extends State<WelcomePage> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: _toStaggeredList(
const AvesLogo(size: 64), duration: const Duration(milliseconds: 375),
const SizedBox(height: 16), childAnimationBuilder: (child) => SlideAnimation(
const Text( verticalOffset: 50.0,
child: FadeInAnimation(
child: child,
),
),
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', 'Welcome to Aves',
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontFamily: 'Concourse', fontFamily: 'Concourse',
), ),
), );
return [
...(MediaQuery.of(context).orientation == Orientation.portrait
? [
const AvesLogo(size: 64),
const SizedBox(height: 16), const SizedBox(height: 16),
Flexible( message,
child: Container( ]
padding: const EdgeInsets.all(16), : [
decoration: BoxDecoration( Row(
borderRadius: BorderRadius.circular(16), mainAxisSize: MainAxisSize.min,
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: [ children: [
WidgetSpan( const AvesLogo(size: 48),
alignment: PlaceholderAlignment.middle, const SizedBox(width: 16),
child: Checkbox( message,
],
)
]),
const SizedBox(height: 16),
];
}
List<Widget> _buildBottomControls(BuildContext context) {
final checkbox = LabeledCheckbox(
value: _hasAcceptedTerms, value: _hasAcceptedTerms,
onChanged: (v) => setState(() => _hasAcceptedTerms = v), onChanged: (v) => setState(() => _hasAcceptedTerms = v),
activeColor: accentColor, text: 'I agree to the terms and conditions',
), );
), final button = RaisedButton(
const TextSpan(
text: 'I accept the Terms of Service',
),
],
),
),
RaisedButton(
color: accentColor,
child: const Text('Continue'), child: const Text('Continue'),
onPressed: _hasAcceptedTerms onPressed: _hasAcceptedTerms
? () { ? () {
@ -90,11 +100,71 @@ class _WelcomePageState extends State<WelcomePage> {
); );
} }
: null, : 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();
} }