mirror of
https://github.com/flutter/samples.git
synced 2026-04-24 07:51:04 +00:00
Compass app (#2446)
This commit is contained in:
121
compass_app/app/lib/ui/core/localization/applocalization.dart
Normal file
121
compass_app/app/lib/ui/core/localization/applocalization.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Simple Localizations similar to
|
||||
/// https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#an-alternative-class-for-the-apps-localized-resources
|
||||
class AppLocalization {
|
||||
static AppLocalization of(BuildContext context) {
|
||||
return Localizations.of(context, AppLocalization);
|
||||
}
|
||||
|
||||
static const _strings = <String, String>{
|
||||
'activities': 'Activities',
|
||||
'addDates': 'Add Dates',
|
||||
'bookingDeleted': 'Booking deleted',
|
||||
'bookNewTrip': 'Book New Trip',
|
||||
'close': 'Close',
|
||||
'confirm': 'Confirm',
|
||||
'daytime': 'Daytime',
|
||||
'errorWhileDeletingBooking': 'Error while deleting booking',
|
||||
'errorWhileLoadingActivities': 'Error while loading activities',
|
||||
'errorWhileLoadingBooking': 'Error while loading booking',
|
||||
'errorWhileLoadingContinents': 'Error while loading continents',
|
||||
'errorWhileLoadingDestinations': 'Error while loading destinations',
|
||||
'errorWhileLoadingHome': 'Error while loading home',
|
||||
'errorWhileLogin': 'Error while trying to login',
|
||||
'errorWhileLogout': 'Error while trying to logout',
|
||||
'errorWhileSavingActivities': 'Error while saving activities',
|
||||
'errorWhileSavingItinerary': 'Error while saving itinerary',
|
||||
'errorWhileSharing': 'Error while sharing booking',
|
||||
'evening': 'Evening',
|
||||
'login': 'Login',
|
||||
'nameTrips': '{name}\'s Trips',
|
||||
'search': 'Search',
|
||||
'searchDestination': 'Search destination',
|
||||
'selected': '{1} selected',
|
||||
'shareTrip': 'Share Trip',
|
||||
'tryAgain': 'Try again',
|
||||
'yourChosenActivities': 'Your chosen activities',
|
||||
'when': 'When',
|
||||
};
|
||||
|
||||
// If string for "label" does not exist, will show "[LABEL]"
|
||||
static String _get(String label) =>
|
||||
_strings[label] ?? '[${label.toUpperCase()}]';
|
||||
|
||||
String get activities => _get('activities');
|
||||
|
||||
String get addDates => _get('addDates');
|
||||
|
||||
String get confirm => _get('confirm');
|
||||
|
||||
String get daytime => _get('daytime');
|
||||
|
||||
String get errorWhileLoadingActivities => _get('errorWhileLoadingActivities');
|
||||
|
||||
String get errorWhileLoadingBooking => _get('errorWhileLoadingBooking');
|
||||
|
||||
String get errorWhileLoadingContinents => _get('errorWhileLoadingContinents');
|
||||
|
||||
String get errorWhileLoadingDestinations =>
|
||||
_get('errorWhileLoadingDestinations');
|
||||
|
||||
String get errorWhileSavingActivities => _get('errorWhileSavingActivities');
|
||||
|
||||
String get errorWhileSavingItinerary => _get('errorWhileSavingItinerary');
|
||||
|
||||
String get evening => _get('evening');
|
||||
|
||||
String get search => _get('search');
|
||||
|
||||
String get searchDestination => _get('searchDestination');
|
||||
|
||||
String get shareTrip => _get('shareTrip');
|
||||
|
||||
String get tryAgain => _get('tryAgain');
|
||||
|
||||
String get yourChosenActivities => _get('yourChosenActivities');
|
||||
|
||||
String get when => _get('when');
|
||||
|
||||
String get errorWhileLogin => _get('errorWhileLogin');
|
||||
|
||||
String get login => _get('login');
|
||||
|
||||
String get errorWhileLogout => _get('errorWhileLogout');
|
||||
|
||||
String get close => _get('close');
|
||||
|
||||
String get errorWhileSharing => _get('errorWhileSharing');
|
||||
|
||||
String get bookNewTrip => _get('bookNewTrip');
|
||||
|
||||
String get errorWhileLoadingHome => _get('errorWhileLoadingHome');
|
||||
|
||||
String get bookingDeleted => _get('bookingDeleted');
|
||||
|
||||
String get errorWhileDeletingBooking => _get('errorWhileDeletingBooking');
|
||||
|
||||
String nameTrips(String name) => _get('nameTrips').replaceAll('{name}', name);
|
||||
|
||||
String selected(int value) =>
|
||||
_get('selected').replaceAll('{1}', value.toString());
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalization> {
|
||||
@override
|
||||
bool isSupported(Locale locale) => locale.languageCode == 'en';
|
||||
|
||||
@override
|
||||
Future<AppLocalization> load(Locale locale) {
|
||||
return SynchronousFuture(AppLocalization());
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(covariant LocalizationsDelegate<AppLocalization> old) =>
|
||||
false;
|
||||
}
|
||||
41
compass_app/app/lib/ui/core/themes/colors.dart
Normal file
41
compass_app/app/lib/ui/core/themes/colors.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppColors {
|
||||
static const black1 = Color(0xFF101010);
|
||||
static const white1 = Color(0xFFFFF7FA);
|
||||
static const grey1 = Color(0xFFF2F2F2);
|
||||
static const grey2 = Color(0xFF4D4D4D);
|
||||
static const grey3 = Color(0xFFA4A4A4);
|
||||
static const whiteTransparent =
|
||||
Color(0x4DFFFFFF); // Figma rgba(255, 255, 255, 0.3)
|
||||
static const blackTransparent = Color(0x4D000000);
|
||||
static const red1 = Color(0xFFE74C3C);
|
||||
|
||||
static const lightColorScheme = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: AppColors.black1,
|
||||
onPrimary: AppColors.white1,
|
||||
secondary: AppColors.black1,
|
||||
onSecondary: AppColors.white1,
|
||||
surface: Colors.white,
|
||||
onSurface: AppColors.black1,
|
||||
error: Colors.white,
|
||||
onError: Colors.red,
|
||||
);
|
||||
|
||||
static const darkColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: AppColors.white1,
|
||||
onPrimary: AppColors.black1,
|
||||
secondary: AppColors.white1,
|
||||
onSecondary: AppColors.black1,
|
||||
surface: AppColors.black1,
|
||||
onSurface: Colors.white,
|
||||
error: Colors.black,
|
||||
onError: AppColors.red1,
|
||||
);
|
||||
}
|
||||
65
compass_app/app/lib/ui/core/themes/dimens.dart
Normal file
65
compass_app/app/lib/ui/core/themes/dimens.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
sealed class Dimens {
|
||||
const Dimens();
|
||||
|
||||
/// General horizontal padding used to separate UI items
|
||||
static const paddingHorizontal = 20.0;
|
||||
|
||||
/// General vertical padding used to separate UI items
|
||||
static const paddingVertical = 24.0;
|
||||
|
||||
/// Horizontal padding for screen edges
|
||||
abstract final double paddingScreenHorizontal;
|
||||
|
||||
/// Vertical padding for screen edges
|
||||
abstract final double paddingScreenVertical;
|
||||
|
||||
/// Horizontal symmetric padding for screen edges
|
||||
EdgeInsets get edgeInsetsScreenHorizontal =>
|
||||
EdgeInsets.symmetric(horizontal: paddingScreenHorizontal);
|
||||
|
||||
/// Symmetric padding for screen edges
|
||||
EdgeInsets get edgeInsetsScreenSymmetric => EdgeInsets.symmetric(
|
||||
horizontal: paddingScreenHorizontal, vertical: paddingScreenVertical);
|
||||
|
||||
static final dimensDesktop = DimensDesktop();
|
||||
static final dimensMobile = DimensMobile();
|
||||
|
||||
/// Get dimensions definition based on screen size
|
||||
factory Dimens.of(BuildContext context) =>
|
||||
switch (MediaQuery.sizeOf(context).width) {
|
||||
> 600 => dimensDesktop,
|
||||
_ => dimensMobile,
|
||||
};
|
||||
|
||||
abstract final double profilePictureSize;
|
||||
}
|
||||
|
||||
/// Mobile dimensions
|
||||
class DimensMobile extends Dimens {
|
||||
@override
|
||||
double paddingScreenHorizontal = Dimens.paddingHorizontal;
|
||||
|
||||
@override
|
||||
double paddingScreenVertical = Dimens.paddingVertical;
|
||||
|
||||
@override
|
||||
double get profilePictureSize => 64.0;
|
||||
}
|
||||
|
||||
/// Desktop/Web dimensions
|
||||
class DimensDesktop extends Dimens {
|
||||
@override
|
||||
double paddingScreenHorizontal = 100.0;
|
||||
|
||||
@override
|
||||
double paddingScreenVertical = 64.0;
|
||||
|
||||
@override
|
||||
double get profilePictureSize => 128.0;
|
||||
}
|
||||
84
compass_app/app/lib/ui/core/themes/theme.dart
Normal file
84
compass_app/app/lib/ui/core/themes/theme.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'colors.dart';
|
||||
import '../ui/tag_chip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
static const _textTheme = TextTheme(
|
||||
headlineLarge: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
);
|
||||
|
||||
static const _inputDecorationTheme = InputDecorationTheme(
|
||||
hintStyle: TextStyle(
|
||||
// grey3 works for both light and dark themes
|
||||
color: AppColors.grey3,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
);
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: AppColors.lightColorScheme,
|
||||
textTheme: _textTheme,
|
||||
inputDecorationTheme: _inputDecorationTheme,
|
||||
extensions: [
|
||||
TagChipTheme(
|
||||
chipColor: AppColors.whiteTransparent,
|
||||
onChipColor: Colors.white,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static ThemeData darkTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: AppColors.darkColorScheme,
|
||||
textTheme: _textTheme,
|
||||
inputDecorationTheme: _inputDecorationTheme,
|
||||
extensions: [
|
||||
TagChipTheme(
|
||||
chipColor: AppColors.blackTransparent,
|
||||
onChipColor: Colors.white,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
63
compass_app/app/lib/ui/core/ui/back_button.dart
Normal file
63
compass_app/app/lib/ui/core/ui/back_button.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
import 'blur_filter.dart';
|
||||
|
||||
/// Custom back button to pop navigation.
|
||||
class CustomBackButton extends StatelessWidget {
|
||||
const CustomBackButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.blur = false,
|
||||
});
|
||||
|
||||
final bool blur;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (blur)
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: kBlurFilter,
|
||||
child: const SizedBox(height: 40.0, width: 40.0),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: Icon(
|
||||
size: 24.0,
|
||||
Icons.arrow_back,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
compass_app/app/lib/ui/core/ui/blur_filter.dart
Normal file
7
compass_app/app/lib/ui/core/ui/blur_filter.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2024 The Flutter team. 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:ui';
|
||||
|
||||
final kBlurFilter = ImageFilter.blur(sigmaX: 2, sigmaY: 2);
|
||||
50
compass_app/app/lib/ui/core/ui/custom_checkbox.dart
Normal file
50
compass_app/app/lib/ui/core/ui/custom_checkbox.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
|
||||
class CustomCheckbox extends StatelessWidget {
|
||||
const CustomCheckbox({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final bool value;
|
||||
final ValueChanged<bool?> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkResponse(
|
||||
radius: 24,
|
||||
onTap: () => onChanged(!value),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: AppColors.grey3),
|
||||
),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: value
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Visibility(
|
||||
visible: value,
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
size: 14,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
compass_app/app/lib/ui/core/ui/date_format_start_end.dart
Normal file
24
compass_app/app/lib/ui/core/ui/date_format_start_end.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
final _dateFormatDay = DateFormat('d');
|
||||
final _dateFormatDayMonth = DateFormat('d MMM');
|
||||
|
||||
String dateFormatStartEnd(DateTimeRange dateTimeRange) {
|
||||
final start = dateTimeRange.start;
|
||||
final end = dateTimeRange.end;
|
||||
|
||||
final dayMonthEnd = _dateFormatDayMonth.format(end);
|
||||
|
||||
if (start.month == end.month) {
|
||||
final dayStart = _dateFormatDay.format(start);
|
||||
return '$dayStart - $dayMonthEnd';
|
||||
}
|
||||
|
||||
final dayMonthStart = _dateFormatDayMonth.format(start);
|
||||
return '$dayMonthStart - $dayMonthEnd';
|
||||
}
|
||||
63
compass_app/app/lib/ui/core/ui/error_indicator.dart
Normal file
63
compass_app/app/lib/ui/core/ui/error_indicator.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
|
||||
class ErrorIndicator extends StatelessWidget {
|
||||
const ErrorIndicator({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IntrinsicWidth(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: onPressed,
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(AppColors.red1),
|
||||
foregroundColor: WidgetStatePropertyAll(Colors.white),
|
||||
),
|
||||
child: Text(label),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
60
compass_app/app/lib/ui/core/ui/home_button.dart
Normal file
60
compass_app/app/lib/ui/core/ui/home_button.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../routing/routes.dart';
|
||||
import '../themes/colors.dart';
|
||||
import 'blur_filter.dart';
|
||||
|
||||
/// Home button to navigate back to the '/' path.
|
||||
class HomeButton extends StatelessWidget {
|
||||
const HomeButton({
|
||||
super.key,
|
||||
this.blur = false,
|
||||
});
|
||||
|
||||
final bool blur;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (blur)
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: kBlurFilter,
|
||||
child: const SizedBox(height: 40.0, width: 40.0),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
context.go(Routes.home);
|
||||
},
|
||||
child: Center(
|
||||
child: Icon(
|
||||
size: 24.0,
|
||||
Icons.home_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
compass_app/app/lib/ui/core/ui/scroll_behavior.dart
Normal file
17
compass_app/app/lib/ui/core/ui/scroll_behavior.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Custom scroll behavior to allow dragging with mouse.
|
||||
/// Necessary to allow dragging with mouse on Continents carousel.
|
||||
class AppCustomScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
// Allow to drag with mouse on Regions carousel
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
111
compass_app/app/lib/ui/core/ui/search_bar.dart
Normal file
111
compass_app/app/lib/ui/core/ui/search_bar.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../localization/applocalization.dart';
|
||||
import '../themes/colors.dart';
|
||||
import '../themes/dimens.dart';
|
||||
import 'date_format_start_end.dart';
|
||||
import 'home_button.dart';
|
||||
|
||||
/// Application top search bar.
|
||||
///
|
||||
/// Displays a search bar with the current configuration.
|
||||
/// Includes [HomeButton] to navigate back to the '/' path.
|
||||
class AppSearchBar extends StatelessWidget {
|
||||
const AppSearchBar({
|
||||
super.key,
|
||||
this.config,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final ItineraryConfig? config;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Dimens.paddingHorizontal,
|
||||
),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: _QueryText(config: config),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const HomeButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QueryText extends StatelessWidget {
|
||||
const _QueryText({
|
||||
required this.config,
|
||||
});
|
||||
|
||||
final ItineraryConfig? config;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (config == null) {
|
||||
return const _EmptySearch();
|
||||
}
|
||||
|
||||
final ItineraryConfig(:continent, :startDate, :endDate, :guests) = config!;
|
||||
if (startDate == null ||
|
||||
endDate == null ||
|
||||
guests == null ||
|
||||
continent == null) {
|
||||
return const _EmptySearch();
|
||||
}
|
||||
|
||||
return Text(
|
||||
'$continent - ${dateFormatStartEnd(DateTimeRange(start: startDate, end: endDate))} - Guests: $guests',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptySearch extends StatelessWidget {
|
||||
const _EmptySearch();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.search),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalization.of(context).searchDestination,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).inputDecorationTheme.hintStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
144
compass_app/app/lib/ui/core/ui/tag_chip.dart
Normal file
144
compass_app/app/lib/ui/core/ui/tag_chip.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 The Flutter team. 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:ui';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class TagChip extends StatelessWidget {
|
||||
const TagChip({
|
||||
super.key,
|
||||
required this.tag,
|
||||
this.fontSize = 10,
|
||||
this.height = 20,
|
||||
this.chipColor,
|
||||
this.onChipColor,
|
||||
});
|
||||
|
||||
final String tag;
|
||||
|
||||
final double fontSize;
|
||||
final double height;
|
||||
final Color? chipColor;
|
||||
final Color? onChipColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(height / 2),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: chipColor ??
|
||||
Theme.of(context).extension<TagChipTheme>()?.chipColor ??
|
||||
AppColors.whiteTransparent,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_iconFrom(tag),
|
||||
color: onChipColor ??
|
||||
Theme.of(context)
|
||||
.extension<TagChipTheme>()
|
||||
?.onChipColor ??
|
||||
Colors.white,
|
||||
size: fontSize,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
tag,
|
||||
textAlign: TextAlign.center,
|
||||
style: _textStyle(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData? _iconFrom(String tag) {
|
||||
return switch (tag) {
|
||||
'Adventure sports' => Icons.kayaking_outlined,
|
||||
'Beach' => Icons.beach_access_outlined,
|
||||
'City' => Icons.location_city_outlined,
|
||||
'Cultural experiences' => Icons.museum_outlined,
|
||||
'Foodie' || 'Food tours' => Icons.restaurant,
|
||||
'Hiking' => Icons.hiking,
|
||||
'Historic' => Icons.menu_book_outlined,
|
||||
'Island' || 'Coastal' || 'Lake' || 'River' => Icons.water,
|
||||
'Luxury' => Icons.attach_money_outlined,
|
||||
'Mountain' || 'Wildlife watching' => Icons.landscape_outlined,
|
||||
'Nightlife' => Icons.local_bar_outlined,
|
||||
'Off-the-beaten-path' => Icons.do_not_step_outlined,
|
||||
'Romantic' => Icons.favorite_border_outlined,
|
||||
'Rural' => Icons.agriculture_outlined,
|
||||
'Secluded' => Icons.church_outlined,
|
||||
'Sightseeing' => Icons.attractions_outlined,
|
||||
'Skiing' => Icons.downhill_skiing_outlined,
|
||||
'Wine tasting' => Icons.wine_bar_outlined,
|
||||
'Winter destination' => Icons.ac_unit,
|
||||
_ => Icons.label_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
// Note: original Figma file uses Google Sans
|
||||
// which is not available on GoogleFonts
|
||||
_textStyle(BuildContext context) => GoogleFonts.openSans(
|
||||
textStyle: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: fontSize,
|
||||
color: onChipColor ??
|
||||
Theme.of(context).extension<TagChipTheme>()?.onChipColor ??
|
||||
Colors.white,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class TagChipTheme extends ThemeExtension<TagChipTheme> {
|
||||
final Color chipColor;
|
||||
final Color onChipColor;
|
||||
|
||||
TagChipTheme({
|
||||
required this.chipColor,
|
||||
required this.onChipColor,
|
||||
});
|
||||
|
||||
@override
|
||||
ThemeExtension<TagChipTheme> copyWith({
|
||||
Color? chipColor,
|
||||
Color? onChipColor,
|
||||
}) {
|
||||
return TagChipTheme(
|
||||
chipColor: chipColor ?? this.chipColor,
|
||||
onChipColor: onChipColor ?? this.onChipColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ThemeExtension<TagChipTheme> lerp(
|
||||
covariant ThemeExtension<TagChipTheme> other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! TagChipTheme) {
|
||||
return this;
|
||||
}
|
||||
return TagChipTheme(
|
||||
chipColor: Color.lerp(chipColor, other.chipColor, t) ?? chipColor,
|
||||
onChipColor: Color.lerp(onChipColor, other.onChipColor, t) ?? onChipColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user