revamped onboarding
This commit is contained in:
@@ -16,7 +16,6 @@ final SavedTrips savedTrips = SavedTrips();
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp(
|
||||
title: APP_NAME,
|
||||
|
@@ -69,7 +69,6 @@ class _LandmarkCardState extends State<LandmarkCard> {
|
||||
padding: EdgeInsets.all(5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Icon(Icons.timer_outlined, size: 16),
|
||||
Text("${widget.landmark.duration?.inMinutes} minutes"),
|
||||
|
97
frontend/lib/modules/onbarding_agreement_card.dart
Normal file
97
frontend/lib/modules/onbarding_agreement_card.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
import 'package:anyway/modules/onboarding_card.dart';
|
||||
|
||||
|
||||
class OnboardingAgreementCard extends StatefulWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final String imagePath;
|
||||
final String agreementTextPath;
|
||||
final ValueChanged<bool> onAgreementChanged;
|
||||
|
||||
|
||||
OnboardingAgreementCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.imagePath,
|
||||
required this.agreementTextPath,
|
||||
required this.onAgreementChanged
|
||||
});
|
||||
|
||||
@override
|
||||
State<OnboardingAgreementCard> createState() => _OnboardingAgreementCardState();
|
||||
}
|
||||
|
||||
class _OnboardingAgreementCardState extends State<OnboardingAgreementCard> {
|
||||
bool agreed = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OnboardingCard(title: widget.title, description: widget.description, imagePath: widget.imagePath),
|
||||
Padding(padding: EdgeInsets.only(top: 20)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: agreed,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
agreed = value!;
|
||||
widget.onAgreementChanged(value);
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
"I agree to the ",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// show a dialog with the agreement text
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
content: FutureBuilder(
|
||||
future: DefaultAssetBundle.of(context).loadString(widget.agreementTextPath),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return MarkdownBody(
|
||||
data: snapshot.data.toString(),
|
||||
);
|
||||
} else {
|
||||
return CircularProgressIndicator();
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
},
|
||||
child: Text(
|
||||
"Terms of Service (click to view)",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -22,9 +22,7 @@ class OnboardingCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
@@ -36,13 +34,12 @@ class OnboardingCard extends StatelessWidget {
|
||||
Padding(padding: EdgeInsets.only(top: 20)),
|
||||
Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,33 +1,37 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/modules/onbarding_agreement_card.dart';
|
||||
import 'package:anyway/modules/onboarding_card.dart';
|
||||
import 'package:anyway/pages/new_trip_location.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const List<Widget> onboardingCards = [
|
||||
OnboardingCard(
|
||||
|
||||
List<Widget> onboardingCards = [
|
||||
const OnboardingCard(
|
||||
title: "Welcome to anyway!",
|
||||
description: "Anyway helps you plan a city trip that suits your wishes.",
|
||||
imagePath: "assets/city.svg"
|
||||
imagePath: "assets/cld-server.svg"
|
||||
),
|
||||
OnboardingCard(
|
||||
title: "Find your way",
|
||||
const OnboardingCard(
|
||||
title: "Do your thing",
|
||||
description: "Bored by churches? No problem! Hate shopping? No worries! Instead of suggesting the generic trips that bore you, anyway will try to give you recommendations that really suit you.",
|
||||
imagePath: "assets/plan.svg"
|
||||
imagePath: "assets/con-drill.svg"
|
||||
),
|
||||
OnboardingCard(
|
||||
const OnboardingCard(
|
||||
title: "Change your mind",
|
||||
description: "Feet get sore, the weather changes. Anyway understands that! Move or remove destinations, visit hidden gems along your journey, do your own thing. Anyway adapts to your spontaneous decisions.",
|
||||
imagePath: "assets/cat.svg"
|
||||
imagePath: "assets/cel-snow-globe.svg"
|
||||
),
|
||||
OnboardingCard(
|
||||
const OnboardingCard(
|
||||
title: "Feeling lost?",
|
||||
description: "Whenever you are confused or need help with the app, look out for the question mark in the top right corner. Help is just a tap away!",
|
||||
imagePath: "assets/confused.svg"
|
||||
imagePath: "assets/gen-lifebelt.svg"
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@@ -37,9 +41,24 @@ class OnboardingPage extends StatefulWidget {
|
||||
|
||||
class _OnboardingPageState extends State<OnboardingPage> {
|
||||
final PageController _controller = PageController();
|
||||
late List<Widget> fullCards;
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Widget agreementCard = OnboardingAgreementCard(
|
||||
title: "The annoying stuff",
|
||||
description: "By using anyway, you agree to our terms and conditions and privacy policy. We don't use cookies or tracking, we don't store the data you submit. We are not responsible for any damage or loss caused by using anyway.",
|
||||
imagePath: "assets/con-warning.svg",
|
||||
agreementTextPath: "assets/terms_and_conditions.md",
|
||||
onAgreementChanged: onAgreementChanged,
|
||||
);
|
||||
// need to add the agreement from within the function because it needs to be able to call setState
|
||||
fullCards = onboardingCards + [agreementCard];
|
||||
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
@@ -55,8 +74,8 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
end: Alignment.bottomRight,
|
||||
colors: APP_GRADIENT.colors,
|
||||
stops: [
|
||||
(_controller.hasClients ? _controller.page ?? _controller.initialPage : _controller.initialPage) / onboardingCards.length,
|
||||
(_controller.hasClients ? _controller.page ?? _controller.initialPage + 1 : _controller.initialPage + 1) / onboardingCards.length,
|
||||
(_controller.hasClients ? (_controller.page ?? _controller.initialPage) : _controller.initialPage) / onboardingCards.length,
|
||||
(_controller.hasClients ? (_controller.page ?? _controller.initialPage) + 1 : _controller.initialPage + 1) / onboardingCards.length,
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -64,7 +83,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100),
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0),
|
||||
color: Colors.black.withValues(alpha: 0.2)
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -74,46 +93,73 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
PageView(
|
||||
controller: _controller,
|
||||
children: List.generate(
|
||||
onboardingCards.length,
|
||||
fullCards.length,
|
||||
(index) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: onboardingCards[index],
|
||||
child: fullCards[index],
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
if (_controller.page == onboardingCards.length - 1) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const NewTripPage()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
_controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease);
|
||||
}
|
||||
},
|
||||
label: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
if ((_controller.page ?? _controller.initialPage) == onboardingCards.length - 1) {
|
||||
return Row(
|
||||
children: [
|
||||
const Text("Start planning!"),
|
||||
Padding(padding: const EdgeInsets.only(right: 8.0)),
|
||||
const Icon(Icons.map_outlined)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Icon(Icons.arrow_forward);
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
floatingActionButton: nextButton(_controller)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget nextButton(PageController controller) => AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
if ((_controller.hasClients ? (_controller.page ?? _controller.initialPage) : 0) != fullCards.length - 1) {
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
controller.nextPage(duration: Duration(milliseconds: 500), curve: Curves.ease);
|
||||
},
|
||||
label: Icon(Icons.arrow_forward)
|
||||
);
|
||||
} else {
|
||||
// only allow the user to proceed if they have agreed to the terms and conditions
|
||||
Future<bool> hasAgreed = SharedPreferences.getInstance().then(
|
||||
(SharedPreferences prefs) {
|
||||
return prefs.getBool('TC_agree') ?? false;
|
||||
}
|
||||
);
|
||||
|
||||
return FutureBuilder(
|
||||
future: hasAgreed,
|
||||
builder: (context, snapshot){
|
||||
if (snapshot.hasData && snapshot.data!) {
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const NewTripPage()
|
||||
)
|
||||
);
|
||||
},
|
||||
label: const Row(
|
||||
children: [
|
||||
Text("Start planning!"),
|
||||
Padding(padding: EdgeInsets.only(right: 8.0)),
|
||||
Icon(Icons.map_outlined)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
void onAgreementChanged(bool value) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('TC_agree', value);
|
||||
// Update the state of the OnboardingPage
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/layouts/scaffold.dart';
|
||||
import 'package:anyway/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:anyway/main.dart';
|
||||
import 'package:anyway/constants.dart';
|
||||
import 'package:anyway/layouts/scaffold.dart';
|
||||
|
||||
|
||||
bool debugMode = false;
|
||||
|
||||
|
Reference in New Issue
Block a user