chore(wip): upgrade dependencies, begin refactor
This commit is contained in:
50
frontend/lib/presentation/pages/create_trip.dart
Normal file
50
frontend/lib/presentation/pages/create_trip.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewTripPage extends StatefulWidget {
|
||||
const NewTripPage({super.key});
|
||||
|
||||
@override
|
||||
_NewTripPageState createState() => _NewTripPageState();
|
||||
}
|
||||
|
||||
|
||||
class _NewTripPageState extends State<NewTripPage> {
|
||||
int _currentStep = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Create New Trip'),
|
||||
),
|
||||
body: Stepper(
|
||||
currentStep: _currentStep,
|
||||
onStepContinue: () {
|
||||
if (_currentStep < 1) {
|
||||
setState(() {
|
||||
_currentStep += 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
onStepCancel: () {
|
||||
if (_currentStep > 0) {
|
||||
setState(() {
|
||||
_currentStep -= 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
Step(
|
||||
title: const Text('Select Location'),
|
||||
content: const MapWithSearchField(), // Replace with your map module
|
||||
isActive: _currentStep >= 0,
|
||||
),
|
||||
Step(
|
||||
title: const Text('Choose Options'),
|
||||
content: const OptionsList(), // Replace with your options module
|
||||
isActive: _currentStep >= 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
12
frontend/lib/presentation/pages/login.dart
Normal file
12
frontend/lib/presentation/pages/login.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(child: Text('Login Page')),
|
||||
);
|
||||
}
|
||||
}
|
||||
172
frontend/lib/presentation/pages/onboarding.dart
Normal file
172
frontend/lib/presentation/pages/onboarding.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:anyway/core/constants.dart';
|
||||
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
|
||||
import 'package:anyway/presentation/widgets/onbarding_agreement_card.dart';
|
||||
import 'package:anyway/presentation/widgets/onboarding_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
|
||||
List<Widget> onboardingCards = [
|
||||
const OnboardingCard(
|
||||
title: "Welcome to anyway!",
|
||||
description: "Anyway helps you plan a city trip that suits your wishes.",
|
||||
imagePath: "assets/cld-server.svg"
|
||||
),
|
||||
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/con-drill.svg"
|
||||
),
|
||||
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/cel-snow-globe.svg"
|
||||
),
|
||||
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/gen-lifebelt.svg"
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
class OnboardingPage extends ConsumerStatefulWidget {
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<OnboardingPage> createState() => _OnboardingPageState();
|
||||
}
|
||||
|
||||
class _OnboardingPageState extends ConsumerState<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: [
|
||||
AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
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,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2)
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
PageView(
|
||||
controller: _controller,
|
||||
children: List.generate(
|
||||
fullCards.length,
|
||||
(index) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: fullCards[index],
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
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: const Duration(milliseconds: 500), curve: Curves.ease);
|
||||
},
|
||||
label: const Icon(Icons.arrow_forward)
|
||||
);
|
||||
} else {
|
||||
// only allow the user to proceed if they have agreed to the terms and conditions
|
||||
// the information is accessible through ref.watch(onboardingStateProvider)
|
||||
|
||||
Widget disabledWidget = FloatingActionButton.extended(
|
||||
onPressed: null,
|
||||
label: const Row(
|
||||
children: [
|
||||
Text("Start planning!"),
|
||||
Padding(padding: EdgeInsets.only(right: 8.0)),
|
||||
Icon(Icons.map_outlined)
|
||||
],
|
||||
)
|
||||
);
|
||||
|
||||
return ref.watch(onboardingStateProvider).when(
|
||||
data: (isOnboarded) {
|
||||
if (isOnboarded) {
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
// proceed to the next page - pop the onboarding page so the StartPage can decide what to show next
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
label: const Row(
|
||||
children: [
|
||||
Text("Start planning!"),
|
||||
Padding(padding: EdgeInsets.only(right: 8.0)),
|
||||
Icon(Icons.map_outlined)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return disabledWidget;
|
||||
}
|
||||
},
|
||||
loading: () => disabledWidget,
|
||||
error: (error, stack) => disabledWidget,
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
void onAgreementChanged(bool value) async {
|
||||
await ref.read(onboardingControllerProvider).setOnboarded(value);
|
||||
|
||||
setState(() {
|
||||
// rebuild to show the next button
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
97
frontend/lib/presentation/pages/start.dart
Normal file
97
frontend/lib/presentation/pages/start.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
|
||||
import 'package:anyway/presentation/pages/login.dart';
|
||||
import 'package:anyway/presentation/pages/onboarding.dart';
|
||||
|
||||
// Example providers (replace these with your actual providers)
|
||||
// final onboardingStateProvider = Provider<bool>((ref) => true); // Replace with actual onboarding state logic
|
||||
final authStateProvider = FutureProvider<bool>((ref) async => true); // Replace with actual auth state logic
|
||||
final tripsAvailableProvider = FutureProvider<bool>((ref) async => false); // Replace with actual trips availability logic
|
||||
|
||||
class StartPage extends ConsumerWidget {
|
||||
const StartPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
|
||||
// the home page is dependent on the state of the providers:
|
||||
// - if the user is not onboarded, show the onboarding flow
|
||||
// - if the user is not logged in, show the login page
|
||||
// - if there are no trips available, show the trip creation page
|
||||
// - else: show the overview page that shows the last trip
|
||||
|
||||
final onboardingState = ref.watch(onboardingStateProvider);
|
||||
final authState = ref.watch(authStateProvider);
|
||||
final tripsAvailable = ref.watch(tripsAvailableProvider);
|
||||
|
||||
return onboardingState.when(
|
||||
data: (isOnboarded) {
|
||||
if (!isOnboarded) {
|
||||
return const OnboardingPage();
|
||||
}
|
||||
|
||||
return authState.when(
|
||||
data: (isLoggedIn) {
|
||||
if (!isLoggedIn) {
|
||||
return const LoginPage();
|
||||
}
|
||||
|
||||
return tripsAvailable.when(
|
||||
data: (hasTrips) {
|
||||
if (!hasTrips) {
|
||||
return const TripCreationPage();
|
||||
}
|
||||
return const OverviewPage();
|
||||
},
|
||||
loading: () => const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
body: Center(child: Text('Error: $error')),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
body: Center(child: Text('Error: $error')),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
body: Center(child: Text('Error: $error')
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class TripCreationPage extends StatelessWidget {
|
||||
const TripCreationPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(child: Text('Trip Creation Page')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OverviewPage extends StatelessWidget {
|
||||
const OverviewPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(child: Text('Overview Page')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
final onboardingStateProvider = FutureProvider<bool>((ref) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool('onboardingCompleted') ?? false;
|
||||
});
|
||||
|
||||
|
||||
final onboardingControllerProvider = Provider<OnboardingController>((ref) {
|
||||
return OnboardingController(ref);
|
||||
});
|
||||
|
||||
class OnboardingController {
|
||||
final Ref ref;
|
||||
OnboardingController(this.ref);
|
||||
|
||||
Future<void> setOnboarded(bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('onboardingCompleted', value);
|
||||
// refresh the read provider so UI updates
|
||||
ref.invalidate(onboardingStateProvider);
|
||||
}
|
||||
}
|
||||
6
frontend/lib/presentation/providers/trip_provider.dart
Normal file
6
frontend/lib/presentation/providers/trip_provider.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
// import 'package:anyway/data/repositories/trip_repository.dart';
|
||||
// import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// final weatherRepositoryProvider = Provider<TripRepository>((ref) {
|
||||
// return TripRepositoryImpl(???);
|
||||
// });
|
||||
23
frontend/lib/presentation/widgets/create_trip_location.dart
Normal file
23
frontend/lib/presentation/widgets/create_trip_location.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:your_project/models/trip_request.dart';
|
||||
import 'package:your_project/presentation/widgets/new_trip_map.dart';
|
||||
import 'package:your_project/presentation/widgets/new_trip_location_search.dart';
|
||||
|
||||
class CreateTripLocation extends StatelessWidget {
|
||||
final TripRequest tripRequest;
|
||||
|
||||
const CreateTripLocation(this.tripRequest, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
NewTripMap(tripRequest),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: NewTripLocationSearch(tripRequest),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
105
frontend/lib/presentation/widgets/create_trip_location_map.dart
Normal file
105
frontend/lib/presentation/widgets/create_trip_location_map.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
// A map that allows the user to select a location for a new trip.
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:widget_to_marker/widget_to_marker.dart';
|
||||
|
||||
import 'package:anyway/constants.dart';
|
||||
|
||||
import 'package:anyway/structs/trip.dart';
|
||||
import 'package:anyway/structs/landmark.dart';
|
||||
import 'package:anyway/modules/landmark_map_marker.dart';
|
||||
|
||||
|
||||
class NewTripMap extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<NewTripMap> createState() => _NewTripMapState();
|
||||
}
|
||||
|
||||
class _NewTripMapState extends State<NewTripMap> {
|
||||
final CameraPosition _cameraPosition = const CameraPosition(
|
||||
target: LatLng(48.8566, 2.3522),
|
||||
zoom: 11.0,
|
||||
);
|
||||
GoogleMapController? _mapController;
|
||||
final Set<Marker> _markers = <Marker>{};
|
||||
|
||||
_onLongPress(LatLng location) {
|
||||
// widget.trip.landmarks.clear();
|
||||
// widget.trip.addLandmark(
|
||||
// Landmark(
|
||||
// uuid: 'pending',
|
||||
// name: 'start',
|
||||
// location: [location.latitude, location.longitude],
|
||||
// type: typeStart
|
||||
// )
|
||||
// );
|
||||
}
|
||||
|
||||
updateTripDetails() async {
|
||||
_markers.clear();
|
||||
if (widget.trip.landmarks.isNotEmpty) {
|
||||
Landmark landmark = widget.trip.landmarks.first;
|
||||
_markers.add(
|
||||
Marker(
|
||||
markerId: MarkerId(landmark.uuid),
|
||||
position: LatLng(landmark.location[0], landmark.location[1]),
|
||||
icon: await ThemedMarker(landmark: landmark, position: 0).toBitmapDescriptor(
|
||||
logicalSize: const Size(150, 150),
|
||||
imageSize: const Size(150, 150)
|
||||
),
|
||||
)
|
||||
);
|
||||
// check if the controller is ready
|
||||
|
||||
if (_mapController != null) {
|
||||
_mapController!.animateCamera(
|
||||
CameraUpdate.newLatLng(
|
||||
LatLng(landmark.location[0], landmark.location[1])
|
||||
)
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _onMapCreated(GoogleMapController controller) async {
|
||||
_mapController = controller;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
widget.trip.addListener(updateTripDetails);
|
||||
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
|
||||
|
||||
|
||||
return FutureBuilder(
|
||||
future: preferences,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
SharedPreferences prefs = snapshot.data as SharedPreferences;
|
||||
bool useLocation = prefs.getBool('useLocation') ?? true;
|
||||
return _buildMap(useLocation);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap(bool useLocation) {
|
||||
return GoogleMap(
|
||||
onMapCreated: _onMapCreated,
|
||||
initialCameraPosition: _cameraPosition,
|
||||
onLongPress: _onLongPress,
|
||||
markers: _markers,
|
||||
cloudMapId: MAP_ID,
|
||||
mapToolbarEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
myLocationEnabled: useLocation,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
|
||||
// A search bar that allow the user to enter a city name
|
||||
import 'dart:developer';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const Map<String, List> debugLocations = {
|
||||
'paris': [48.8575, 2.3514],
|
||||
'london': [51.5074, -0.1278],
|
||||
'new york': [40.7128, -74.0060],
|
||||
'tokyo': [35.6895, 139.6917],
|
||||
};
|
||||
|
||||
|
||||
|
||||
class NewTripLocationSearch extends StatefulWidget {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
Trip trip;
|
||||
|
||||
NewTripLocationSearch(
|
||||
this.trip,
|
||||
);
|
||||
|
||||
|
||||
@override
|
||||
State<NewTripLocationSearch> createState() => _NewTripLocationSearchState();
|
||||
}
|
||||
|
||||
class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
|
||||
setTripLocation (String query) async {
|
||||
List<Location> locations = [];
|
||||
Location startLocation;
|
||||
log('Searching for: $query');
|
||||
if (GeocodingPlatform.instance != null) {
|
||||
locations.addAll(await locationFromAddress(query));
|
||||
}
|
||||
|
||||
if (locations.isNotEmpty) {
|
||||
startLocation = locations.first;
|
||||
} else {
|
||||
log('No results found for: $query. Is geocoding available?');
|
||||
log('Setting Fallback location');
|
||||
List coordinates = debugLocations[query.toLowerCase()] ?? [48.8575, 2.3514];
|
||||
startLocation = Location(
|
||||
latitude: coordinates[0],
|
||||
longitude: coordinates[1],
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
widget.trip.landmarks.clear();
|
||||
widget.trip.addLandmark(
|
||||
Landmark(
|
||||
uuid: 'pending',
|
||||
name: query,
|
||||
location: [startLocation.latitude, startLocation.longitude],
|
||||
type: typeStart
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
late Widget locationSearchBar = SearchBar(
|
||||
hintText: 'Enter a city name or long press on the map.',
|
||||
onSubmitted: setTripLocation,
|
||||
controller: _controller,
|
||||
leading: const Icon(Icons.search),
|
||||
trailing: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
setTripLocation(_controller.text);
|
||||
},
|
||||
child: const Text('Search'),
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
late Widget useCurrentLocationButton = ElevatedButton(
|
||||
onPressed: () async {
|
||||
// this widget is only shown if the user has already granted location permissions
|
||||
Position position = await Geolocator.getCurrentPosition();
|
||||
widget.trip.landmarks.clear();
|
||||
widget.trip.addLandmark(
|
||||
Landmark(
|
||||
uuid: 'pending',
|
||||
name: 'start',
|
||||
location: [position.latitude, position.longitude],
|
||||
type: typeStart
|
||||
)
|
||||
);
|
||||
},
|
||||
child: const Text('Use current location'),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: widget.prefs,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final useLocation = snapshot.data!.getBool('useLocation') ?? false;
|
||||
if (useLocation) {
|
||||
return Column(
|
||||
children: [
|
||||
locationSearchBar,
|
||||
useCurrentLocationButton,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return locationSearchBar;
|
||||
}
|
||||
} else {
|
||||
return locationSearchBar;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
121
frontend/lib/presentation/widgets/onbarding_agreement_card.dart
Normal file
121
frontend/lib/presentation/widgets/onbarding_agreement_card.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
|
||||
import 'package:anyway/presentation/widgets/onboarding_card.dart';
|
||||
|
||||
|
||||
|
||||
class OnboardingAgreementCard extends ConsumerStatefulWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final String imagePath;
|
||||
final String agreementTextPath;
|
||||
final ValueChanged<bool> onAgreementChanged;
|
||||
|
||||
|
||||
const OnboardingAgreementCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.imagePath,
|
||||
required this.agreementTextPath,
|
||||
required this.onAgreementChanged
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<OnboardingAgreementCard> createState() => _OnboardingAgreementCardState();
|
||||
|
||||
}
|
||||
|
||||
class _OnboardingAgreementCardState extends ConsumerState<OnboardingAgreementCard> {
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// // You can use ref here if needed in initState
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OnboardingCard(title: widget.title, description: widget.description, imagePath: widget.imagePath),
|
||||
const Padding(padding: EdgeInsets.only(top: 20)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// The checkbox of the agreement
|
||||
FutureBuilder(
|
||||
future: ref.read(onboardingStateProvider.future),
|
||||
builder: (context, snapshot) {
|
||||
bool agreed = false;
|
||||
if (snapshot.hasData) {
|
||||
agreed = snapshot.data!;
|
||||
}
|
||||
return Checkbox(
|
||||
value: agreed,
|
||||
onChanged: (bool? value) {
|
||||
if (value != null) {
|
||||
widget.onAgreementChanged(value);
|
||||
}
|
||||
},
|
||||
activeColor: Colors.white,
|
||||
checkColor: Colors.black,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// The text of the agreement
|
||||
Text(
|
||||
"I agree to the ",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
// The clickable text of the agreement that shows the agreement text
|
||||
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 const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
},
|
||||
child: Text(
|
||||
"Terms of Service (click to view)",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
45
frontend/lib/presentation/widgets/onboarding_card.dart
Normal file
45
frontend/lib/presentation/widgets/onboarding_card.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class OnboardingCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final String imagePath;
|
||||
|
||||
const OnboardingCard({super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.imagePath,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 20)),
|
||||
SvgPicture.asset(
|
||||
imagePath,
|
||||
height: 200,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 20)),
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user