Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
496287716a | ||
|
|
d0ecd48407 | ||
|
|
092fa1215f | ||
|
|
3ca76d0c26 | ||
|
|
54f9a4b2ad | ||
|
|
b37bb3bf2b | ||
| b78c83cc4a | |||
| c4e28decb9 | |||
| 6038d450e4 |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.3 KiB |
BIN
assets/gif/citycards customer app.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
assets/icons/citycards_customer_logo.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
assets/icons/citycards_main_logo.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
assets/images/hotel_offers_bg.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
@@ -1,6 +1,6 @@
|
|||||||
# flutter pub run flutter_launcher_icons
|
# flutter pub run flutter_launcher_icons
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
image_path: "assets/logo/logo_city_cards.png"
|
image_path: "assets/icons/citycards_customer_logo.jpg"
|
||||||
|
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
# image_path_android: "assets/icon/icon.png"
|
# image_path_android: "assets/icon/icon.png"
|
||||||
|
|||||||
@@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- app_links (7.0.0):
|
||||||
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_angle (0.3.8):
|
- flutter_angle (0.3.8):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -23,6 +25,8 @@ PODS:
|
|||||||
- GoogleMaps/Maps (9.4.0)
|
- GoogleMaps/Maps (9.4.0)
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- open_filex (0.0.2):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -93,11 +97,14 @@ PODS:
|
|||||||
- StripeCore (= 25.0.1)
|
- StripeCore (= 25.0.1)
|
||||||
- three_js_sensors (0.1.2):
|
- three_js_sensors (0.1.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- video_player_avfoundation (0.0.1):
|
- video_player_avfoundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_angle (from `.symlinks/plugins/flutter_angle/darwin`)
|
- flutter_angle (from `.symlinks/plugins/flutter_angle/darwin`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
@@ -105,6 +112,7 @@ DEPENDENCIES:
|
|||||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
|
||||||
- google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
|
- google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
@@ -112,6 +120,7 @@ DEPENDENCIES:
|
|||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- stripe_ios (from `.symlinks/plugins/stripe_ios/ios`)
|
- stripe_ios (from `.symlinks/plugins/stripe_ios/ios`)
|
||||||
- three_js_sensors (from `.symlinks/plugins/three_js_sensors/ios`)
|
- three_js_sensors (from `.symlinks/plugins/three_js_sensors/ios`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
@@ -129,6 +138,8 @@ SPEC REPOS:
|
|||||||
- StripeUICore
|
- StripeUICore
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
app_links:
|
||||||
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_angle:
|
flutter_angle:
|
||||||
@@ -143,6 +154,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/google_maps_flutter_ios/ios"
|
:path: ".symlinks/plugins/google_maps_flutter_ios/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
open_filex:
|
||||||
|
:path: ".symlinks/plugins/open_filex/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -157,27 +170,31 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/stripe_ios/ios"
|
:path: ".symlinks/plugins/stripe_ios/ios"
|
||||||
three_js_sensors:
|
three_js_sensors:
|
||||||
:path: ".symlinks/plugins/three_js_sensors/ios"
|
:path: ".symlinks/plugins/three_js_sensors/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
app_links: 6d01271b3907b0ee7325c5297c75d697c4226c4d
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_angle: 7b1a2b3e733221bf2e0325e42fc3edf95b5d44c4
|
flutter_angle: fc44e198cea1f07e1a5919bad1484049fab65c96
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||||
FlutterAngle: c810891af800750361b1d0e7cc944f2338d5ae18
|
FlutterAngle: c810891af800750361b1d0e7cc944f2338d5ae18
|
||||||
geocoding_ios: 33776c9ebb98d037b5e025bb0e7537f6dd19646e
|
geocoding_ios: eafacae6ad11a1eb56681f7d11df602a5fd49416
|
||||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd
|
||||||
Google-Maps-iOS-Utils: 0a484b05ed21d88c9f9ebbacb007956edd508a96
|
Google-Maps-iOS-Utils: 0a484b05ed21d88c9f9ebbacb007956edd508a96
|
||||||
google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264
|
google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3
|
||||||
GoogleMaps: 0608099d4870cac8754bdba9b6953db543432438
|
GoogleMaps: 0608099d4870cac8754bdba9b6953db543432438
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||||
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
Stripe: 4728e3e0dd8df134e4a420ab504e929a93a815f0
|
Stripe: 4728e3e0dd8df134e4a420ab504e929a93a815f0
|
||||||
stripe_ios: b6b8ef64e89a5409da466b4e6a2a6a26178892a6
|
stripe_ios: c552a249333c2e810e02539140dba366c7f0683f
|
||||||
StripeApplePay: 43997281ace138a1c75a8f2d7be11925ea28644c
|
StripeApplePay: 43997281ace138a1c75a8f2d7be11925ea28644c
|
||||||
StripeCore: 457c30e2fd3a7c4b274a5ad53d1ff03661eef2a0
|
StripeCore: 457c30e2fd3a7c4b274a5ad53d1ff03661eef2a0
|
||||||
StripeFinancialConnections: 8c2e326f767fb014b53174b3a5f8592c0a45fa56
|
StripeFinancialConnections: 8c2e326f767fb014b53174b3a5f8592c0a45fa56
|
||||||
@@ -185,8 +202,9 @@ SPEC CHECKSUMS:
|
|||||||
StripePaymentSheet: 3f93ce6ea84afde770d3c7e18a9b8f99aed63896
|
StripePaymentSheet: 3f93ce6ea84afde770d3c7e18a9b8f99aed63896
|
||||||
StripePaymentsUI: 626726a01255a6458c35436f7f6431dacee82684
|
StripePaymentsUI: 626726a01255a6458c35436f7f6431dacee82684
|
||||||
StripeUICore: 30f8352fd7a5cf1541b7777a57b3ad1133bf6763
|
StripeUICore: 30f8352fd7a5cf1541b7777a57b3ad1133bf6763
|
||||||
three_js_sensors: f516b092803411e05b1e3dc7625efa36acd8f455
|
three_js_sensors: ab5f24fbeb97ab5c5ce2978c3e63a25d67a076f5
|
||||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||||
|
video_player_avfoundation: 7993f492ae0bd77edaea24d9dc051d8bb2cd7c86
|
||||||
|
|
||||||
PODFILE CHECKSUM: 1857a7cdb7dfafe45f2b0e9a9af44644190f7506
|
PODFILE CHECKSUM: 1857a7cdb7dfafe45f2b0e9a9af44644190f7506
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import Flutter
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 555 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 857 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 5.2 KiB |
@@ -34,6 +34,27 @@
|
|||||||
<string>Citycard customer needs your location to find the closest place you can visit.</string>
|
<string>Citycard customer needs your location to find the closest place you can visit.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>We need access to your camera for taking photos for profile and to build a postcard.</string>
|
<string>We need access to your camera for taking photos for profile and to build a postcard.</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
@@ -48,10 +69,7 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
5
l10n.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
output-class: AppLocalizations
|
||||||
|
nullable-getter: false
|
||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:phone_numbers_parser/phone_numbers_parser.dart'; // ✅ NEW IMPORT
|
import 'package:phone_numbers_parser/phone_numbers_parser.dart'; // ✅ NEW IMPORT
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../checkout/bloc/pass_purchase_details_bloc.dart';
|
import '../checkout/bloc/pass_purchase_details_bloc.dart';
|
||||||
import '../checkout/bloc/pass_purchase_details_event.dart';
|
import '../checkout/bloc/pass_purchase_details_event.dart';
|
||||||
@@ -68,8 +69,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
cityController.text.isEmpty ||
|
cityController.text.isEmpty ||
|
||||||
countryController.text.isEmpty) {
|
countryController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Please fill all fields'),
|
content: Text(AppLocalizations.of(context)!.pleaseFillAllFields),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -78,8 +79,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
|
|
||||||
if (!_isValidEmail(emailController.text)) {
|
if (!_isValidEmail(emailController.text)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Please enter a valid email address'),
|
content: Text(AppLocalizations.of(context)!.enterValidEmail),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -90,7 +91,7 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
if (!_isValidPhone(phoneController.text)) {
|
if (!_isValidPhone(phoneController.text)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Enter a valid phone number for $_selectedIsdCode'),
|
content: Text(AppLocalizations.of(context)!.enterValidPhoneForIsd(_selectedIsdCode)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -125,7 +126,7 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
if (state is PurchaseDetailsError) {
|
if (state is PurchaseDetailsError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(state.errorMessage ?? 'Failed to submit details'),
|
content: Text(state.errorMessage ?? AppLocalizations.of(context)!.failedToSubmitDetails),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -158,7 +159,7 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
Text(
|
Text(
|
||||||
"Add details",
|
AppLocalizations.of(context)!.addDetailsTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -170,7 +171,7 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "Tell us about the recipient",
|
text: AppLocalizations.of(context)!.aboutRecipient,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -180,8 +181,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "First Name *",
|
label: AppLocalizations.of(context)!.firstNameLabelWithStar,
|
||||||
hint: "Enter recipient's first name",
|
hint: AppLocalizations.of(context)!.firstNameHint,
|
||||||
controller: firstNameController,
|
controller: firstNameController,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
@@ -192,8 +193,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Last Name *",
|
label: AppLocalizations.of(context)!.lastNameLabelWithStar,
|
||||||
hint: "Enter recipient's last name",
|
hint: AppLocalizations.of(context)!.lastNameHint,
|
||||||
controller: lastNameController,
|
controller: lastNameController,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
@@ -204,8 +205,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Email *",
|
label: AppLocalizations.of(context)!.emailLabelWithStar,
|
||||||
hint: "Enter recipient's email address",
|
hint: AppLocalizations.of(context)!.emailHint,
|
||||||
controller: emailController,
|
controller: emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
),
|
),
|
||||||
@@ -215,8 +216,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Phone Number *",
|
label: AppLocalizations.of(context)!.phoneNumberLabelWithStar,
|
||||||
hint: "Enter phone number",
|
hint: AppLocalizations.of(context)!.phoneNumberHint,
|
||||||
controller: phoneController,
|
controller: phoneController,
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
maxLength: 12,
|
maxLength: 12,
|
||||||
@@ -237,9 +238,9 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
color: const Color(0xFF2D3134),
|
color: const Color(0xFF2D3134),
|
||||||
),
|
),
|
||||||
dialogTextStyle: TextStyle(fontSize: 14.sp),
|
dialogTextStyle: TextStyle(fontSize: 14.sp),
|
||||||
searchDecoration: const InputDecoration(
|
searchDecoration: InputDecoration(
|
||||||
hintText: 'Search country...',
|
hintText: AppLocalizations.of(context)!.searchCountryHint,
|
||||||
prefixIcon: Icon(Icons.search),
|
prefixIcon: const Icon(Icons.search),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -251,8 +252,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "City *",
|
label: AppLocalizations.of(context)!.cityLabelWithStar,
|
||||||
hint: "Enter the name of the city",
|
hint: AppLocalizations.of(context)!.cityHint,
|
||||||
controller: cityController,
|
controller: cityController,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
@@ -263,8 +264,8 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Country *",
|
label: AppLocalizations.of(context)!.countryLabelWithStar,
|
||||||
hint: "Enter country name",
|
hint: AppLocalizations.of(context)!.countryHint,
|
||||||
controller: countryController,
|
controller: countryController,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
@@ -276,7 +277,7 @@ class _AddDetailsViewState extends State<AddDetailsView> {
|
|||||||
|
|
||||||
CustomFilledButton(
|
CustomFilledButton(
|
||||||
onTap: () => _handleSubmit(context, isSubmitting),
|
onTap: () => _handleSubmit(context, isSubmitting),
|
||||||
label: isSubmitting ? "Submitting..." : "Continue",
|
label: isSubmitting ? AppLocalizations.of(context)!.submittingLabel : AppLocalizations.of(context)!.continueTitle,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,39 @@ class AttractionDetailsBloc
|
|||||||
required this.repository,
|
required this.repository,
|
||||||
}) : super(AttractionDetailsInitial()) {
|
}) : super(AttractionDetailsInitial()) {
|
||||||
on<FetchAttractionDetails>(_onFetchAttractionDetails);
|
on<FetchAttractionDetails>(_onFetchAttractionDetails);
|
||||||
|
on<ToggleDescriptionExpanded>(_onToggleDescriptionExpanded);
|
||||||
|
on<UpdateGalleryIndex>(_onUpdateGalleryIndex);
|
||||||
|
on<UpdateFullScreenGalleryIndex>(_onUpdateFullScreenGalleryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onToggleDescriptionExpanded(
|
||||||
|
ToggleDescriptionExpanded event,
|
||||||
|
Emitter<AttractionDetailsState> emit,
|
||||||
|
) {
|
||||||
|
if (state is AttractionDetailsLoaded) {
|
||||||
|
final currentState = state as AttractionDetailsLoaded;
|
||||||
|
emit(currentState.copyWith(isExpanded: !currentState.isExpanded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateGalleryIndex(
|
||||||
|
UpdateGalleryIndex event,
|
||||||
|
Emitter<AttractionDetailsState> emit,
|
||||||
|
) {
|
||||||
|
if (state is AttractionDetailsLoaded) {
|
||||||
|
final currentState = state as AttractionDetailsLoaded;
|
||||||
|
emit(currentState.copyWith(galleryIndex: event.index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateFullScreenGalleryIndex(
|
||||||
|
UpdateFullScreenGalleryIndex event,
|
||||||
|
Emitter<AttractionDetailsState> emit,
|
||||||
|
) {
|
||||||
|
if (state is AttractionDetailsLoaded) {
|
||||||
|
final currentState = state as AttractionDetailsLoaded;
|
||||||
|
emit(currentState.copyWith(fullScreenGalleryIndex: event.index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchAttractionDetails(
|
Future<void> _onFetchAttractionDetails(
|
||||||
|
|||||||
@@ -17,3 +17,19 @@ class FetchAttractionDetails extends AttractionDetailsEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [attractionId];
|
List<Object?> get props => [attractionId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ToggleDescriptionExpanded extends AttractionDetailsEvent {}
|
||||||
|
|
||||||
|
class UpdateGalleryIndex extends AttractionDetailsEvent {
|
||||||
|
final int index;
|
||||||
|
const UpdateGalleryIndex({required this.index});
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [index];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateFullScreenGalleryIndex extends AttractionDetailsEvent {
|
||||||
|
final int index;
|
||||||
|
const UpdateFullScreenGalleryIndex({required this.index});
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [index];
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,13 +15,33 @@ class AttractionDetailsLoading extends AttractionDetailsState {}
|
|||||||
|
|
||||||
class AttractionDetailsLoaded extends AttractionDetailsState {
|
class AttractionDetailsLoaded extends AttractionDetailsState {
|
||||||
final AttractionDetailsModel attractionDetails;
|
final AttractionDetailsModel attractionDetails;
|
||||||
|
final bool isExpanded;
|
||||||
|
final int galleryIndex;
|
||||||
|
final int fullScreenGalleryIndex;
|
||||||
|
|
||||||
const AttractionDetailsLoaded({
|
const AttractionDetailsLoaded({
|
||||||
required this.attractionDetails,
|
required this.attractionDetails,
|
||||||
|
this.isExpanded = false,
|
||||||
|
this.galleryIndex = 0,
|
||||||
|
this.fullScreenGalleryIndex = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AttractionDetailsLoaded copyWith({
|
||||||
|
AttractionDetailsModel? attractionDetails,
|
||||||
|
bool? isExpanded,
|
||||||
|
int? galleryIndex,
|
||||||
|
int? fullScreenGalleryIndex,
|
||||||
|
}) {
|
||||||
|
return AttractionDetailsLoaded(
|
||||||
|
attractionDetails: attractionDetails ?? this.attractionDetails,
|
||||||
|
isExpanded: isExpanded ?? this.isExpanded,
|
||||||
|
galleryIndex: galleryIndex ?? this.galleryIndex,
|
||||||
|
fullScreenGalleryIndex: fullScreenGalleryIndex ?? this.fullScreenGalleryIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [attractionDetails];
|
List<Object?> get props => [attractionDetails, isExpanded, galleryIndex, fullScreenGalleryIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttractionDetailsError extends AttractionDetailsState {
|
class AttractionDetailsError extends AttractionDetailsState {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.dart';
|
import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.dart';
|
||||||
import 'package:citycards_customer/common_packages/app_bar.dart';
|
import 'package:citycards_customer/common_packages/app_bar.dart';
|
||||||
import 'package:citycards_customer/common_packages/custom_text.dart';
|
import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||||
@@ -7,7 +10,9 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
import '../bloc/attraction_details_bloc.dart';
|
import '../bloc/attraction_details_bloc.dart';
|
||||||
import '../bloc/attraction_details_event.dart';
|
import '../bloc/attraction_details_event.dart';
|
||||||
@@ -67,101 +72,156 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
// ── White app bar above the image ───────────────────────
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
|
child: CommonAppBar(
|
||||||
|
isWhiteLogo: false,
|
||||||
|
isProfilePage: false,
|
||||||
|
showDivider: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20.h,
|
||||||
|
),
|
||||||
|
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Image.network(
|
// ── Hero image ──────────────────────────────────────
|
||||||
coverImage,
|
CachedNetworkImage(
|
||||||
height: 377.h,
|
imageUrl: coverImage,
|
||||||
|
height: 280.h,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
return Image.asset(
|
width: double.infinity,
|
||||||
'assets/images/koh_rong_samloem_banner.png',
|
height: 280.h,
|
||||||
height: 377.h,
|
borderRadius: 0,
|
||||||
width: double.infinity,
|
),
|
||||||
fit: BoxFit.cover,
|
errorWidget: (context, url, error) => Image.asset(
|
||||||
);
|
'assets/images/koh_rong_samloem_banner.png',
|
||||||
},
|
height: 280.h,
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ── Bottom fade gradient ─────────────────────────────
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
height: 180.h,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Color(0xCC000000), // ~80% black at bottom
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ── Top: pill-style back button (no AppBar) ──────────
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), // 🔽 reduced
|
||||||
horizontal: 20.w, vertical: 10.h),
|
child: Align(
|
||||||
child: Column(
|
alignment: Alignment.centerLeft,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: GestureDetector(
|
||||||
children: [
|
onTap: () => Navigator.pop(context),
|
||||||
CommonAppBar(
|
child: Container(
|
||||||
isWhiteLogo: true,
|
padding: EdgeInsets.symmetric(
|
||||||
isProfilePage: false,
|
horizontal: 12.w, // 🔽 smaller
|
||||||
showDivider: true,
|
vertical: 8.h, // 🔽 smaller
|
||||||
),
|
),
|
||||||
SizedBox(height: 10.h),
|
decoration: BoxDecoration(
|
||||||
Row(
|
color: Colors.white,
|
||||||
children: [
|
borderRadius: BorderRadius.circular(24.r), // 🔽 slightly smaller
|
||||||
GestureDetector(
|
boxShadow: [
|
||||||
onTap: () => Navigator.pop(context),
|
BoxShadow(
|
||||||
child: Icon(
|
color: Colors.black.withOpacity(0.10), // 🔽 lighter shadow
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
size: 24.sp,
|
size: 16.sp, // 🔽 smaller icon
|
||||||
color: Colors.white,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: 6.w), // 🔽 smaller spacing
|
||||||
SizedBox(width: 8.w),
|
Text(
|
||||||
Expanded(
|
'Back to attractions',
|
||||||
child: Text(
|
|
||||||
attraction.title,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 13.sp, // 🔽 slightly smaller
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600, // ✅ bold
|
||||||
color: Colors.white,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ── Bottom-left: attraction title (smaller, over fade) ─
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 31.h,
|
bottom: 48.h,
|
||||||
left: 12.w,
|
left: 14.w,
|
||||||
right: 60.w, // Add this - leaves space for share button
|
right: 60.w,
|
||||||
child: Text(
|
child: Text(
|
||||||
attraction.title,
|
attraction.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 44.sp,
|
fontSize: 28.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w600,
|
||||||
height: 1.2,
|
height: 1.25,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ── Bottom-right: share button ───────────────────────
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 31.h,
|
bottom: 48.h,
|
||||||
right: 17.w,
|
right: 14.w,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Share.share(
|
Share.share(
|
||||||
'www.google.com',
|
'www.google.com',
|
||||||
subject: 'Check this out',
|
subject: AppLocalizations.of(context)!.checkThisOut,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 36.h,
|
height: 42.h,
|
||||||
width: 36.w,
|
width: 42.w,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(20.r),
|
borderRadius: BorderRadius.circular(21.r),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.15),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
@@ -172,33 +232,110 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ── Gallery Section (All Images) ──────────────────────────
|
||||||
|
if (attraction.attractionGalleries.length > 1)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 20.h, left: 16.w, right: 16.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Gallery',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_GalleryStrip(
|
||||||
|
galleries: attraction.attractionGalleries,
|
||||||
|
currentIndex: state.galleryIndex,
|
||||||
|
onTap: (index) => showFullScreenGallery(
|
||||||
|
context,
|
||||||
|
attraction.attractionGalleries
|
||||||
|
.map((g) => g.filePathUrl)
|
||||||
|
.toList(),
|
||||||
|
index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// About Section
|
// About Section
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 20.h),
|
||||||
EdgeInsets.only(left: 16.w, right: 16.w, top: 20.h),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"About",
|
AppLocalizations.of(context)!.aboutTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 12.32.h),
|
SizedBox(height: 12.32.h),
|
||||||
Text(
|
|
||||||
attraction.description,
|
LayoutBuilder(
|
||||||
style: TextStyle(
|
builder: (context, constraints) {
|
||||||
color: Color(0xFF262626),
|
final textSpan = TextSpan(
|
||||||
fontWeight: FontWeight.w400,
|
text: attraction.description,
|
||||||
fontSize: 14.sp,
|
style: TextStyle(
|
||||||
height: 1.5,
|
color: Color(0xFF262626),
|
||||||
),
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final textPainter = TextPainter(
|
||||||
|
text: textSpan,
|
||||||
|
maxLines: 3,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
|
||||||
|
textPainter.layout(maxWidth: constraints.maxWidth);
|
||||||
|
final isTextOverflowing = textPainter.didExceedMaxLines;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
attraction.description,
|
||||||
|
maxLines: state.isExpanded ? null : (isTextOverflowing ? 3 : null),
|
||||||
|
overflow: state.isExpanded ? TextOverflow.visible : (isTextOverflowing ? TextOverflow.ellipsis : TextOverflow.visible),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF262626),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isTextOverflowing) ...[
|
||||||
|
SizedBox(height: 6.h),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
context.read<AttractionDetailsBloc>().add(ToggleDescriptionExpanded());
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
state.isExpanded ? "See less" : "See more",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFF95F62), // your theme color
|
||||||
|
fontSize: 13.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -368,10 +505,10 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
Divider(color: Colors.black.withOpacity(0.2)),
|
Divider(color: Colors.black.withOpacity(0.2)),
|
||||||
SizedBox(height: 30.h),
|
SizedBox(height: 30.h),
|
||||||
Text(
|
Text(
|
||||||
"What is included",
|
AppLocalizations.of(context)!.whatIsIncluded,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
@@ -393,17 +530,17 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 30.h),
|
SizedBox(height: 30.h),
|
||||||
// Divider(color: Colors.black.withOpacity(0.2)),
|
// Divider(color: Colors.black.withOpacity(0.2)),
|
||||||
SizedBox(height: 30.h),
|
// SizedBox(height: 30.h),
|
||||||
Text(
|
Text(
|
||||||
"Exact Location",
|
AppLocalizations.of(context)!.exactLocation,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "View the location on map",
|
text: AppLocalizations.of(context)!.viewOnMap,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Colors.black.withOpacity(.6),
|
color: Colors.black.withOpacity(.6),
|
||||||
),
|
),
|
||||||
@@ -463,28 +600,29 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
SizedBox(height: 30.h),
|
SizedBox(height: 30.h),
|
||||||
Divider(color: Colors.black.withOpacity(0.2)),
|
if (attraction.attractionFaqs.isNotEmpty) ...[
|
||||||
SizedBox(height: 30.h),
|
Divider(color: Colors.black.withOpacity(0.2)),
|
||||||
Text(
|
SizedBox(height: 30.h),
|
||||||
"People frequently ask",
|
Text(
|
||||||
style: TextStyle(
|
AppLocalizations.of(context)!.peopleFrequentlyAsk,
|
||||||
fontSize: 18.sp,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w400,
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 15.h),
|
||||||
SizedBox(height: 15.h),
|
Column(
|
||||||
Column(
|
children: attraction.attractionFaqs.map((faq) {
|
||||||
children: attraction.attractionFaqs.map((faq) {
|
return Padding(
|
||||||
return Padding(
|
padding: EdgeInsets.only(bottom: 15.h),
|
||||||
padding: EdgeInsets.only(bottom: 15.h),
|
child: faqBox(
|
||||||
child: faqBox(
|
title: faq.faqQuestion,
|
||||||
title: faq.faqQuestion,
|
desc: faq.faqAnswer,
|
||||||
desc: faq.faqAnswer,
|
),
|
||||||
),
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).toList(),
|
),
|
||||||
),
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -499,7 +637,7 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text("Something went wrong"),
|
child: Text(AppLocalizations.of(context)!.somethingWentWrong),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -590,4 +728,276 @@ class AttractionDetailsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Full Screen Swipeable Gallery Viewer ────────────────────────────
|
||||||
|
void showFullScreenGallery(BuildContext context, List<String> imageUrls, int initialIndex) {
|
||||||
|
final bloc = context.read<AttractionDetailsBloc>();
|
||||||
|
bloc.add(UpdateFullScreenGalleryIndex(index: initialIndex));
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.black,
|
||||||
|
builder: (ctx) => BlocProvider.value(
|
||||||
|
value: bloc,
|
||||||
|
child: _FullScreenGallery(
|
||||||
|
imageUrls: imageUrls,
|
||||||
|
initialIndex: initialIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Auto-scroll Gallery Strip with Dot Indicators ───────────────────
|
||||||
|
class _GalleryStrip extends StatefulWidget {
|
||||||
|
final List galleries;
|
||||||
|
final int currentIndex;
|
||||||
|
final void Function(int index) onTap;
|
||||||
|
|
||||||
|
const _GalleryStrip({required this.galleries, required this.currentIndex, required this.onTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_GalleryStrip> createState() => _GalleryStripState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GalleryStripState extends State<_GalleryStrip> {
|
||||||
|
late final PageController _pageController;
|
||||||
|
Timer? _timer;
|
||||||
|
late int _currentPage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Start at a high number divisible by length to allow infinite scrolling in both directions
|
||||||
|
int initialPage = widget.galleries.isNotEmpty ? widget.galleries.length * 1000 : 0;
|
||||||
|
_currentPage = initialPage;
|
||||||
|
_pageController = PageController(
|
||||||
|
viewportFraction: 0.38, // shows partial next/prev image
|
||||||
|
initialPage: initialPage,
|
||||||
|
);
|
||||||
|
_startAutoScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startAutoScroll() {
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_pageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.galleries.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 120.h,
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: _pageController,
|
||||||
|
// No itemCount to allow infinite scrolling
|
||||||
|
onPageChanged: (i) {
|
||||||
|
_currentPage = i;
|
||||||
|
context.read<AttractionDetailsBloc>().add(UpdateGalleryIndex(index: i % widget.galleries.length));
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final realIndex = index % widget.galleries.length;
|
||||||
|
final gallery = widget.galleries[realIndex];
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(right: 12.w),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => widget.onTap(realIndex),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: gallery.filePathUrl,
|
||||||
|
width: 120.w,
|
||||||
|
height: 120.h,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
|
width: 120.w,
|
||||||
|
height: 120.h,
|
||||||
|
borderRadius: 12.r,
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => Container(
|
||||||
|
width: 120.w,
|
||||||
|
height: 120.h,
|
||||||
|
color: Colors.grey[300],
|
||||||
|
child: const Icon(Icons.broken_image, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
// Dot indicators
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(widget.galleries.length, (index) {
|
||||||
|
final isActive = index == widget.currentIndex;
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
||||||
|
width: isActive ? 18.w : 6.w,
|
||||||
|
height: 6.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isActive
|
||||||
|
? const Color(0xFFF95F62)
|
||||||
|
: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Full Screen Swipeable Gallery ───────────────────────────────────
|
||||||
|
class _FullScreenGallery extends StatefulWidget {
|
||||||
|
final List<String> imageUrls;
|
||||||
|
final int initialIndex;
|
||||||
|
|
||||||
|
const _FullScreenGallery({
|
||||||
|
required this.imageUrls,
|
||||||
|
required this.initialIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FullScreenGallery> createState() => _FullScreenGalleryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FullScreenGalleryState extends State<_FullScreenGallery> {
|
||||||
|
late final PageController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = PageController(initialPage: widget.initialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AttractionDetailsBloc, AttractionDetailsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
int currentIndex = widget.initialIndex;
|
||||||
|
if (state is AttractionDetailsLoaded) {
|
||||||
|
currentIndex = state.fullScreenGalleryIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// Swipeable images
|
||||||
|
PageView.builder(
|
||||||
|
controller: _controller,
|
||||||
|
itemCount: widget.imageUrls.length,
|
||||||
|
onPageChanged: (i) => context.read<AttractionDetailsBloc>().add(UpdateFullScreenGalleryIndex(index: i)),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return InteractiveViewer(
|
||||||
|
child: Center(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: widget.imageUrls[index],
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
placeholder: (context, url) => const Center(
|
||||||
|
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => const Icon(
|
||||||
|
Icons.broken_image,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Close button + counter
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(8.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.25),
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.close, color: Colors.white, size: 24.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 6.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${currentIndex + 1} / ${widget.imageUrls.length}',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Bottom dot indicators
|
||||||
|
Positioned(
|
||||||
|
bottom: 30.h,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(widget.imageUrls.length, (index) {
|
||||||
|
final isActive = index == currentIndex;
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
||||||
|
width: isActive ? 18.w : 6.w,
|
||||||
|
height: 6.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isActive ? const Color(0xFFF95F62) : Colors.white54,
|
||||||
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:citycards_customer/common_packages/back_widget.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../common_packages/custom_search_field.dart';
|
import '../../common_packages/custom_search_field.dart';
|
||||||
import '../blocs/attractions_bloc.dart';
|
import '../blocs/attractions_bloc.dart';
|
||||||
@@ -37,116 +38,125 @@ class AttractionsPage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: RefreshIndicator(
|
||||||
padding: const EdgeInsets.all(16),
|
color: Color(0xffF95F62),
|
||||||
child: Column(
|
onRefresh: () async {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
bloc.add(
|
||||||
children: [
|
const FetchAttractionsByCategory(),
|
||||||
// App bar
|
);
|
||||||
CommonAppBar(
|
},
|
||||||
isWhiteLogo: false,
|
child: SingleChildScrollView(
|
||||||
isProfilePage: false,
|
padding: const EdgeInsets.all(16),
|
||||||
showDivider: true,
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
),
|
child: Column(
|
||||||
backWidget(context, "Your Attraction", Colors.black),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(height: 20),
|
children: [
|
||||||
|
// App bar
|
||||||
// 🔍 Search field (UI kept, logic disabled)
|
CommonAppBar(
|
||||||
CommonSearchField(
|
isWhiteLogo: false,
|
||||||
hint: "Search attractions...",
|
isProfilePage: false,
|
||||||
hintColor: Colors.grey.shade500,
|
showDivider: true,
|
||||||
onChanged: (value) {
|
|
||||||
bloc.add(SearchAttractions(value));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// 🏖️ Category chips row - DYNAMIC
|
|
||||||
if (state is AttractionsLoaded)
|
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children: state.categories
|
|
||||||
.map(
|
|
||||||
(category) => buildCategoryChip(
|
|
||||||
category.categoryName ?? '',
|
|
||||||
isSelected: state.selectedCategoryId == category.id,
|
|
||||||
onTap: () {
|
|
||||||
bloc.add(
|
|
||||||
FetchAttractionsByCategory(
|
|
||||||
categoryXid: category.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// else
|
backWidget(context, AppLocalizations.of(context)!.yourAttractionTitle, Colors.black),
|
||||||
// // Show placeholder chips while loading
|
const SizedBox(height: 20),
|
||||||
// SingleChildScrollView(
|
|
||||||
// scrollDirection: Axis.horizontal,
|
|
||||||
// child: Row(
|
|
||||||
// children: [
|
|
||||||
// buildCategoryChip("Beach", isSelected: true, onTap: () {}),
|
|
||||||
// buildCategoryChip("Hike", isSelected: false, onTap: () {}),
|
|
||||||
// buildCategoryChip("Adventure", isSelected: false, onTap: () {}),
|
|
||||||
// buildCategoryChip("Best in Summer", isSelected: false, onTap: () {}),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
// 🔍 Search field (UI kept, logic disabled)
|
||||||
|
CommonSearchField(
|
||||||
|
hint: AppLocalizations.of(context)!.searchAttractionsHint,
|
||||||
|
hintColor: Colors.grey.shade500,
|
||||||
|
onChanged: (value) {
|
||||||
|
bloc.add(SearchAttractions(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
// 🙏️ Attraction list
|
const SizedBox(height: 16),
|
||||||
if (state is AttractionsLoading)
|
|
||||||
const Center(
|
// 🏖️ Category chips row - DYNAMIC
|
||||||
child: Padding(
|
if (state is AttractionsLoaded)
|
||||||
padding: EdgeInsets.only(top: 60),
|
SingleChildScrollView(
|
||||||
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
scrollDirection: Axis.horizontal,
|
||||||
),
|
child: Row(
|
||||||
)
|
children: state.categories
|
||||||
else if (state is AttractionsLoaded)
|
.map(
|
||||||
state.attractions.isEmpty
|
(category) => buildCategoryChip(
|
||||||
? Center(
|
category.categoryName ?? '',
|
||||||
child: Padding(
|
isSelected: state.selectedCategoryId == category.id,
|
||||||
padding: const EdgeInsets.only(top: 60),
|
onTap: () {
|
||||||
child: Text(
|
bloc.add(
|
||||||
"No attractions found",
|
FetchAttractionsByCategory(
|
||||||
style: TextStyle(
|
categoryXid: category.id,
|
||||||
color: Colors.grey,
|
),
|
||||||
fontSize: 14.sp,
|
);
|
||||||
),
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
// else
|
||||||
: Column(
|
// // Show placeholder chips while loading
|
||||||
children: state.attractions
|
// SingleChildScrollView(
|
||||||
.map(
|
// scrollDirection: Axis.horizontal,
|
||||||
(attraction) => AttractionCard(
|
// child: Row(
|
||||||
attraction: attraction,
|
// children: [
|
||||||
|
// buildCategoryChip("Beach", isSelected: true, onTap: () {}),
|
||||||
|
// buildCategoryChip("Hike", isSelected: false, onTap: () {}),
|
||||||
|
// buildCategoryChip("Adventure", isSelected: false, onTap: () {}),
|
||||||
|
// buildCategoryChip("Best in Summer", isSelected: false, onTap: () {}),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
// 🙏️ Attraction list
|
||||||
|
if (state is AttractionsLoading)
|
||||||
|
const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 60),
|
||||||
|
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
else if (state is AttractionsLoaded)
|
||||||
)
|
state.attractions.isEmpty
|
||||||
else if (state is AttractionsError)
|
? Center(
|
||||||
Center(
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 60),
|
padding: const EdgeInsets.only(top: 60),
|
||||||
child: Text(
|
child: Text(
|
||||||
state.message,
|
AppLocalizations.of(context)!.noAttractionsFound,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.red,
|
color: Colors.grey,
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
: Column(
|
||||||
const SizedBox(),
|
children: state.attractions
|
||||||
],
|
.map(
|
||||||
|
(attraction) => AttractionCard(
|
||||||
|
attraction: attraction,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
)
|
||||||
|
else if (state is AttractionsError)
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 60),
|
||||||
|
child: Text(
|
||||||
|
state.message,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import '../../common_packages/common_app_texts.dart';
|
import '../../common_packages/common_app_texts.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
import '../models/attraction_model.dart';
|
import '../models/attraction_model.dart';
|
||||||
|
|
||||||
@@ -48,7 +50,11 @@ class AttractionCard extends StatelessWidget {
|
|||||||
height: 94.h,
|
height: 94.h,
|
||||||
width: 94.w,
|
width: 94.w,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) => _imageFallback(),
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
|
width: 94.w,
|
||||||
|
height: 94.h,
|
||||||
|
borderRadius: 8.r,
|
||||||
|
),
|
||||||
errorWidget: (_, __, ___) => _imageFallback(),
|
errorWidget: (_, __, ___) => _imageFallback(),
|
||||||
)
|
)
|
||||||
: _imageFallback(),
|
: _imageFallback(),
|
||||||
@@ -71,18 +77,18 @@ class AttractionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 6.h),
|
// SizedBox(height: 6.h),
|
||||||
|
//
|
||||||
Text(
|
// Text(
|
||||||
attraction.address,
|
// attraction.address,
|
||||||
maxLines: 1,
|
// maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
// overflow: TextOverflow.ellipsis,
|
||||||
style: GoogleFonts.poppins(
|
// style: GoogleFonts.poppins(
|
||||||
fontSize: 12.sp,
|
// fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w400,
|
// fontWeight: FontWeight.w400,
|
||||||
color: const Color(0xff464646),
|
// color: const Color(0xff464646),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
|
|
||||||
SizedBox(height: 6.h),
|
SizedBox(height: 6.h),
|
||||||
|
|
||||||
@@ -98,7 +104,7 @@ class AttractionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "/person",
|
text: AppLocalizations.of(context)!.perPersonSuffix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10.sp,
|
fontSize: 10.sp,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import 'package:citycards_customer/core/route_constants.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../common_packages/back_widget.dart';
|
||||||
import '../../networkApiServices/api_urls.dart';
|
import '../../networkApiServices/api_urls.dart';
|
||||||
import '../bloc/buy_pass_bloc.dart';
|
import '../bloc/buy_pass_bloc.dart';
|
||||||
import '../bloc/buy_pass_event.dart';
|
import '../bloc/buy_pass_event.dart';
|
||||||
import '../bloc/buy_pass_state.dart';
|
import '../bloc/buy_pass_state.dart';
|
||||||
import '../repository/buy_pass_repository.dart';
|
import '../repository/buy_pass_repository.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class BuyPassView extends StatelessWidget {
|
class BuyPassView extends StatelessWidget {
|
||||||
const BuyPassView({super.key});
|
const BuyPassView({super.key});
|
||||||
@@ -19,8 +21,8 @@ class BuyPassView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => BuyPassBloc(repository: BuyPassRepository())
|
create: (context) =>
|
||||||
..add(FetchBuyPassData()),
|
BuyPassBloc(repository: BuyPassRepository())..add(FetchBuyPassData()),
|
||||||
child: const BuyPassContent(),
|
child: const BuyPassContent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -34,7 +36,8 @@ class BuyPassContent extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _BuyPassContentState extends State<BuyPassContent> {
|
class _BuyPassContentState extends State<BuyPassContent> {
|
||||||
late PageController _pageController;@override
|
late PageController _pageController;
|
||||||
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_pageController = PageController(viewportFraction: 0.85);
|
_pageController = PageController(viewportFraction: 0.85);
|
||||||
@@ -45,6 +48,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -54,9 +58,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is BuyPassLoading) {
|
if (state is BuyPassLoading) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(color: Color(0xFFF95F62)),
|
||||||
color: Color(0xFFF95F62),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Error loading data",
|
text: AppLocalizations.of(context)!.errorLoadingDataTitle,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
@@ -83,7 +85,9 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<BuyPassBloc>().add(FetchBuyPassData());
|
context.read<BuyPassBloc>().add(FetchBuyPassData());
|
||||||
},
|
},
|
||||||
child: const Text("Retry"),
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.retryButtonLabel,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -107,31 +111,25 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0.w),
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
child: GestureDetector(
|
child: backWidget(
|
||||||
onTap: () {
|
context,
|
||||||
Navigator.pop(context);
|
AppLocalizations.of(context)!.buyACardTitle,
|
||||||
},
|
Colors.black,
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.arrow_back),
|
|
||||||
SizedBox(width: 8.w),
|
|
||||||
CustomText(text: "Buy a Card", size: 12.sp),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 22.h),
|
SizedBox(height: 20.h),
|
||||||
|
// Pass Cards Horizontal List — with next-card peek + scroll hint
|
||||||
// Pass Cards Horizontal List
|
|
||||||
// Pass Cards Horizontal List
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 140.h,
|
height: 140.h,
|
||||||
child: PageView.builder(
|
child: PageView.builder(
|
||||||
controller: PageController(viewportFraction: 0.92),
|
controller: PageController(viewportFraction: 0.85),
|
||||||
|
clipBehavior: Clip.none,
|
||||||
itemCount: data.cards.length,
|
itemCount: data.cards.length,
|
||||||
onPageChanged: (index) {
|
onPageChanged: (index) {
|
||||||
context.read<BuyPassBloc>().add(ChangeSelectedCard(index));
|
context.read<BuyPassBloc>().add(
|
||||||
|
ChangeSelectedCard(index),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final card = data.cards[index];
|
final card = data.cards[index];
|
||||||
@@ -154,20 +152,44 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 30.h),
|
// "Scroll to reveal more" hint — only visible when there are multiple cards
|
||||||
|
if (data.cards.length > 1) ...[
|
||||||
|
SizedBox(height: 14.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward,
|
||||||
|
size: 18.sp,
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
),
|
||||||
|
SizedBox(width: 6.w),
|
||||||
|
Text(
|
||||||
|
'Scroll to reveal more',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
|
||||||
// Payment Card
|
// Payment Card
|
||||||
// ✅ UPDATED PAYMENT CARD SECTION IN buy_pass_view.dart
|
// ✅ UPDATED PAYMENT CARD SECTION IN buy_pass_view.dart
|
||||||
// Replace the existing PaymentCard widget (around line 154) with this:
|
// Replace the existing PaymentCard widget (around line 154) with this:
|
||||||
|
|
||||||
Center(
|
Center(
|
||||||
child: PaymentCard(
|
child: PaymentCard(
|
||||||
city: data.city.name,
|
city: data.city.name,
|
||||||
heroImage: data.city.heroBanner.image,
|
heroImage: data.city.heroBanner.image,
|
||||||
cardType: selectedCard.cardType.name,
|
cardType: selectedCard.cardType.name,
|
||||||
cardDisplayName: selectedCard.cardType.displayName,
|
cardDisplayName: selectedCard.cardType.displayName,
|
||||||
themeColor: selectedCard.cardType.name == "selective_pass"
|
themeColor:
|
||||||
? Color(0xFFF95FAF) // pink for flexi/selective pass
|
selectedCard.cardType.name == "selective_pass"
|
||||||
|
? Color(0xFFF95FAF) // pink for flexi/selective pass
|
||||||
: Color(0xFFF95F62),
|
: Color(0xFFF95F62),
|
||||||
adultPrice: selectedCard.adultPrice.toDouble(),
|
adultPrice: selectedCard.adultPrice.toDouble(),
|
||||||
childPrice: selectedCard.childPrice.toDouble(),
|
childPrice: selectedCard.childPrice.toDouble(),
|
||||||
@@ -216,14 +238,21 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
CustomText(text: "Member Privileges", size: 18.sp),
|
CustomText(
|
||||||
|
text: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.memberPrivilegesTitle,
|
||||||
|
size: 18.sp,
|
||||||
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pushNamed(
|
Navigator.pushNamed(
|
||||||
context, RouteConstants.searchOffer);
|
context,
|
||||||
|
RouteConstants.searchOffer,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "View All",
|
text: AppLocalizations.of(context)!.viewAll,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Color(0xFFFF5757),
|
color: Color(0xFFFF5757),
|
||||||
),
|
),
|
||||||
@@ -240,12 +269,13 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate:
|
||||||
crossAxisCount: 2,
|
SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisSpacing: 16.w,
|
crossAxisCount: 2,
|
||||||
mainAxisSpacing: 22.h,
|
crossAxisSpacing: 16.w,
|
||||||
childAspectRatio: 0.65,
|
mainAxisSpacing: 22.h,
|
||||||
),
|
childAspectRatio: 0.65,
|
||||||
|
),
|
||||||
itemCount: selectedCard.offers.length > 2
|
itemCount: selectedCard.offers.length > 2
|
||||||
? 2
|
? 2
|
||||||
: selectedCard.offers.length,
|
: selectedCard.offers.length,
|
||||||
@@ -266,7 +296,9 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFF95F62).withOpacity(.24),
|
color: const Color(
|
||||||
|
0xFFF95F62,
|
||||||
|
).withOpacity(.24),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12.sp),
|
borderRadius: BorderRadius.circular(12.sp),
|
||||||
),
|
),
|
||||||
@@ -276,62 +308,75 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
/// Image
|
/// Image
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8.sp),
|
borderRadius: BorderRadius.circular(8.sp),
|
||||||
child: offer.mobileBannerImage != null &&
|
child:
|
||||||
offer.mobileBannerImage!.isNotEmpty
|
offer.mobileBannerImage != null &&
|
||||||
|
offer
|
||||||
|
.mobileBannerImage!
|
||||||
|
.isNotEmpty
|
||||||
? Image.network(
|
? Image.network(
|
||||||
'${ApiUrls.baseUrl}/${offer.mobileBannerImage}',
|
'${ApiUrls.baseUrl}/${offer.mobileBannerImage}',
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 120.5.h,
|
height: 120.5.h,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder:
|
||||||
return Container(
|
(context, error, stackTrace) {
|
||||||
width: double.infinity,
|
return Container(
|
||||||
height: 120.5.h,
|
width: double.infinity,
|
||||||
color: const Color(0xFFFEE7E7),
|
height: 120.5.h,
|
||||||
child: Icon(
|
color: const Color(
|
||||||
Icons.local_offer,
|
0xFFFEE7E7,
|
||||||
size: 40.sp,
|
),
|
||||||
color:
|
child: Icon(
|
||||||
const Color(0xFFF95F62).withOpacity(.6),
|
Icons.local_offer,
|
||||||
),
|
size: 40.sp,
|
||||||
);
|
color: const Color(
|
||||||
},
|
0xFFF95F62,
|
||||||
loadingBuilder:
|
).withOpacity(.6),
|
||||||
(context, child, loadingProgress) {
|
),
|
||||||
if (loadingProgress == null) return child;
|
);
|
||||||
|
},
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null)
|
||||||
|
return child;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 120.5.h,
|
height: 120.5.h,
|
||||||
color: const Color(0xFFFEE7E7),
|
color: const Color(
|
||||||
child: Center(
|
0xFFFEE7E7,
|
||||||
child: CircularProgressIndicator(
|
),
|
||||||
strokeWidth: 2,
|
child: Center(
|
||||||
color: const Color(0xFFF95F62),
|
child: CircularProgressIndicator(
|
||||||
value: loadingProgress
|
strokeWidth: 2,
|
||||||
.expectedTotalBytes !=
|
color: const Color(
|
||||||
null
|
0xFFF95F62,
|
||||||
? loadingProgress
|
),
|
||||||
.cumulativeBytesLoaded /
|
value:
|
||||||
loadingProgress
|
loadingProgress
|
||||||
.expectedTotalBytes!
|
.expectedTotalBytes !=
|
||||||
: null,
|
null
|
||||||
|
? loadingProgress
|
||||||
|
.cumulativeBytesLoaded /
|
||||||
|
loadingProgress
|
||||||
|
.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 120.5.h,
|
||||||
|
color: const Color(0xFFFEE7E7),
|
||||||
|
child: Icon(
|
||||||
|
Icons.local_offer,
|
||||||
|
size: 40.sp,
|
||||||
|
color: const Color(
|
||||||
|
0xFFF95F62,
|
||||||
|
).withOpacity(.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 120.5.h,
|
|
||||||
color: const Color(0xFFFEE7E7),
|
|
||||||
child: Icon(
|
|
||||||
Icons.local_offer,
|
|
||||||
size: 40.sp,
|
|
||||||
color:
|
|
||||||
const Color(0xFFF95F62).withOpacity(.6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
@@ -348,7 +393,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
|
|
||||||
/// Offer Code
|
/// Offer Code
|
||||||
CustomText(
|
CustomText(
|
||||||
text: offer.description??"N/A",
|
text: offer.description ?? "N/A",
|
||||||
color: Colors.black.withOpacity(.6),
|
color: Colors.black.withOpacity(.6),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
@@ -366,7 +411,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
height: 100.h,
|
height: 100.h,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "No offers available",
|
text: AppLocalizations.of(context)!.noOffersAvailable,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
@@ -383,7 +428,11 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0.w),
|
padding: EdgeInsets.symmetric(horizontal: 20.0.w),
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "Available Attractions", size: 18.sp),
|
text: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.availableAttractionsTitle,
|
||||||
|
size: 18.sp,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 12.h),
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
@@ -404,7 +453,9 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
width: 104.w,
|
width: 104.w,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
borderRadius: BorderRadius.circular(
|
||||||
|
8.r,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -414,35 +465,60 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
borderRadius: BorderRadius.circular(
|
||||||
child: attraction.thumbnail != null &&
|
8.r,
|
||||||
attraction.thumbnail!.isNotEmpty
|
|
||||||
? Image.network(
|
|
||||||
attraction.thumbnail!,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
return Icon(
|
|
||||||
Icons.location_on,
|
|
||||||
size: 40.sp,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
|
||||||
if (loadingProgress == null) return child;
|
|
||||||
return Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20.w,
|
|
||||||
height: 20.w,
|
|
||||||
child: CircularProgressIndicator(color: Color(0xffF95F62),strokeWidth: 2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Icons.location_on,
|
|
||||||
size: 40.sp,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
),
|
||||||
|
child:
|
||||||
|
attraction.thumbnail != null &&
|
||||||
|
attraction
|
||||||
|
.thumbnail!
|
||||||
|
.isNotEmpty
|
||||||
|
? Image.network(
|
||||||
|
attraction.thumbnail!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
) {
|
||||||
|
return Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
size: 40.sp,
|
||||||
|
color:
|
||||||
|
Colors.grey[400],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loadingBuilder:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
child,
|
||||||
|
loadingProgress,
|
||||||
|
) {
|
||||||
|
if (loadingProgress ==
|
||||||
|
null)
|
||||||
|
return child;
|
||||||
|
return Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20.w,
|
||||||
|
height: 20.w,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color: Color(
|
||||||
|
0xffF95F62,
|
||||||
|
),
|
||||||
|
strokeWidth:
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
size: 40.sp,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -468,7 +544,9 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
height: 100.h,
|
height: 100.h,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "No attractions available",
|
text: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.noAttractionsAvailable,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
@@ -485,7 +563,7 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "View All",
|
text: AppLocalizations.of(context)!.viewAll,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
),
|
),
|
||||||
@@ -503,4 +581,4 @@ class _BuyPassContentState extends State<BuyPassContent> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
import '../../common_packages/common_app_texts.dart';
|
import '../../common_packages/common_app_texts.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class FeatureTable extends StatelessWidget {
|
class FeatureTable extends StatelessWidget {
|
||||||
const FeatureTable({super.key});
|
const FeatureTable({super.key});
|
||||||
@@ -9,15 +9,15 @@ class FeatureTable extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final features = [
|
final features = [
|
||||||
FeatureModel('Access to attractions', true, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToAttractions, true, true),
|
||||||
FeatureModel('Entry to attractions', true, true),
|
FeatureModel(AppLocalizations.of(context)!.featureEntryToAttractions, true, true),
|
||||||
FeatureModel('Access to experiences', true, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToExperiences, true, true),
|
||||||
FeatureModel('Entry to sites', false, true),
|
FeatureModel(AppLocalizations.of(context)!.featureEntryToSites, false, true),
|
||||||
FeatureModel('Access to venues', true, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToVenues, true, true),
|
||||||
FeatureModel('Entry to events', true, true),
|
FeatureModel(AppLocalizations.of(context)!.featureEntryToEvents, true, true),
|
||||||
FeatureModel('Access to experiences', false, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToExperiences, false, true),
|
||||||
FeatureModel('Access to Itinerary creation', false, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToItineraryCreation, false, true),
|
||||||
FeatureModel('Access to postcard creation', false, true),
|
FeatureModel(AppLocalizations.of(context)!.featureAccessToPostcardCreation, false, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
@@ -44,7 +44,7 @@ class FeatureTable extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
children: [
|
children: [
|
||||||
_buildHeaderRow(),
|
_buildHeaderRow(context),
|
||||||
...features.map(_buildFeatureRow).toList(),
|
...features.map(_buildFeatureRow).toList(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -54,13 +54,13 @@ class FeatureTable extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HEADER ROW
|
// HEADER ROW
|
||||||
TableRow _buildHeaderRow() {
|
TableRow _buildHeaderRow(BuildContext context) {
|
||||||
return TableRow(
|
return TableRow(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12.h),
|
padding: EdgeInsets.only(bottom: 12.h),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Features',
|
AppLocalizations.of(context)!.featuresTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 15.sp,
|
fontSize: 15.sp,
|
||||||
@@ -68,7 +68,7 @@ class FeatureTable extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildHeaderText(CommonAppText.selectiveCard),
|
_buildHeaderText(CommonAppText.selectiveCard),
|
||||||
_buildHeaderText('Unlimited'),
|
_buildHeaderText(AppLocalizations.of(context)!.unlimitedTitle),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:citycards_customer/common_packages/custom_text.dart';
|
import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
class PassCardView extends StatelessWidget {
|
class PassCardView extends StatelessWidget {
|
||||||
final Color? themeColor;
|
final Color? themeColor;
|
||||||
final String? city;
|
final String? city;
|
||||||
@@ -95,7 +95,7 @@ class PassCardView extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"From ",
|
AppLocalizations.of(context)!.fromPrefix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withOpacity(0.6),
|
||||||
fontSize: 11.sp,
|
fontSize: 11.sp,
|
||||||
@@ -111,7 +111,7 @@ class PassCardView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
" /Adult",
|
AppLocalizations.of(context)!.perAdultSuffix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.8),
|
color: Colors.black.withOpacity(0.8),
|
||||||
fontSize: 11.sp,
|
fontSize: 11.sp,
|
||||||
@@ -125,7 +125,7 @@ class PassCardView extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"and ",
|
AppLocalizations.of(context)!.andPrefix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withOpacity(0.6),
|
||||||
fontSize: 11.sp,
|
fontSize: 11.sp,
|
||||||
@@ -141,7 +141,7 @@ class PassCardView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
" /child",
|
AppLocalizations.of(context)!.perChildSuffix,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.8),
|
color: Colors.black.withOpacity(0.8),
|
||||||
fontSize: 11.sp,
|
fontSize: 11.sp,
|
||||||
@@ -153,8 +153,7 @@ class PassCardView extends StatelessWidget {
|
|||||||
|
|
||||||
/// Description
|
/// Description
|
||||||
CustomText(
|
CustomText(
|
||||||
text: description ??
|
text: description ?? AppLocalizations.of(context)!.diveIntoSelection,
|
||||||
"Dive into an extensive selection of thrilling destinations!",
|
|
||||||
color: const Color(0xFF000000).withOpacity(0.6),
|
color: const Color(0xFF000000).withOpacity(0.6),
|
||||||
size: 11.sp,
|
size: 11.sp,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import '../bloc/buy_pass_event.dart';
|
|||||||
import '../bloc/buy_pass_state.dart';
|
import '../bloc/buy_pass_state.dart';
|
||||||
import '../models/checkout_model.dart';
|
import '../models/checkout_model.dart';
|
||||||
import '../../checkout/view/checkout_view.dart';
|
import '../../checkout/view/checkout_view.dart';
|
||||||
import '../repository/buy_pass_repository.dart'; // ✅ Import repository
|
import '../repository/buy_pass_repository.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
class PaymentCard extends StatefulWidget {
|
class PaymentCard extends StatefulWidget {
|
||||||
final String city;
|
final String city;
|
||||||
final String heroImage;
|
final String heroImage;
|
||||||
@@ -111,19 +111,19 @@ class _PaymentCardState extends State<PaymentCard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
_buildCounterRow("No. of Adults", widget.adults, widget.onAdultChanged, context, minValue: 1),
|
_buildCounterRow(AppLocalizations.of(context)!.noOfAdultsLabel, widget.adults, widget.onAdultChanged, context, minValue: 1),
|
||||||
SizedBox(height: 10.h),
|
SizedBox(height: 10.h),
|
||||||
_buildCounterRow("No. of Children", widget.children, widget.onChildChanged, context),
|
_buildCounterRow(AppLocalizations.of(context)!.noOfChildrenLabel, widget.children, widget.onChildChanged, context),
|
||||||
SizedBox(height: 10.h),
|
SizedBox(height: 10.h),
|
||||||
if (isUnlimitedCard)
|
if (isUnlimitedCard)
|
||||||
_buildDropdownRow(
|
_buildDropdownRow(
|
||||||
label: "No. of Days",
|
label: AppLocalizations.of(context)!.noOfDaysLabel,
|
||||||
value: widget.selectedValue,
|
value: widget.selectedValue,
|
||||||
onChanged: widget.onValidityChanged,
|
onChanged: widget.onValidityChanged,
|
||||||
)
|
)
|
||||||
else if (isSelectivePass)
|
else if (isSelectivePass)
|
||||||
_buildDropdownRow(
|
_buildDropdownRow(
|
||||||
label: "No. of Attractions",
|
label: AppLocalizations.of(context)!.noOfAttractionsLabel,
|
||||||
value: widget.selectedValue,
|
value: widget.selectedValue,
|
||||||
onChanged: widget.onValidityChanged,
|
onChanged: widget.onValidityChanged,
|
||||||
),
|
),
|
||||||
@@ -132,7 +132,7 @@ class _PaymentCardState extends State<PaymentCard> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You Pay",
|
text: AppLocalizations.of(context)!.youPayLabel,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -234,9 +234,10 @@ class _PaymentCardState extends State<PaymentCard> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ✅ Show error message
|
// ✅ Show error message
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
String errorMessage = e.toString().replaceFirst('Exception: ', '');
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Failed to proceed: ${e.toString()}'),
|
content: Text(errorMessage),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
duration: Duration(seconds: 3),
|
duration: Duration(seconds: 3),
|
||||||
@@ -247,7 +248,7 @@ class _PaymentCardState extends State<PaymentCard> {
|
|||||||
bloc.add(AddToCartDone()); // ✅ stop loading
|
bloc.add(AddToCartDone()); // ✅ stop loading
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label: isLoading ? "Please wait..." : "Proceed to Pay",
|
label: isLoading ? AppLocalizations.of(context)!.pleaseWaitLabel : AppLocalizations.of(context)!.proceedToPayLabel,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -341,8 +342,8 @@ class _PaymentCardState extends State<PaymentCard> {
|
|||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
minValue == 1
|
minValue == 1
|
||||||
? "At least 1 adult is required"
|
? AppLocalizations.of(context)!.atLeastOneAdultRequired
|
||||||
: "Cannot go below 0",
|
: AppLocalizations.of(context)!.cannotGoBelowZero,
|
||||||
),
|
),
|
||||||
backgroundColor: const Color(0xFFF95F62),
|
backgroundColor: const Color(0xFFF95F62),
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../blocs/myPassCart/my_pass_cart_event.dart';
|
|||||||
import '../blocs/myPostcardsCart/my_postcards_cart_bloc.dart';
|
import '../blocs/myPostcardsCart/my_postcards_cart_bloc.dart';
|
||||||
import 'my_pass_cart_page_view.dart';
|
import 'my_pass_cart_page_view.dart';
|
||||||
import 'my_postcard_cart_page_view.dart';
|
import 'my_postcard_cart_page_view.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MyCartPage extends StatefulWidget {
|
class MyCartPage extends StatefulWidget {
|
||||||
const MyCartPage({super.key});
|
const MyCartPage({super.key});
|
||||||
@@ -45,7 +46,7 @@ class _MyCartPageState extends State<MyCartPage> {
|
|||||||
showCart: false,
|
showCart: false,
|
||||||
showDivider: true,
|
showDivider: true,
|
||||||
),
|
),
|
||||||
backWidget(context, "Your Cart", Colors.black),
|
backWidget(context, AppLocalizations.of(context)!.yourCartTitle, Colors.black),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.all(4.w),
|
padding: EdgeInsets.all(4.w),
|
||||||
@@ -55,8 +56,8 @@ class _MyCartPageState extends State<MyCartPage> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_tabButton("My Passes", 0),
|
_tabButton(AppLocalizations.of(context)!.myCardsTab, 0),
|
||||||
_tabButton("My Post Cards", 1),
|
_tabButton(AppLocalizations.of(context)!.myPostCardsTab, 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import '../../add_details/add_details_view.dart';
|
|||||||
import '../../buy_a_pass/models/checkout_model.dart';
|
import '../../buy_a_pass/models/checkout_model.dart';
|
||||||
import '../../checkout/view/checkout_view.dart';
|
import '../../checkout/view/checkout_view.dart';
|
||||||
import '../../checkout/widget/pass_purchase_details_bottomsheet.dart';
|
import '../../checkout/widget/pass_purchase_details_bottomsheet.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../../login/view/login_email_bottomsheet.dart';
|
import '../../login/view/login_email_bottomsheet.dart';
|
||||||
import '../../common_packages/common_app_texts.dart';
|
import '../../common_packages/common_app_texts.dart';
|
||||||
import '../../localPreference/local_preference.dart';
|
import '../../localPreference/local_preference.dart';
|
||||||
import '../blocs/myPassCart/my_pass_cart_bloc.dart';
|
import '../blocs/myPassCart/my_pass_cart_bloc.dart';
|
||||||
import '../blocs/myPassCart/my_pass_cart_event.dart';
|
import '../blocs/myPassCart/my_pass_cart_event.dart';
|
||||||
import '../blocs/myPassCart/my_pass_cart_state.dart';
|
import '../blocs/myPassCart/my_pass_cart_state.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MyPassesCartPage extends StatefulWidget {
|
class MyPassesCartPage extends StatefulWidget {
|
||||||
const MyPassesCartPage({super.key});
|
const MyPassesCartPage({super.key});
|
||||||
@@ -52,7 +54,7 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
final apiCartData = state.apiCartData;
|
final apiCartData = state.apiCartData;
|
||||||
|
|
||||||
if (apiCartData.cartItems.isEmpty) {
|
if (apiCartData.cartItems.isEmpty) {
|
||||||
return const Center(child: Text('Your cart is empty'));
|
return Center(child: Text(AppLocalizations.of(context)!.yourCartIsEmpty));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
@@ -82,8 +84,8 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains("unlimited");
|
.contains("unlimited");
|
||||||
final String validityLabel = isUnlimitedCard
|
final String validityLabel = isUnlimitedCard
|
||||||
? "$validityDuration Days"
|
? "$validityDuration${AppLocalizations.of(context)!.daysLabelSuffix}"
|
||||||
: "${cartItem.noOfAttractions} Attractions";
|
: "${cartItem.noOfAttractions}${AppLocalizations.of(context)!.attractionsLabelSuffix}";
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: 15.h),
|
padding: EdgeInsets.only(bottom: 15.h),
|
||||||
@@ -92,14 +94,14 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
final checkoutData = CheckoutData(
|
final checkoutData = CheckoutData(
|
||||||
cityName: cityName,
|
cityName: cityName,
|
||||||
heroImage: heroImage,
|
heroImage: heroImage,
|
||||||
cardTypeName: cardTypeName,
|
cardTypeName: isUnlimitedCard ? "unlimited_card" : "flexi_pass",
|
||||||
cardDisplayName: cardDisplayName,
|
cardDisplayName: cardDisplayName,
|
||||||
themeColor: Color(themeColor),
|
themeColor: Color(themeColor),
|
||||||
adultCount: adultCount,
|
adultCount: adultCount,
|
||||||
childCount: childCount,
|
childCount: childCount,
|
||||||
adultPrice: 0.0,
|
adultPrice: 0.0,
|
||||||
childPrice: 0.0,
|
childPrice: 0.0,
|
||||||
validityDuration: validityDuration,
|
validityDuration: isUnlimitedCard ? validityDuration : cartItem.noOfAttractions,
|
||||||
totalPrice: cartItem.baseAmount,
|
totalPrice: cartItem.baseAmount,
|
||||||
description: null,
|
description: null,
|
||||||
);
|
);
|
||||||
@@ -164,8 +166,8 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
|
|
||||||
final bool isUnlimitedCard = cardTypeName == "unlimited_card";
|
final bool isUnlimitedCard = cardTypeName == "unlimited_card";
|
||||||
final String validityLabel = isUnlimitedCard
|
final String validityLabel = isUnlimitedCard
|
||||||
? "$validityDuration Days"
|
? "$validityDuration${AppLocalizations.of(context)!.daysLabelSuffix}"
|
||||||
: "$validityDuration Attractions";
|
: "$validityDuration${AppLocalizations.of(context)!.attractionsLabelSuffix}";
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
@@ -173,15 +175,43 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 22.h),
|
SizedBox(height: 22.h),
|
||||||
_CartItemCard(
|
GestureDetector(
|
||||||
heroImage: heroImage,
|
onTap: () {
|
||||||
cityName: cityName,
|
final checkoutData = CheckoutData(
|
||||||
validityLabel: validityLabel,
|
cityName: cityName,
|
||||||
adultCount: adultCount,
|
heroImage: heroImage,
|
||||||
childCount: childCount,
|
cardTypeName: cardTypeName,
|
||||||
totalPrice: totalPrice,
|
cardDisplayName: cardDisplayName,
|
||||||
themeColor: themeColor,
|
themeColor: Color(themeColor),
|
||||||
cardDisplayName: cardDisplayName,
|
adultCount: adultCount,
|
||||||
|
childCount: childCount,
|
||||||
|
adultPrice: adultPrice,
|
||||||
|
childPrice: childPrice,
|
||||||
|
validityDuration: validityDuration,
|
||||||
|
totalPrice: totalPrice,
|
||||||
|
description: description,
|
||||||
|
);
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const CheckoutView(
|
||||||
|
bookingId: 0,
|
||||||
|
couponId: null,
|
||||||
|
),
|
||||||
|
settings: RouteSettings(arguments: checkoutData),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _CartItemCard(
|
||||||
|
heroImage: heroImage,
|
||||||
|
cityName: cityName,
|
||||||
|
validityLabel: validityLabel,
|
||||||
|
adultCount: adultCount,
|
||||||
|
childCount: childCount,
|
||||||
|
totalPrice: totalPrice,
|
||||||
|
themeColor: themeColor,
|
||||||
|
cardDisplayName: cardDisplayName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
],
|
],
|
||||||
@@ -198,14 +228,14 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
children: [
|
children: [
|
||||||
Image.asset("assets/gif/empty_cart.gif", width: 250.w),
|
Image.asset("assets/gif/empty_cart.gif", width: 250.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You do not have any passes",
|
text: AppLocalizations.of(context)!.youDoNotHaveAnyPasses,
|
||||||
size: 22.sp,
|
size: 22.sp,
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
Text(
|
Text(
|
||||||
"Get a pass and get offers and discounts and more on your trip to your favourite city",
|
AppLocalizations.of(context)!.emptyPassesDescription,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF656565),
|
color: const Color(0xFF656565),
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
@@ -217,7 +247,7 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
label: "Buy a Pass",
|
label: AppLocalizations.of(context)!.buyAPassLabel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -233,7 +263,7 @@ class _MyPassesCartPageState extends State<MyPassesCartPage> {
|
|||||||
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Error loading cart",
|
text: AppLocalizations.of(context)!.errorLoadingCart,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
@@ -298,32 +328,30 @@ class _CartItemCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: heroImage.isNotEmpty
|
child: heroImage.isNotEmpty
|
||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: heroImage,
|
imageUrl: heroImage,
|
||||||
width: 105.w,
|
width: 105.w,
|
||||||
height: 130.h,
|
height: 130.h,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorWidget: (context, url, error) => Image.asset(
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
"assets/images/card_banner.png",
|
width: 105.w,
|
||||||
scale: 4,
|
height: 130.h,
|
||||||
width: 105.w,
|
borderRadius: 0,
|
||||||
height: 123.h,
|
),
|
||||||
fit: BoxFit.cover,
|
errorWidget: (context, url, error) => Image.asset(
|
||||||
),
|
"assets/images/card_banner.png",
|
||||||
placeholder: (context, url) => Image.asset(
|
scale: 4,
|
||||||
"assets/images/card_banner.png",
|
width: 105.w,
|
||||||
scale: 4,
|
height: 123.h,
|
||||||
width: 105.w,
|
fit: BoxFit.cover,
|
||||||
height: 123.h,
|
),
|
||||||
fit: BoxFit.cover,
|
)
|
||||||
),
|
|
||||||
)
|
|
||||||
: Image.asset(
|
: Image.asset(
|
||||||
"assets/images/card_banner.png",
|
"assets/images/card_banner.png",
|
||||||
scale: 4,
|
scale: 4,
|
||||||
width: 105.w,
|
width: 105.w,
|
||||||
height: 123.h,
|
height: 123.h,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 6.66.w),
|
SizedBox(width: 6.66.w),
|
||||||
|
|
||||||
@@ -358,7 +386,7 @@ class _CartItemCard extends StatelessWidget {
|
|||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"$adultCount ${adultCount == 1 ? 'adult' : 'adults'}",
|
"$adultCount ${adultCount == 1 ? AppLocalizations.of(context)!.adultLabelSingular : AppLocalizations.of(context)!.adultLabelPlural}",
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -379,7 +407,7 @@ class _CartItemCard extends StatelessWidget {
|
|||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"$childCount ${childCount == 1 ? 'Kid' : 'Kids'}",
|
"$childCount ${childCount == 1 ? AppLocalizations.of(context)!.kidLabelSingular : AppLocalizations.of(context)!.kidLabelPlural}",
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import '../blocs/myPostcardsCart/my_postcards_cart_bloc.dart';
|
|||||||
import '../blocs/myPostcardsCart/my_postcards_cart_state.dart';
|
import '../blocs/myPostcardsCart/my_postcards_cart_state.dart';
|
||||||
import '../model/my_postcards_cart_model.dart';
|
import '../model/my_postcards_cart_model.dart';
|
||||||
import '../widget/ticket_card_view.dart';
|
import '../widget/ticket_card_view.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MyPostCardsCartPage extends StatelessWidget {
|
class MyPostCardsCartPage extends StatelessWidget {
|
||||||
const MyPostCardsCartPage({super.key});
|
const MyPostCardsCartPage({super.key});
|
||||||
@@ -76,7 +77,7 @@ class _CartLoadedScreenState extends State<_CartLoadedScreen> {
|
|||||||
|
|
||||||
// Height of one card slot (card height + bottom padding).
|
// Height of one card slot (card height + bottom padding).
|
||||||
// 330h card + 20h gap = 350. Adjust if your device renders differently.
|
// 330h card + 20h gap = 350. Adjust if your device renders differently.
|
||||||
static const double _cardItemHeight = 350.0;
|
double get _cardItemHeight => 330.h + 20.h;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -93,6 +94,7 @@ class _CartLoadedScreenState extends State<_CartLoadedScreen> {
|
|||||||
|
|
||||||
void _onScroll() {
|
void _onScroll() {
|
||||||
final offset = _scrollController.offset;
|
final offset = _scrollController.offset;
|
||||||
|
// Use round() but based on dynamic height, not hardcoded
|
||||||
final newIndex = (offset / _cardItemHeight).round();
|
final newIndex = (offset / _cardItemHeight).round();
|
||||||
final clamped = newIndex.clamp(0, widget.cartData.cartItems.length - 1);
|
final clamped = newIndex.clamp(0, widget.cartData.cartItems.length - 1);
|
||||||
if (clamped != _selectedIndex) {
|
if (clamped != _selectedIndex) {
|
||||||
@@ -173,7 +175,7 @@ class _CartLoadedScreenState extends State<_CartLoadedScreen> {
|
|||||||
SizedBox(width: 10.w),
|
SizedBox(width: 10.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'You can purchase one postcard at a time',
|
AppLocalizations.of(context)!.purchaseOnePostcardAtTime,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
color: const Color(0xFF212121),
|
color: const Color(0xFF212121),
|
||||||
@@ -324,7 +326,7 @@ class _CartLoadedScreenState extends State<_CartLoadedScreen> {
|
|||||||
// Navigator.pop(context);
|
// Navigator.pop(context);
|
||||||
_navigateToCheckout(items[_selectedIndex]);
|
_navigateToCheckout(items[_selectedIndex]);
|
||||||
},
|
},
|
||||||
label: "Proceed to Checkout",
|
label: AppLocalizations.of(context)!.proceedToCheckoutLabel,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 14.h),
|
SizedBox(height: 14.h),
|
||||||
@@ -350,14 +352,14 @@ class _NotLoggedInScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Image.asset("assets/gif/empty_cart.gif", width: 250.w),
|
Image.asset("assets/gif/empty_cart.gif", width: 250.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You are not logged in yet!",
|
text: AppLocalizations.of(context)!.notLoggedInYet,
|
||||||
size: 22.sp,
|
size: 22.sp,
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
Text(
|
Text(
|
||||||
"To access my postcards cart please login",
|
AppLocalizations.of(context)!.loginToAccessPostcardsCart,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF656565),
|
color: const Color(0xFF656565),
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
@@ -377,7 +379,7 @@ class _NotLoggedInScreen extends StatelessWidget {
|
|||||||
builder: (_) => const LoginEmailBottomsheet(),
|
builder: (_) => const LoginEmailBottomsheet(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: "Login to Checkout",
|
label: AppLocalizations.of(context)!.loginToCheckoutLabel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -404,7 +406,7 @@ class _EmptyCartScreen extends StatelessWidget {
|
|||||||
Image.asset('assets/gif/empty_post_card.gif', width: 200.w),
|
Image.asset('assets/gif/empty_post_card.gif', width: 200.w),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Text(
|
Text(
|
||||||
'You do not have any postcards',
|
AppLocalizations.of(context)!.youDoNotHaveAnyPostcards,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 20.sp,
|
fontSize: 20.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -414,7 +416,7 @@ class _EmptyCartScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
Text(
|
Text(
|
||||||
"You do not possess any postcards yet nor have you sent to anyone",
|
AppLocalizations.of(context)!.emptyPostcardsDescription,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
@@ -427,7 +429,7 @@ class _EmptyCartScreen extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
label: "Design my postcard",
|
label: AppLocalizations.of(context)!.designMyPostcardLabel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -459,7 +461,7 @@ class _ErrorScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Text(
|
Text(
|
||||||
'Something went wrong',
|
AppLocalizations.of(context)!.somethingWentWrong,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -486,7 +488,7 @@ class _ErrorScreen extends StatelessWidget {
|
|||||||
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h),
|
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Retry',
|
AppLocalizations.of(context)!.retryLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xffF95F62),
|
color: const Color(0xffF95F62),
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:citycards_customer/networkApiServices/api_urls.dart';
|
import 'package:citycards_customer/networkApiServices/api_urls.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../model/my_postcards_cart_model.dart';
|
import '../model/my_postcards_cart_model.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class TicketCard extends StatelessWidget {
|
class TicketCard extends StatelessWidget {
|
||||||
final CartItem cartItem;
|
final CartItem cartItem;
|
||||||
@@ -35,27 +37,15 @@ class TicketCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16.r),
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
child: cartItem.pcImagePath.isNotEmpty
|
child: cartItem.pcImagePath.isNotEmpty
|
||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl:
|
imageUrl: '${ApiUrls.baseUrl}${cartItem.pcImagePath}',
|
||||||
'${ApiUrls.baseUrl}${cartItem.pcImagePath}',
|
|
||||||
width: 210.w,
|
width: 210.w,
|
||||||
height: 170.h,
|
height: 170.h,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
progressIndicatorBuilder:
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
(context, url, progress) {
|
width: 210.w,
|
||||||
return Container(
|
height: 170.h,
|
||||||
width: 210.w,
|
borderRadius: 16.r,
|
||||||
height: 170.h,
|
),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade200,
|
|
||||||
borderRadius: BorderRadius.circular(16.r),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: const Color(0xffF95F62),
|
|
||||||
value: progress.progress,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
errorWidget: (_, __, ___) => _placeholderImage(),
|
errorWidget: (_, __, ___) => _placeholderImage(),
|
||||||
)
|
)
|
||||||
: _placeholderImage(),
|
: _placeholderImage(),
|
||||||
@@ -81,7 +71,7 @@ class TicketCard extends StatelessWidget {
|
|||||||
|
|
||||||
// ── Title ──
|
// ── Title ──
|
||||||
Text(
|
Text(
|
||||||
cartItem.pcTitle.isNotEmpty ? cartItem.pcTitle : 'No Title',
|
cartItem.pcTitle.isNotEmpty ? cartItem.pcTitle : AppLocalizations.of(context)!.noTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -108,7 +98,7 @@ class TicketCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Edit Draft',
|
AppLocalizations.of(context)!.editDraftLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xffF95F62),
|
color: const Color(0xffF95F62),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import '../../StripePayment/view/stripe_payment.dart';
|
import '../../StripePayment/view/stripe_payment.dart';
|
||||||
import '../../add_details/add_details_view.dart';
|
import '../../add_details/add_details_view.dart';
|
||||||
import '../../buy_a_pass/models/checkout_model.dart';
|
import '../../buy_a_pass/models/checkout_model.dart';
|
||||||
|
import '../../common_packages/back_widget.dart';
|
||||||
import '../../common_packages/custom_snackbar.dart';
|
import '../../common_packages/custom_snackbar.dart';
|
||||||
import '../../itinerary_creation/bloc/get_itinerary_bloc.dart';
|
import '../../itinerary_creation/bloc/get_itinerary_bloc.dart';
|
||||||
import '../../localPreference/local_preference.dart';
|
import '../../localPreference/local_preference.dart';
|
||||||
@@ -22,6 +23,7 @@ import '../widget/pass_purchase_details_bottomsheet.dart';
|
|||||||
import '../repository/all_coupons_repository.dart';
|
import '../repository/all_coupons_repository.dart';
|
||||||
import '../repository/checkout_repository.dart';
|
import '../repository/checkout_repository.dart';
|
||||||
import '../models/all_coupons_model.dart';
|
import '../models/all_coupons_model.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class CheckoutView extends StatefulWidget {
|
class CheckoutView extends StatefulWidget {
|
||||||
final int bookingId;
|
final int bookingId;
|
||||||
@@ -65,7 +67,7 @@ class _CheckoutViewState extends State<CheckoutView> {
|
|||||||
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
Icon(Icons.error_outline, size: 60.sp, color: Colors.red),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "No checkout data available",
|
text: AppLocalizations.of(context)!.noCheckoutDataAvailable,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
@@ -78,7 +80,7 @@ class _CheckoutViewState extends State<CheckoutView> {
|
|||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text("Go Back"),
|
child: Text(AppLocalizations.of(context)!.goBackButtonLabel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -143,10 +145,10 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
amount: finalTotal,
|
amount: finalTotal,
|
||||||
currencySymbol: '\$',
|
currencySymbol: '\$',
|
||||||
title: 'Complete Payment',
|
title: AppLocalizations.of(context)!.completePaymentTitle,
|
||||||
loadingMessage: 'Processing your pass payment...',
|
loadingMessage: AppLocalizations.of(context)!.processingPaymentMessage,
|
||||||
successMessage: 'Payment Successful!\nYour pass is ready.',
|
successMessage: AppLocalizations.of(context)!.paymentSuccessfulMessage,
|
||||||
failureMessage: 'Payment Failed',
|
failureMessage: AppLocalizations.of(context)!.paymentFailedMessage,
|
||||||
primaryColor: const Color(0xFFF95F62),
|
primaryColor: const Color(0xFFF95F62),
|
||||||
heightRatio: 0.5,
|
heightRatio: 0.5,
|
||||||
isDismissible: false,
|
isDismissible: false,
|
||||||
@@ -172,8 +174,8 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
onPaymentCancelled: () {
|
onPaymentCancelled: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Payment cancelled'),
|
content: Text(AppLocalizations.of(context)!.paymentCancelledMessage),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -191,8 +193,8 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
context.read<GetItineraryBloc>().add(CheckLoginAndFetchItinerary());
|
context.read<GetItineraryBloc>().add(CheckLoginAndFetchItinerary());
|
||||||
context.read<MyPassesBloc>().add(CheckLoginAndFetchPasses());
|
context.read<MyPassesBloc>().add(CheckLoginAndFetchPasses());
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Payment confirmed successfully!'),
|
content: Text(AppLocalizations.of(context)!.paymentConfirmedMessage),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -333,21 +335,19 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
showCart: false,
|
showCart: false,
|
||||||
showDivider: true,
|
showDivider: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
// ✅ Back Button & Title
|
// ✅ Back Button & Title
|
||||||
Row(
|
// Row(
|
||||||
children: [
|
// children: [
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
onTap: () => Navigator.pop(context),
|
// onTap: () => Navigator.pop(context),
|
||||||
child: const Icon(Icons.arrow_back),
|
// child: const Icon(Icons.arrow_back),
|
||||||
),
|
// ),
|
||||||
SizedBox(width: 8.w),
|
// SizedBox(width: 8.w),
|
||||||
CustomText(text: "Checkout", size: 12.sp),
|
// CustomText(text: AppLocalizations.of(context)!.checkoutTitle, size: 12.sp),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
|
backWidget(context, AppLocalizations.of(context)!.checkoutTitle, Colors.black),
|
||||||
SizedBox(height: 22.h),
|
SizedBox(height: 22.h),
|
||||||
|
|
||||||
// ✅ PASS CARD SECTION
|
// ✅ PASS CARD SECTION
|
||||||
Container(
|
Container(
|
||||||
height: 140.h,
|
height: 140.h,
|
||||||
@@ -422,7 +422,9 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 5.h),
|
SizedBox(height: 5.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: widget.checkoutData.validityLabel,
|
text: widget.checkoutData.isUnlimitedCard
|
||||||
|
? "${widget.checkoutData.validityDuration} ${AppLocalizations.of(context)!.daysLabelSuffix}"
|
||||||
|
: "${widget.checkoutData.validityDuration} ${AppLocalizations.of(context)!.attractionsLabelSuffix}",
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -439,7 +441,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"${widget.checkoutData.adultCount} adult${widget.checkoutData.adultCount > 1 ? 's' : ''}",
|
"${widget.checkoutData.adultCount} ${widget.checkoutData.adultCount > 1 ? AppLocalizations.of(context)!.adultLabelPlural : AppLocalizations.of(context)!.adultLabelSingular}",
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -463,7 +465,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"${widget.checkoutData.childCount} Kid${widget.checkoutData.childCount > 1 ? 's' : ''}",
|
"${widget.checkoutData.childCount} ${widget.checkoutData.childCount > 1 ? AppLocalizations.of(context)!.kidLabelPlural : AppLocalizations.of(context)!.kidLabelSingular}",
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -550,7 +552,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Loading coupons...",
|
text: AppLocalizations.of(context)!.loadingCoupons,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
@@ -558,7 +560,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
)
|
)
|
||||||
: state is CheckoutCouponsErrorState
|
: state is CheckoutCouponsErrorState
|
||||||
? CustomText(
|
? CustomText(
|
||||||
text: "Error loading coupons",
|
text: AppLocalizations.of(context)!.errorLoadingCoupons,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
)
|
)
|
||||||
@@ -573,10 +575,10 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
children: [
|
children: [
|
||||||
CustomText(
|
CustomText(
|
||||||
text: appliedCoupon != null
|
text: appliedCoupon != null
|
||||||
? "Coupon Applied: ${appliedCoupon.couponCode}"
|
? "${AppLocalizations.of(context)!.couponAppliedPrefix}${appliedCoupon.couponCode}"
|
||||||
: state.coupons.isNotEmpty
|
: state.coupons.isNotEmpty
|
||||||
? "${state.coupons[0].discountPercent}% discount on ${state.coupons[0].title}"
|
? "${state.coupons[0].discountPercent}%${AppLocalizations.of(context)!.discountOnPrefix}${state.coupons[0].title}"
|
||||||
: "No coupons available",
|
: AppLocalizations.of(context)!.noCouponsAvailable,
|
||||||
color: const Color(0xFF262626),
|
color: const Color(0xFF262626),
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -620,7 +622,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "View all coupons",
|
text: AppLocalizations.of(context)!.viewAllCoupons,
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -682,10 +684,10 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
),
|
),
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: state.isApplyingCoupon
|
text: state.isApplyingCoupon
|
||||||
? "Applying..."
|
? AppLocalizations.of(context)!.applyingLabel
|
||||||
: (appliedCoupon != null
|
: (appliedCoupon != null
|
||||||
? "Remove"
|
? AppLocalizations.of(context)!.removeLabel
|
||||||
: "Apply"),
|
: AppLocalizations.of(context)!.applyLabel),
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
),
|
),
|
||||||
@@ -711,7 +713,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
CustomText(text: "Subtotal", size: 14.sp),
|
CustomText(text: AppLocalizations.of(context)!.subtotalLabel, size: 14.sp),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "\$${subtotal.toStringAsFixed(2)}",
|
text: "\$${subtotal.toStringAsFixed(2)}",
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
@@ -726,7 +728,7 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
CustomText(text: "Discount", size: 14.sp),
|
CustomText(text: AppLocalizations.of(context)!.discountLabel, size: 14.sp),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"-\$${discountAmount.toStringAsFixed(2)} (${discountPercentage.toStringAsFixed(0)}%)",
|
"-\$${discountAmount.toStringAsFixed(2)} (${discountPercentage.toStringAsFixed(0)}%)",
|
||||||
@@ -755,11 +757,11 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CustomText(text: 'Total', size: 14.sp),
|
CustomText(text: AppLocalizations.of(context)!.totalLabel, size: 14.sp),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text:
|
||||||
"Including \$${taxAmount.toStringAsFixed(2)} in taxes",
|
"${AppLocalizations.of(context)!.includingTaxesPrefix}\$${taxAmount.toStringAsFixed(2)}${AppLocalizations.of(context)!.inTaxesSuffix}",
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
@@ -848,10 +850,10 @@ class _CheckoutContentState extends State<_CheckoutContent> {
|
|||||||
label: isLoggedIn
|
label: isLoggedIn
|
||||||
? (widget.isPurchaseDetailsConfirmed
|
? (widget.isPurchaseDetailsConfirmed
|
||||||
? (isInitiatingPayment || isConfirmingPayment
|
? (isInitiatingPayment || isConfirmingPayment
|
||||||
? "Processing..."
|
? AppLocalizations.of(context)!.processingLabel
|
||||||
: "Pay \$${finalTotal.toStringAsFixed(2)}")
|
: "${AppLocalizations.of(context)!.payPrefixLabel}\$${finalTotal.toStringAsFixed(2)}")
|
||||||
: "Checkout")
|
: AppLocalizations.of(context)!.checkoutTitle)
|
||||||
: "Login to Checkout",
|
: AppLocalizations.of(context)!.loginToCheckoutLabel,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../bloc/allCoupons/all_coupons_bloc.dart';
|
|||||||
import '../bloc/allCoupons/all_coupons_event.dart';
|
import '../bloc/allCoupons/all_coupons_event.dart';
|
||||||
import '../bloc/allCoupons/all_coupons_state.dart';
|
import '../bloc/allCoupons/all_coupons_state.dart';
|
||||||
import '../repository/all_coupons_repository.dart';
|
import '../repository/all_coupons_repository.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AllCouponsBottomsheet extends StatelessWidget {
|
class AllCouponsBottomsheet extends StatelessWidget {
|
||||||
final Function(dynamic coupon)? onCouponSelected;
|
final Function(dynamic coupon)? onCouponSelected;
|
||||||
@@ -45,7 +46,7 @@ class AllCouponsBottomsheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 12.h),
|
SizedBox(height: 12.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "All Coupons", size: 18.sp, weight: FontWeight.w500),
|
text: AppLocalizations.of(context)!.allCouponsTitle, size: 18.sp, weight: FontWeight.w500),
|
||||||
SizedBox(height: 22.h),
|
SizedBox(height: 22.h),
|
||||||
|
|
||||||
/// --- Coupon list ---
|
/// --- Coupon list ---
|
||||||
@@ -69,7 +70,7 @@ class AllCouponsBottomsheet extends StatelessWidget {
|
|||||||
if (state.coupons.isEmpty) {
|
if (state.coupons.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "No coupons available",
|
text: AppLocalizations.of(context)!.noCouponsAvailable,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -104,7 +105,7 @@ class AllCouponsBottomsheet extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text:
|
text:
|
||||||
"${coupon.discountPercent}% discount on ${coupon.title}",
|
"${coupon.discountPercent}%${AppLocalizations.of(context)!.discountOnPrefix}${coupon.title}",
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
weight: FontWeight.w400,
|
weight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
@@ -129,7 +130,7 @@ class AllCouponsBottomsheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "Apply Coupon",
|
text: AppLocalizations.of(context)!.applyCouponLabel,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../../profile/view/edit_profile/edit_profile_view.dart';
|
|||||||
import '../bloc/pass_purchase_details_bloc.dart';
|
import '../bloc/pass_purchase_details_bloc.dart';
|
||||||
import '../bloc/pass_purchase_details_event.dart';
|
import '../bloc/pass_purchase_details_event.dart';
|
||||||
import '../bloc/pass_purchase_details_state.dart';
|
import '../bloc/pass_purchase_details_state.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class PassPurchaseBottomSheet {
|
class PassPurchaseBottomSheet {
|
||||||
static Future<String?> show(BuildContext context, {required int bookingId}) async {
|
static Future<String?> show(BuildContext context, {required int bookingId}) async {
|
||||||
@@ -87,7 +88,7 @@ class _PassPurchaseContent extends StatelessWidget {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"Purchase Details",
|
AppLocalizations.of(context)!.purchaseDetailsTitle,
|
||||||
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
@@ -120,7 +121,7 @@ class _PassPurchaseContent extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Buy Pass for Myself",
|
AppLocalizations.of(context)!.buyPassForMyselfTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: !state.isGift
|
color: !state.isGift
|
||||||
@@ -177,9 +178,9 @@ class _PassPurchaseContent extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: Text(
|
||||||
"Edit Details",
|
AppLocalizations.of(context)!.editDetailsLabel,
|
||||||
style: TextStyle(fontSize: 12, color: Colors.white),
|
style: const TextStyle(fontSize: 12, color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -215,15 +216,15 @@ class _PassPurchaseContent extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: const [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Gift the pass",
|
AppLocalizations.of(context)!.giftThePassTitle,
|
||||||
style: TextStyle(fontWeight: FontWeight.w600),
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
"Gift the pass for someone else",
|
AppLocalizations.of(context)!.giftThePassDescription,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13, color: Color(0xff9E9E9E)),
|
fontSize: 13, color: Color(0xff9E9E9E)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -280,7 +281,7 @@ class _PassPurchaseContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
"Proceed",
|
AppLocalizations.of(context)!.proceedLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
|
|||||||
@@ -1,22 +1,69 @@
|
|||||||
|
import 'package:citycards_customer/localPreference/local_preference.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
abstract class LanguageEvent{}
|
// ─── Events ────────────────────────────────────────────────────────────────
|
||||||
|
abstract class LanguageEvent {}
|
||||||
|
|
||||||
class UpdateLanguage extends LanguageEvent{
|
/// Dispatched on app start to restore the last saved locale.
|
||||||
final String language;
|
class LoadSavedLanguage extends LanguageEvent {}
|
||||||
UpdateLanguage(this.language);
|
|
||||||
|
/// Dispatched when the user picks a new language in the bottom sheet.
|
||||||
|
class UpdateLanguage extends LanguageEvent {
|
||||||
|
/// Display label shown in the bottom sheet (e.g. "Spanish / Español").
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// BCP-47 locale code (e.g. "es").
|
||||||
|
final String code;
|
||||||
|
|
||||||
|
UpdateLanguage({required this.label, required this.code});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── State ─────────────────────────────────────────────────────────────────
|
||||||
|
class LanguageState {
|
||||||
|
/// BCP-47 locale code (e.g. "en", "nl", "es").
|
||||||
|
final String code;
|
||||||
|
|
||||||
class LanguageState{
|
/// Display label shown in the UI (e.g. "English / English").
|
||||||
final String selectedLanguage;
|
final String label;
|
||||||
LanguageState(this.selectedLanguage);
|
|
||||||
|
/// The [Locale] that MaterialApp should use.
|
||||||
|
Locale get locale => Locale(code);
|
||||||
|
|
||||||
|
const LanguageState({required this.code, required this.label});
|
||||||
|
|
||||||
|
static const initial = LanguageState(
|
||||||
|
code: 'en',
|
||||||
|
label: 'English / English',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LanguageBloc extends Bloc<LanguageEvent , LanguageState>{
|
// ─── BLoC ──────────────────────────────────────────────────────────────────
|
||||||
LanguageBloc() : super(LanguageState("English / Englis")){
|
class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
|
||||||
on<UpdateLanguage>((event, emit){
|
LanguageBloc() : super(LanguageState.initial) {
|
||||||
emit(LanguageState(event.language));
|
on<LoadSavedLanguage>(_onLoadSaved);
|
||||||
});
|
on<UpdateLanguage>(_onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map of BCP-47 code → display label (must match language_selection_bottomsheet).
|
||||||
|
static const Map<String, String> _codeToLabel = {
|
||||||
|
'en': 'English / English',
|
||||||
|
'nl': 'Dutch / Nederlands',
|
||||||
|
'es': 'Spanish / Español',
|
||||||
|
'fr': 'French / Français',
|
||||||
|
'ja': 'Japanese / 日本語',
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<void> _onLoadSaved(
|
||||||
|
LoadSavedLanguage event,
|
||||||
|
Emitter<LanguageState> emit,
|
||||||
|
) async {
|
||||||
|
final code = await LocalPreference.getLanguage();
|
||||||
|
final label = _codeToLabel[code] ?? 'English / English';
|
||||||
|
emit(LanguageState(code: code, label: label));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdate(UpdateLanguage event, Emitter<LanguageState> emit) {
|
||||||
|
emit(LanguageState(code: event.code, label: event.label));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ Widget backWidget(BuildContext context, String title, Color? textColor){
|
|||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.sp,
|
fontSize: 14.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: textColor ?? Colors.black
|
color: textColor ?? Colors.black
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import '../common_bloc/bottom_navigation_bloc.dart';
|
import '../common_bloc/bottom_navigation_bloc.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class CustomBottomNavBar extends StatelessWidget {
|
class CustomBottomNavBar extends StatelessWidget {
|
||||||
const CustomBottomNavBar({super.key});
|
const CustomBottomNavBar({super.key});
|
||||||
@@ -35,28 +36,28 @@ class CustomBottomNavBar extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
index: 0,
|
index: 0,
|
||||||
iconPath: 'assets/icons/explore.png',
|
iconPath: 'assets/icons/explore.png',
|
||||||
label: 'Explore',
|
label: AppLocalizations.of(context)!.navExplore,
|
||||||
isActive: state.selectedIndex == 0,
|
isActive: state.selectedIndex == 0,
|
||||||
),
|
),
|
||||||
_buildNavItem(
|
_buildNavItem(
|
||||||
context,
|
context,
|
||||||
index: 1,
|
index: 1,
|
||||||
iconPath: 'assets/icons/magic.png',
|
iconPath: 'assets/icons/magic.png',
|
||||||
label: 'Magic Itinerary',
|
label: AppLocalizations.of(context)!.navMagicItinerary,
|
||||||
isActive: state.selectedIndex == 1,
|
isActive: state.selectedIndex == 1,
|
||||||
),
|
),
|
||||||
_buildNavItem(
|
_buildNavItem(
|
||||||
context,
|
context,
|
||||||
index: 2,
|
index: 2,
|
||||||
iconPath: 'assets/icons/pass_icon.png',
|
iconPath: 'assets/icons/pass_icon.png',
|
||||||
label: 'My Cards',
|
label: AppLocalizations.of(context)!.navMyCards,
|
||||||
isActive: state.selectedIndex == 2,
|
isActive: state.selectedIndex == 2,
|
||||||
),
|
),
|
||||||
_buildNavItem(
|
_buildNavItem(
|
||||||
context,
|
context,
|
||||||
index: 3,
|
index: 3,
|
||||||
iconPath: 'assets/icons/postcard_icon.png',
|
iconPath: 'assets/icons/postcard_icon.png',
|
||||||
label: 'Postcard',
|
label: AppLocalizations.of(context)!.navPostcard,
|
||||||
isActive: state.selectedIndex == 3,
|
isActive: state.selectedIndex == 3,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -46,11 +46,15 @@ class CustomFilledButton extends StatelessWidget {
|
|||||||
: Row(
|
: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CustomText(
|
Flexible(
|
||||||
text: label,
|
child: CustomText(
|
||||||
color: Colors.white,
|
text: label,
|
||||||
size: 16.sp,
|
color: Colors.white,
|
||||||
weight: FontWeight.w500,
|
size: 16.sp,
|
||||||
|
weight: FontWeight.w500,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (showArrow!) SizedBox(width: 8),
|
if (showArrow!) SizedBox(width: 8),
|
||||||
if (showArrow!)
|
if (showArrow!)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
import '../localPreference/local_preference.dart';
|
import '../localPreference/local_preference.dart';
|
||||||
|
|
||||||
class LanguageSelectionBottomsheet extends StatefulWidget {
|
class LanguageSelectionBottomsheet extends StatefulWidget {
|
||||||
@@ -60,7 +61,7 @@ class _LanguageSelectionBottomsheetState
|
|||||||
|
|
||||||
// Update BLoC
|
// Update BLoC
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
context.read<LanguageBloc>().add(UpdateLanguage(_pendingLabel!));
|
context.read<LanguageBloc>().add(UpdateLanguage(label: _pendingLabel!, code: code));
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ class _LanguageSelectionBottomsheetState
|
|||||||
child: BlocBuilder<LanguageBloc, LanguageState>(
|
child: BlocBuilder<LanguageBloc, LanguageState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// Seed pending selection from current BLoC state on first build
|
// Seed pending selection from current BLoC state on first build
|
||||||
_pendingLabel ??= state.selectedLanguage;
|
_pendingLabel ??= state.label;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -103,7 +104,7 @@ class _LanguageSelectionBottomsheetState
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
"Change Language",
|
AppLocalizations.of(context)!.changeLanguageTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -116,7 +117,7 @@ class _LanguageSelectionBottomsheetState
|
|||||||
TextField(
|
TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: "Search Languages",
|
hintText: AppLocalizations.of(context)!.searchLanguages,
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: const Color(0xBBC83B61).withOpacity(0.4),
|
color: const Color(0xBBC83B61).withOpacity(0.4),
|
||||||
@@ -188,7 +189,7 @@ class _LanguageSelectionBottomsheetState
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 48.h,
|
height: 48.h,
|
||||||
onTap: _onSave,
|
onTap: _onSave,
|
||||||
label: "Save",
|
label: AppLocalizations.of(context)!.save,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
|
|||||||
32
lib/common_packages/shimmer_animation.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class SkeletonWidget extends StatelessWidget {
|
||||||
|
final double height;
|
||||||
|
final double width;
|
||||||
|
final double borderRadius;
|
||||||
|
|
||||||
|
const SkeletonWidget({
|
||||||
|
super.key,
|
||||||
|
required this.height,
|
||||||
|
required this.width,
|
||||||
|
this.borderRadius = 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Shimmer.fromColors(
|
||||||
|
baseColor: Colors.grey[300]!,
|
||||||
|
highlightColor: Colors.grey[100]!,
|
||||||
|
child: Container(
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[300],
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:phone_numbers_parser/phone_numbers_parser.dart';
|
import 'package:phone_numbers_parser/phone_numbers_parser.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
countryController.text.trim().isEmpty ||
|
countryController.text.trim().isEmpty ||
|
||||||
postalController.text.trim().isEmpty) {
|
postalController.text.trim().isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Please fill all fields')),
|
SnackBar(content: Text(AppLocalizations.of(context)!.pleaseFillAllFields)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
if (!isValidPhone) {
|
if (!isValidPhone) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Enter a valid phone number for $_selectedIsdCode'),
|
content: Text(AppLocalizations.of(context)!.enterValidPhoneForIsd(_selectedIsdCode)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -150,6 +151,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
emailController.text = widget.email;
|
emailController.text = widget.email;
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
@@ -211,7 +213,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Create your account",
|
text: l10n.createYourAccount,
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -220,7 +222,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
SizedBox(height: 26.h),
|
SizedBox(height: 26.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Personal Information",
|
text: l10n.personalInformation,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -230,8 +232,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "First Name *",
|
label: l10n.firstNameLabelWithStar,
|
||||||
hint: "Enter your first name",
|
hint: l10n.enterYourFirstName,
|
||||||
controller: firstNameController,
|
controller: firstNameController,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
noSpace: true,
|
noSpace: true,
|
||||||
@@ -243,8 +245,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Last Name *",
|
label: l10n.lastNameLabelWithStar,
|
||||||
hint: "Enter your last name",
|
hint: l10n.enterYourLastName,
|
||||||
controller: lastNameController,
|
controller: lastNameController,
|
||||||
onlyLetters: true,
|
onlyLetters: true,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
@@ -256,8 +258,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Email *",
|
label: l10n.emailLabelWithStar,
|
||||||
hint: "Enter your email address",
|
hint: l10n.enterYourEmailAddress,
|
||||||
controller: emailController,
|
controller: emailController,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
@@ -268,8 +270,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Phone Number *",
|
label: l10n.phoneNumberLabelWithStar,
|
||||||
hint: "Enter phone number",
|
hint: l10n.phoneNumberHint,
|
||||||
controller: phoneController,
|
controller: phoneController,
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
maxLength: 12,
|
maxLength: 12,
|
||||||
@@ -290,9 +292,9 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
color: const Color(0xFF2D3134),
|
color: const Color(0xFF2D3134),
|
||||||
),
|
),
|
||||||
dialogTextStyle: TextStyle(fontSize: 14.sp),
|
dialogTextStyle: TextStyle(fontSize: 14.sp),
|
||||||
searchDecoration: const InputDecoration(
|
searchDecoration: InputDecoration(
|
||||||
hintText: 'Search country...',
|
hintText: l10n.searchCountryHint,
|
||||||
prefixIcon: Icon(Icons.search),
|
prefixIcon: const Icon(Icons.search),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -301,7 +303,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
SizedBox(height: 12.h),
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Location Details *",
|
text: l10n.locationDetailsWithStar,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -312,8 +314,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Address *",
|
label: l10n.addressLabelWithStar,
|
||||||
hint: "Enter your address",
|
hint: l10n.enterYourAddress,
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
),
|
),
|
||||||
@@ -325,8 +327,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "City *",
|
label: l10n.cityLabelWithStar,
|
||||||
hint: "Enter your city",
|
hint: l10n.enterYourCity,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
// noSpace: true,
|
// noSpace: true,
|
||||||
controller: cityController,
|
controller: cityController,
|
||||||
@@ -338,8 +340,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "State *",
|
label: l10n.stateLabelWithStar,
|
||||||
hint: "Enter your state",
|
hint: l10n.enterYourState,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
// noSpace: true,
|
// noSpace: true,
|
||||||
controller: stateController,
|
controller: stateController,
|
||||||
@@ -351,8 +353,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
label: "Country *",
|
label: l10n.countryLabelWithStar,
|
||||||
hint: "Enter your country",
|
hint: l10n.enterYourCountry,
|
||||||
maxLength: 50,
|
maxLength: 50,
|
||||||
// noSpace: true,
|
// noSpace: true,
|
||||||
controller: countryController,
|
controller: countryController,
|
||||||
@@ -374,8 +376,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
maxLength: 6,
|
maxLength: 6,
|
||||||
onChanged: fetchLocationFromZip,
|
onChanged: fetchLocationFromZip,
|
||||||
label: 'Zip Code *',
|
label: l10n.zipCodeLabelWithStar,
|
||||||
hint: 'Enter the zip code you reside in',
|
hint: l10n.zipCodeHint,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_isZipLoading)
|
if (_isZipLoading)
|
||||||
@@ -395,7 +397,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
Text(
|
Text(
|
||||||
"City, State & Country will auto-fill from zip",
|
l10n.autoFillZipNote,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10.sp,
|
fontSize: 10.sp,
|
||||||
color: const Color(0xFF8E8E8E),
|
color: const Color(0xFF8E8E8E),
|
||||||
@@ -413,13 +415,13 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
|||||||
return CustomFilledButton(
|
return CustomFilledButton(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
label: "Creating...",
|
label: l10n.creatingLabelEllipsis,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return CustomFilledButton(
|
return CustomFilledButton(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
onTap: () => _submitForm(context),
|
onTap: () => _submitForm(context),
|
||||||
label: "Create Account",
|
label: l10n.createAccount,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import 'package:citycards_customer/common_packages/custom_filled_button.dart';
|
|||||||
import 'package:citycards_customer/common_packages/custom_text.dart';
|
import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class EsimOfferPage extends StatelessWidget {
|
class EsimOfferPage extends StatelessWidget {
|
||||||
const EsimOfferPage({super.key});
|
const EsimOfferPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@@ -83,8 +85,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 350.w,
|
width: 350.w,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text:
|
text: l10n.esimOfferTitle,
|
||||||
"Connect instantly with your free eSIM",
|
|
||||||
size: 22.sp,
|
size: 22.sp,
|
||||||
color: Color(0xFFFFFFFF),
|
color: Color(0xFFFFFFFF),
|
||||||
),
|
),
|
||||||
@@ -93,8 +94,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 350,
|
width: 350,
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text:
|
text: l10n.esimOfferSubtitle,
|
||||||
"Every great journey begins with smooth connectivity.",
|
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -118,7 +118,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "View Plans",
|
text: l10n.viewPlans,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
),
|
),
|
||||||
@@ -145,14 +145,14 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "With your ",
|
text: l10n.withYour,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.sp,
|
fontSize: 26.sp,
|
||||||
fontWeight: FontWeight.w300,
|
fontWeight: FontWeight.w300,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "eSIM",
|
text: l10n.esimKeyword,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 26.sp,
|
fontSize: 26.sp,
|
||||||
@@ -160,7 +160,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ", you can:",
|
text: l10n.youCanKeyword,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.sp,
|
fontSize: 26.sp,
|
||||||
fontWeight: FontWeight.w300,
|
fontWeight: FontWeight.w300,
|
||||||
@@ -172,26 +172,26 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
SizedBox(height: 37.h),
|
SizedBox(height: 37.h),
|
||||||
ServiceCard(
|
ServiceCard(
|
||||||
"assets/icons/esim_location.png",
|
"assets/icons/esim_location.png",
|
||||||
"Navigate the city with ease",
|
l10n.navigateCity,
|
||||||
"Access real-time maps and directions wherever you go",
|
l10n.navigateCityDesc,
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
ServiceCard(
|
ServiceCard(
|
||||||
"assets/icons/esim_phone.png",
|
"assets/icons/esim_phone.png",
|
||||||
"Book rides, access maps, and find attractions in real time",
|
l10n.bookRides,
|
||||||
"Stay connected to all essential travel services",
|
l10n.bookRidesDesc,
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
ServiceCard(
|
ServiceCard(
|
||||||
"assets/icons/esim_camera.png",
|
"assets/icons/esim_camera.png",
|
||||||
"Share photos and memories instantly",
|
l10n.sharePhotos,
|
||||||
"Upload and share your travel moments without delay",
|
l10n.sharePhotosDesc,
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
ServiceCard(
|
ServiceCard(
|
||||||
"assets/icons/esim_people.png",
|
"assets/icons/esim_people.png",
|
||||||
"Stay connected with friends, family, and travel plans",
|
l10n.stayConnectedFeatures,
|
||||||
"Never miss important updates or messages while traveling",
|
l10n.stayConnectedFeaturesDesc,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 75.h),
|
SizedBox(height: 75.h),
|
||||||
@@ -211,11 +211,11 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Simple ",
|
text: l10n.simpleKeyword,
|
||||||
style: TextStyle(fontSize: 24.sp),
|
style: TextStyle(fontSize: 24.sp),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "3-Step Process",
|
text: l10n.threeStepProcess,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -227,28 +227,28 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Get connected in seconds",
|
text: l10n.getConnectedInSeconds,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Color(0xFF4B5563),
|
color: Color(0xFF4B5563),
|
||||||
),
|
),
|
||||||
SizedBox(height: 56.h),
|
SizedBox(height: 56.h),
|
||||||
ProcessCard(
|
ProcessCard(
|
||||||
"Receive QR Code",
|
l10n.receiveQrCode,
|
||||||
"Get your unique eSIM QR code with your CityCard",
|
l10n.receiveQrCodeDesc,
|
||||||
"1",
|
"1",
|
||||||
"assets/icons/process_qr.png",
|
"assets/icons/process_qr.png",
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
ProcessCard(
|
ProcessCard(
|
||||||
"Scan Code",
|
l10n.scanCode,
|
||||||
"Open your phone camera and scan the QR code",
|
l10n.scanCodeDesc,
|
||||||
"2",
|
"2",
|
||||||
"assets/icons/process_phone.png",
|
"assets/icons/process_phone.png",
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
ProcessCard(
|
ProcessCard(
|
||||||
"Connected",
|
l10n.connectedStep,
|
||||||
"You're online instantly - start exploring!",
|
l10n.connectedStepDesc,
|
||||||
"3",
|
"3",
|
||||||
"assets/icons/process_wifi.png",
|
"assets/icons/process_wifi.png",
|
||||||
),
|
),
|
||||||
@@ -277,7 +277,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "It's one more way",
|
text: l10n.itsOneMoreWay,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -285,7 +285,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " CityCards",
|
text: l10n.cityCardsKeywordSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -302,7 +302,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "makes your journey",
|
text: l10n.makesYourJourney,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -310,7 +310,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " smarter",
|
text: l10n.smarterKeywordSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -327,7 +327,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "and more",
|
text: l10n.andMoreSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -335,7 +335,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " effortless",
|
text: l10n.effortlessSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
@@ -350,7 +350,7 @@ class EsimOfferPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
CustomFilledButton(
|
CustomFilledButton(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
label: "Start Your Journey Today",
|
label: l10n.startYourJourneyToday,
|
||||||
height: 60.h,
|
height: 60.h,
|
||||||
width: 300.w,
|
width: 300.w,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:citycards_customer/core/route_constants.dart';
|
import 'package:citycards_customer/core/route_constants.dart';
|
||||||
import '../../common_packages/app_bar.dart';
|
import '../../common_packages/app_bar.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../../localPreference/local_preference.dart';
|
import '../../localPreference/local_preference.dart';
|
||||||
import '../../networkApiServices/api_urls.dart';
|
import '../../networkApiServices/api_urls.dart';
|
||||||
import '../widgets/explore_cities_card.dart';
|
import '../widgets/explore_cities_card.dart';
|
||||||
import '../bloc/FirstTimeUserHome/first_time_user_home_bloc.dart';
|
import '../bloc/FirstTimeUserHome/first_time_user_home_bloc.dart';
|
||||||
import '../bloc/FirstTimeUserHome/first_time_user_home_event.dart';
|
import '../bloc/FirstTimeUserHome/first_time_user_home_event.dart';
|
||||||
import '../bloc/FirstTimeUserHome/first_time_user_home_state.dart';
|
import '../bloc/FirstTimeUserHome/first_time_user_home_state.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class FirstTimeUserHomePage extends StatefulWidget {
|
class FirstTimeUserHomePage extends StatefulWidget {
|
||||||
const FirstTimeUserHomePage({super.key});
|
const FirstTimeUserHomePage({super.key});
|
||||||
@@ -78,7 +81,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
CommonAppBar(isWhiteLogo: true, isProfilePage: false, showDivider: false),
|
CommonAppBar(isWhiteLogo: true, isProfilePage: false, showDivider: false),
|
||||||
SizedBox(height: 120.h),
|
SizedBox(height: 120.h),
|
||||||
Text(
|
Text(
|
||||||
"CityCards.\nSee More,\nSpend Less.",
|
AppLocalizations.of(context)!.homeTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 44.sp,
|
fontSize: 44.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -86,8 +89,8 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
const Text(
|
Text(
|
||||||
"Instant QR access to 40+ attractions,\nexclusive perks, and savings up to 30%",
|
AppLocalizations.of(context)!.homeSubtitle,
|
||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
@@ -110,7 +113,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Get Your CityCards",
|
AppLocalizations.of(context)!.getYourCityCards,
|
||||||
style: TextStyle(color: Colors.white,fontSize: 14.sp),
|
style: TextStyle(color: Colors.white,fontSize: 14.sp),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10.w),
|
SizedBox(width: 10.w),
|
||||||
@@ -124,7 +127,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Explore ",
|
text: AppLocalizations.of(context)!.exploreCitiesTitleExplore,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -132,7 +135,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Cities",
|
text: AppLocalizations.of(context)!.exploreCitiesTitleCities,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@@ -143,8 +146,8 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
const Text(
|
Text(
|
||||||
"Explore your dream destination and experience various attractions.",
|
AppLocalizations.of(context)!.exploreDescription,
|
||||||
style: TextStyle(color: Color(0xff676D75)),
|
style: TextStyle(color: Color(0xff676D75)),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.sp),
|
SizedBox(height: 16.sp),
|
||||||
@@ -168,7 +171,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
height: 270.h,
|
height: 270.h,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Error: ${state.message}',
|
AppLocalizations.of(context)!.errorMessage(state.message),
|
||||||
style: const TextStyle(color: Colors.red),
|
style: const TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -181,8 +184,8 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
if (cities.isEmpty) {
|
if (cities.isEmpty) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 270.h,
|
height: 270.h,
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Text('No cities available'),
|
child: Text(AppLocalizations.of(context)!.noCitiesAvailable),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -215,8 +218,8 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ExploreCitiesCard(
|
child: ExploreCitiesCard(
|
||||||
name: city.cityName ?? 'N/A',
|
name: city.cityName ?? AppLocalizations.of(context)!.notAvailableLabel,
|
||||||
description: city.tagLine ?? 'N/A',
|
description: city.tagLine ?? AppLocalizations.of(context)!.notAvailableLabel,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
individualPrice: '\$${city.indivisualTicketAmt ?? 0}+',
|
individualPrice: '\$${city.indivisualTicketAmt ?? 0}+',
|
||||||
cityCardPrice: '\$${city.cityCardTicketAmt ?? 0}',
|
cityCardPrice: '\$${city.cityCardTicketAmt ?? 0}',
|
||||||
@@ -255,7 +258,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Upcoming ",
|
text: AppLocalizations.of(context)!.upcomingCitiesTitleUpcoming,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -263,7 +266,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Cities",
|
text: AppLocalizations.of(context)!.upcomingCitiesTitleCities,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@@ -275,7 +278,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
Text(
|
Text(
|
||||||
"Explore your dream destination and experience various attractions.",
|
AppLocalizations.of(context)!.exploreDescription,
|
||||||
style: TextStyle(color: Colors.grey[600]),
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
@@ -299,7 +302,7 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
height: 80.h,
|
height: 80.h,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Error: ${state.message}',
|
AppLocalizations.of(context)!.errorMessage(state.message),
|
||||||
style: const TextStyle(color: Colors.red),
|
style: const TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -327,8 +330,37 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 28.r,
|
radius: 28.r,
|
||||||
backgroundImage: NetworkImage(imageUrl),
|
|
||||||
backgroundColor: Colors.grey.shade200,
|
backgroundColor: Colors.grey.shade200,
|
||||||
|
child: ClipOval(
|
||||||
|
child: imageUrl.isNotEmpty
|
||||||
|
? CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
width: 56.r,
|
||||||
|
height: 56.r,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
|
||||||
|
// 🔄 Skeleton Loader
|
||||||
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
|
width: 56.r,
|
||||||
|
height: 56.r,
|
||||||
|
borderRadius: 100, // fully round
|
||||||
|
),
|
||||||
|
|
||||||
|
// ❌ Error fallback
|
||||||
|
errorWidget: (_, __, ___) => Image.asset(
|
||||||
|
'assets/images/city_sydney.png',
|
||||||
|
width: 56.r,
|
||||||
|
height: 56.r,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Image.asset(
|
||||||
|
'assets/images/city_sydney.png',
|
||||||
|
width: 56.r,
|
||||||
|
height: 56.r,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 6.h),
|
SizedBox(height: 6.h),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '../../common_bloc/bottom_navigation_bloc.dart';
|
|||||||
import '../../common_packages/app_bar.dart';
|
import '../../common_packages/app_bar.dart';
|
||||||
import '../../common_packages/custom_filled_button.dart';
|
import '../../common_packages/custom_filled_button.dart';
|
||||||
import '../../common_packages/custom_text.dart';
|
import '../../common_packages/custom_text.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
import '../../localPreference/local_preference.dart';
|
import '../../localPreference/local_preference.dart';
|
||||||
import '../../networkApiServices/api_urls.dart';
|
import '../../networkApiServices/api_urls.dart';
|
||||||
@@ -25,6 +26,7 @@ import '../widgets/gradient_container_bg.dart';
|
|||||||
import '../widgets/itineary_animation.dart';
|
import '../widgets/itineary_animation.dart';
|
||||||
import '../widgets/pass_card_list.dart';
|
import '../widgets/pass_card_list.dart';
|
||||||
import '../widgets/search_city_bottomsheet.dart';
|
import '../widgets/search_city_bottomsheet.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class RegisteredUserHomePage extends StatefulWidget {
|
class RegisteredUserHomePage extends StatefulWidget {
|
||||||
const RegisteredUserHomePage({super.key});
|
const RegisteredUserHomePage({super.key});
|
||||||
@@ -118,7 +120,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
SizedBox(height: 32.h),
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Oops! Something went wrong",
|
text: AppLocalizations.of(context)!.oopsSomethingWentWrong,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -140,7 +142,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
onTap:() {
|
onTap:() {
|
||||||
context.read<HomeBloc>().add(FetchHomeData());
|
context.read<HomeBloc>().add(FetchHomeData());
|
||||||
},
|
},
|
||||||
label: "Try Again",
|
label: AppLocalizations.of(context)!.tryAgain,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -166,33 +168,42 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Stack(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Background image - use city banner if available
|
// ── White app bar above the image ───────────────────────
|
||||||
_buildBannerImage(bannerImageUrl),
|
Padding(
|
||||||
Column(
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: CommonAppBar(
|
||||||
|
isWhiteLogo: false,
|
||||||
|
isProfilePage: false,
|
||||||
|
showDivider: false,
|
||||||
|
// imageUrl: cityIconUrl,
|
||||||
|
isSelectCity: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
|
||||||
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
// Background image - use city banner if available
|
||||||
padding: EdgeInsets.all(10.r),
|
_buildBannerImage(bannerImageUrl),
|
||||||
child: Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CommonAppBar(
|
Padding(
|
||||||
isWhiteLogo: false,
|
padding: EdgeInsets.all(10.r),
|
||||||
isProfilePage: false,
|
child: Column(
|
||||||
showDivider: false,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
// imageUrl: cityIconUrl,
|
children: [
|
||||||
isSelectCity: true,
|
SizedBox(height: 100.h),
|
||||||
),
|
|
||||||
SizedBox(height: 130.h),
|
|
||||||
|
|
||||||
// City name from API
|
// City name from API
|
||||||
Text(
|
Text(
|
||||||
city?.cityName ?? "City Name",
|
city?.cityName ?? AppLocalizations.of(context)!.defaultCityName,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 44.sp,
|
fontSize: 44.sp,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -200,7 +211,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
|
|
||||||
// City description from API
|
// City description from API
|
||||||
Text(
|
Text(
|
||||||
city?.description ?? "City description",
|
city?.description ?? AppLocalizations.of(context)!.defaultCityDescription,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withOpacity(0.9),
|
color: Colors.white.withOpacity(0.9),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
@@ -223,7 +234,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
child: _buildTag(highlight.title ?? ""),
|
child: _buildTag(highlight.title ?? ""),
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
return tags.isEmpty ? [_buildTag("No Highlights Available")] : tags;
|
return tags.isEmpty ? [_buildTag(AppLocalizations.of(context)!.noHighlightsAvailable)] : tags;
|
||||||
}(),
|
}(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -237,7 +248,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Popular ",
|
text: AppLocalizations.of(context)!.popular,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -245,7 +256,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Attractions",
|
text: AppLocalizations.of(context)!.attractions,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@@ -263,7 +274,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"View all",
|
AppLocalizations.of(context)!.viewAll,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -280,99 +291,101 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
InwardCurvedContainer(
|
InwardCurvedContainer(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 40.h),
|
SizedBox(height: 40.h),
|
||||||
const ItineraryVideo(),
|
const ItineraryVideo(),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
// Button section
|
// Button section
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 240.w,
|
width: 290.w,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<NavigationBloc>().add(
|
context.read<NavigationBloc>().add(
|
||||||
NavigationTabChanged(1),
|
NavigationTabChanged(1),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xffF95F62),
|
backgroundColor: const Color(0xffF95F62),
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 14.h,
|
vertical: 14.h,
|
||||||
),
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
30.r,
|
30.r,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Create My Magic Itinerary",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 4.w),
|
|
||||||
const Icon(
|
|
||||||
Icons.arrow_forward,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.createMyMagicItinerary,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_forward,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
HotelOffersSection(),
|
||||||
|
ESimOfferSection(),
|
||||||
|
SizedBox(height: 10.h,),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
RouteConstants.searchOffer,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _buildFeatureCard(
|
||||||
|
image:
|
||||||
|
"assets/images/claim_offers_bg.jpg",
|
||||||
|
title: AppLocalizations.of(context)!.claimOffersWithCityCards,
|
||||||
|
subtitle: AppLocalizations.of(context)!.offerSubtitleDummy,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
ESimOfferSection(),
|
SizedBox(height: 24.h),
|
||||||
HotelOffersSection(),
|
ChooseYourPassSection(
|
||||||
Padding(
|
cards: state.homeModel.city?.cards ?? [],
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
RouteConstants.searchOffer,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: _buildFeatureCard(
|
|
||||||
image:
|
|
||||||
"assets/images/claim_offers_bg.jpg",
|
|
||||||
title: "Claim offers with your City Cards",
|
|
||||||
subtitle: "Lorem ipsum dolor sit amet...",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
SizedBox(height: 24.h),
|
|
||||||
ChooseYourPassSection(
|
|
||||||
cards: state.homeModel.city?.cards ?? [],
|
|
||||||
),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
GetYourPassCard(),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 20.h),
|
||||||
],
|
GetYourPassCard(),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -480,32 +493,56 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|||||||
|
|
||||||
Widget _buildBannerImage(String? imageUrl) {
|
Widget _buildBannerImage(String? imageUrl) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 350.h,
|
height: 290.h,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: (imageUrl == null || imageUrl.isEmpty)
|
child: Stack(
|
||||||
? Image.asset(
|
children: [
|
||||||
"assets/images/chicago.png",
|
// 🖼️ Image
|
||||||
fit: BoxFit.cover,
|
Positioned.fill(
|
||||||
)
|
child: (imageUrl == null || imageUrl.isEmpty)
|
||||||
: CachedNetworkImage(
|
? Image.asset(
|
||||||
imageUrl: imageUrl,
|
"assets/images/chicago.png",
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
// 🔄 Loader (same as your loadingBuilder)
|
: CachedNetworkImage(
|
||||||
placeholder: (context, url) => Container(
|
imageUrl: imageUrl,
|
||||||
color: Colors.grey[300],
|
fit: BoxFit.cover,
|
||||||
child: const Center(
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
child: CircularProgressIndicator(
|
width: double.infinity,
|
||||||
color: Color(0xffF95F62),
|
height: 290.h,
|
||||||
|
borderRadius: 0,
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => Image.asset(
|
||||||
|
"assets/images/chicago.png",
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
// ❌ Error fallback (same as errorBuilder)
|
// 🌑 Dark → Fade → Transparent (ends at 65%)
|
||||||
errorWidget: (context, url, error) => Image.asset(
|
Positioned.fill(
|
||||||
"assets/images/chicago.png",
|
child: Container(
|
||||||
fit: BoxFit.cover,
|
decoration: BoxDecoration(
|
||||||
),
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.black.withOpacity(0.9), // 0% (bottom)
|
||||||
|
Colors.black.withOpacity(0.5), // 50% still dark
|
||||||
|
Colors.black.withOpacity(0.3), // start fade
|
||||||
|
Colors.transparent, // fully transparent
|
||||||
|
],
|
||||||
|
stops: [
|
||||||
|
0.0, // bottom
|
||||||
|
0.5, // 👈 still dark till 50%
|
||||||
|
0.6, // fade in progress
|
||||||
|
0.65, // 👈 fade ends here
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
import '../../attraction_details/views/attraction_details_view.dart';
|
import '../../attraction_details/views/attraction_details_view.dart';
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
import '../model/home_model.dart';
|
import '../model/home_model.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AttractionsListView extends StatefulWidget {
|
class AttractionsListView extends StatefulWidget {
|
||||||
final List<Attraction> attractions;
|
final List<Attraction> attractions;
|
||||||
@@ -62,7 +64,7 @@ class _AttractionsListViewState extends State<AttractionsListView> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(20.w),
|
padding: EdgeInsets.all(20.w),
|
||||||
child: Text(
|
child: Text(
|
||||||
'No attractions available',
|
AppLocalizations.of(context)!.noAttractionsAvailable,
|
||||||
style: TextStyle(fontSize: 16.sp, color: Colors.grey),
|
style: TextStyle(fontSize: 16.sp, color: Colors.grey),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -122,15 +124,15 @@ class _AttractionsListViewState extends State<AttractionsListView> {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 400,
|
memCacheWidth: 400,
|
||||||
memCacheHeight: 600,
|
memCacheHeight: 600,
|
||||||
placeholder: (context, url) => const Center(
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
height: 232.h,
|
||||||
|
width: 161.w,
|
||||||
),
|
),
|
||||||
errorWidget: (context, url, error) => _buildPlaceholder(),
|
errorWidget: (context, url, error) => _buildPlaceholder(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_buildPlaceholder(),
|
_buildPlaceholder(),
|
||||||
|
|
||||||
// Title + Description Overlay
|
// Title + Description Overlay
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ESimOfferSection extends StatelessWidget {
|
class ESimOfferSection extends StatelessWidget {
|
||||||
const ESimOfferSection({super.key});
|
const ESimOfferSection({super.key});
|
||||||
@@ -30,7 +31,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Explore ",
|
text: AppLocalizations.of(context)!.explore,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -38,7 +39,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "More Cities.",
|
text: AppLocalizations.of(context)!.moreCities,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: Colors.white.withOpacity(0.9),
|
color: Colors.white.withOpacity(0.9),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -60,7 +61,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Stay ",
|
text: AppLocalizations.of(context)!.stay,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
@@ -68,7 +69,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Connected",
|
text: AppLocalizations.of(context)!.connected,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
@@ -82,7 +83,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
// Everywhere (centered)
|
// Everywhere (centered)
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Everywhere",
|
AppLocalizations.of(context)!.everywhere,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
@@ -111,7 +112,7 @@ class ESimOfferSection extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Claim e-Sim offers with\nCityCard",
|
AppLocalizations.of(context)!.claimEsimOffers,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
import '../../common_packages/shimmer_animation.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ExploreCitiesCard extends StatelessWidget {
|
class ExploreCitiesCard extends StatelessWidget {
|
||||||
final String name;
|
final String name;
|
||||||
final String description;
|
final String description;
|
||||||
@@ -42,15 +45,15 @@ class ExploreCitiesCard extends StatelessWidget {
|
|||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) => Container(
|
|
||||||
color: Colors.grey.shade200,
|
// 🔄 Skeleton Loader
|
||||||
child: const Center(
|
placeholder: (context, url) => SkeletonWidget(
|
||||||
child: CircularProgressIndicator(
|
width: double.infinity,
|
||||||
color: Color(0xffF95F62),
|
height: double.infinity,
|
||||||
strokeWidth: 2,
|
borderRadius: 0,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// ❌ Error fallback
|
||||||
errorWidget: (context, url, error) => Image.asset(
|
errorWidget: (context, url, error) => Image.asset(
|
||||||
'assets/images/city_sydney.png',
|
'assets/images/city_sydney.png',
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -134,7 +137,7 @@ class ExploreCitiesCard extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Individual tickets:",
|
AppLocalizations.of(context)!.individualTickets,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xffFDCDCE),
|
color: const Color(0xffFDCDCE),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
@@ -155,7 +158,7 @@ class ExploreCitiesCard extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"City Card:",
|
AppLocalizations.of(context)!.cityCardLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xffFDCDCE),
|
color: const Color(0xffFDCDCE),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class GetYourPassCard extends StatelessWidget {
|
class GetYourPassCard extends StatelessWidget {
|
||||||
const GetYourPassCard({super.key});
|
const GetYourPassCard({super.key});
|
||||||
@@ -25,7 +26,7 @@ class GetYourPassCard extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Get Your Card",
|
AppLocalizations.of(context)!.getYourCard,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -64,7 +65,7 @@ class GetYourPassCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
Text(
|
Text(
|
||||||
"Attractions",
|
AppLocalizations.of(context)!.attractions,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@@ -77,7 +78,7 @@ class GetYourPassCard extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"From",
|
AppLocalizations.of(context)!.fromLabel,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 11.sp,
|
fontSize: 11.sp,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@@ -95,7 +96,7 @@ class GetYourPassCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " /Adult",
|
text: AppLocalizations.of(context)!.perAdult,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12 .sp,
|
fontSize: 12 .sp,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class HotelOffersSection extends StatelessWidget {
|
class HotelOffersSection extends StatelessWidget {
|
||||||
const HotelOffersSection({super.key});
|
const HotelOffersSection({super.key});
|
||||||
@@ -11,171 +12,182 @@ class HotelOffersSection extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
gradient: LinearGradient(
|
),
|
||||||
begin: Alignment.topLeft,
|
child: ClipRRect(
|
||||||
end: Alignment.bottomRight,
|
borderRadius: BorderRadius.circular(20),
|
||||||
colors: [
|
child: Stack(
|
||||||
Color(0xFFFFCA8E).withOpacity(0.24), // deep navy blue
|
children: [
|
||||||
Color(0xFFDB00FF).withOpacity(0.24), // rich red tone
|
// ===== BACKGROUND IMAGE =====
|
||||||
Color(0xFFF95F62).withOpacity(0.24),
|
Positioned.fill(
|
||||||
|
child: Image.asset(
|
||||||
|
"assets/images/hotel_offers_bg.jpg",
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// ===== BLACK OVERLAY =====
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.42),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// ===== CONTENT =====
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// ===== HEADER LINE =====
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.marriott,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.moments,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ", ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.citycard,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.prices,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
// ===== MAIN HEADING =====
|
||||||
|
Text.rich(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.premium,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
fontSize: 32.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.stays,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 32.sp,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
AppLocalizations.of(context)!.citycardPrices,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 32.sp,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// ===== SUB CARD =====
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.claimHotelDiscountOffers,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context, rootNavigator: true)
|
||||||
|
.pushNamed(RouteConstants.hotelOffer);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Image.asset(
|
||||||
|
"assets/icons/arrow_angle_up.png",
|
||||||
|
color: Color(0xFFF95F62),
|
||||||
|
scale: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
"Lorem ipsum dolor sit amet...",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white.withOpacity(0.85),
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
// ===== HEADER LINE =====
|
|
||||||
RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: "MARRIOTT ",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: const Color(0xFFF95F62),
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: "MOMENTS",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: ", ",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: const Color(0xFFF95F62),
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: "CITYCARD ",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: const Color(0xFFF95F62),
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: "PRICES",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
|
|
||||||
// ===== MAIN HEADING =====
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
// Stay Connected
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: "Premium ",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: const Color(0xFFF95F62),
|
|
||||||
fontSize: 32.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: "Stays",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 32.sp,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
"CityCard Prices",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 32.sp,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// ===== SUB CARD =====
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF95F62),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Claim Hotel Discount offers\nwith CityCard",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: (){
|
|
||||||
Navigator.of(context, rootNavigator: true)
|
|
||||||
.pushNamed(RouteConstants.hotelOffer);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Image.asset(
|
|
||||||
"assets/icons/arrow_angle_up.png",
|
|
||||||
color: Color(0xFFF95F62),
|
|
||||||
scale: 4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
"Lorem ipsum dolor sit amet...",
|
|
||||||
style: GoogleFonts.poppins(
|
|
||||||
color: Colors.white.withOpacity(0.85),
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class DreamJourneySection extends StatefulWidget {
|
class DreamJourneySection extends StatefulWidget {
|
||||||
const DreamJourneySection({super.key});
|
const DreamJourneySection({super.key});
|
||||||
@@ -96,12 +97,12 @@ class _DreamJourneySectionState extends State<DreamJourneySection> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Plan Your ",
|
text: AppLocalizations.of(context)!.planYour,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 20, fontWeight: FontWeight.w600),
|
fontSize: 20, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Dream Journey",
|
text: AppLocalizations.of(context)!.dreamJourney,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -109,7 +110,7 @@ class _DreamJourneySectionState extends State<DreamJourneySection> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "\nin Just 3 Seconds",
|
text: AppLocalizations.of(context)!.inJust3Seconds,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 20, fontWeight: FontWeight.w600),
|
fontSize: 20, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import '../../common_packages/common_app_texts.dart';
|
import '../../common_packages/common_app_texts.dart';
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
import '../model/home_model.dart';
|
import '../model/home_model.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ChooseYourPassSection extends StatefulWidget {
|
class ChooseYourPassSection extends StatefulWidget {
|
||||||
final List<CardModel> cards; // 👈 from API
|
final List<CardModel> cards; // 👈 from API
|
||||||
@@ -22,7 +23,7 @@ class ChooseYourPassSection extends StatefulWidget {
|
|||||||
|
|
||||||
class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
||||||
final PageController _pageController = PageController(
|
final PageController _pageController = PageController(
|
||||||
viewportFraction: 0.92,
|
viewportFraction: 0.80,
|
||||||
);
|
);
|
||||||
|
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
@@ -52,7 +53,7 @@ class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Choose Your Card",
|
AppLocalizations.of(context)!.chooseYourCard,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -60,8 +61,7 @@ class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
Text(
|
Text(
|
||||||
"Dive into an extensive selection of thrilling destinations, "
|
AppLocalizations.of(context)!.diveIntoExtensiveSelection,
|
||||||
"thoughtfully categorized to help you find the perfect getaway.",
|
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 13.sp,
|
fontSize: 13.sp,
|
||||||
color: Colors.grey[700],
|
color: Colors.grey[700],
|
||||||
@@ -162,7 +162,7 @@ class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "From ",
|
text: AppLocalizations.of(context)!.from,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
fontSize: 16.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -206,7 +206,7 @@ class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"Get a Card",
|
AppLocalizations.of(context)!.getACard,
|
||||||
style: GoogleFonts.poppins(
|
style: GoogleFonts.poppins(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import '../../my_pass/blocs/myPasses/my_passes_event.dart';
|
|||||||
import '../../postcard/blocs/myPostCards/my_postcard_bloc.dart';
|
import '../../postcard/blocs/myPostCards/my_postcard_bloc.dart';
|
||||||
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
||||||
import '../../profile/bloc/profile/profile_bloc.dart';
|
import '../../profile/bloc/profile/profile_bloc.dart';
|
||||||
|
import '../../profile/bloc/profile/profile_bloc.dart';
|
||||||
import '../../profile/bloc/profile/profile_event.dart';
|
import '../../profile/bloc/profile/profile_event.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class CitySelectionBottomSheet extends StatelessWidget {
|
class CitySelectionBottomSheet extends StatelessWidget {
|
||||||
const CitySelectionBottomSheet({super.key});
|
const CitySelectionBottomSheet({super.key});
|
||||||
@@ -78,14 +80,14 @@ class _CitySelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.arrow_back, size: 18),
|
const Icon(Icons.arrow_back, size: 18),
|
||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(text: "Back", size: 12.sp),
|
CustomText(text: AppLocalizations.of(context)!.back, size: 12.sp),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Select a City",
|
AppLocalizations.of(context)!.selectACity,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
fontSize: 16.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -110,7 +112,7 @@ class _CitySelectionView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: "Search Cities",
|
hintText: AppLocalizations.of(context)!.searchCities,
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: const Color(0xFF2B2B2B),
|
color: const Color(0xFF2B2B2B),
|
||||||
@@ -165,7 +167,7 @@ class _CitySelectionView extends StatelessWidget {
|
|||||||
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Text(
|
Text(
|
||||||
'Error loading cities',
|
AppLocalizations.of(context)!.errorLoadingCities,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
fontSize: 16.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -212,7 +214,7 @@ class _CitySelectionView extends StatelessWidget {
|
|||||||
color: Colors.grey[400]),
|
color: Colors.grey[400]),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Text(
|
Text(
|
||||||
"No cities found",
|
AppLocalizations.of(context)!.noCitiesFound,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
fontSize: 16.sp,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import 'package:citycards_customer/common_packages/app_bar.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
|
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class HotelOfferView extends StatelessWidget {
|
class HotelOfferView extends StatelessWidget {
|
||||||
const HotelOfferView({super.key});
|
const HotelOfferView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@@ -18,7 +20,11 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
child: CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: false),
|
child: CommonAppBar(
|
||||||
|
isWhiteLogo: false,
|
||||||
|
isProfilePage: false,
|
||||||
|
showDivider: false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
// Banner Section
|
// Banner Section
|
||||||
Stack(
|
Stack(
|
||||||
@@ -53,7 +59,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Enjoy 20% Off Iconic\nMarriott Hotels -\nExclusively with CityCards",
|
l10n.enjoy20OffMarriott,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 30.sp,
|
fontSize: 30.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -63,7 +69,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 21.h),
|
SizedBox(height: 21.h),
|
||||||
Text(
|
Text(
|
||||||
"Make every stay as unforgettable as the city you're exploring.",
|
l10n.makeEveryStayUnforgettable,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -75,7 +81,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigator.of(context).pushNamed(RouteConstants.uploadPhotoPage);
|
// Navigator.of(context).pushNamed(RouteConstants.uploadPhotoPage);
|
||||||
},
|
},
|
||||||
label: "Book Now",
|
label: l10n.bookNow,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -92,7 +98,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Your CityCards unlocks more than just attractions — it also opens doors to exceptional stays.",
|
l10n.yourCityCardsUnlocksMore,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
color: Color(0xFF1F2937),
|
color: Color(0xFF1F2937),
|
||||||
@@ -111,22 +117,17 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
height: 1.6,
|
height: 1.6,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(
|
TextSpan(text: l10n.thanksToOurExclusivePartnership),
|
||||||
text: "Thanks to our exclusive partnership with ",
|
|
||||||
),
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Marriott Hotels",
|
text: l10n.marriottHotelsKeyword,
|
||||||
style: TextStyle(color: const Color(0xFFF95F62)),
|
style: TextStyle(color: const Color(0xFFF95F62)),
|
||||||
),
|
),
|
||||||
const TextSpan(text: ", CityCards holders enjoy "),
|
TextSpan(text: l10n.cityCardsHoldersEnjoy),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "20% off best available rates",
|
text: l10n.twentyPercentOffRates,
|
||||||
style: TextStyle(color: const Color(0xFFF95F62)),
|
style: TextStyle(color: const Color(0xFFF95F62)),
|
||||||
),
|
),
|
||||||
const TextSpan(
|
TextSpan(text: l10n.acrossCuratedProperties),
|
||||||
text:
|
|
||||||
" across a curated selection of properties in the city.",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -150,9 +151,9 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
fontSize: 26.25.sp,
|
fontSize: 26.25.sp,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(text: "Choose from a "),
|
TextSpan(text: l10n.chooseFromAKeyword),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Wide \nVariety",
|
text: l10n.wideVarietyBreak,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -171,20 +172,17 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
height: 1.5,
|
height: 1.5,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(
|
TextSpan(text: l10n.chooseFromAWideVarietyDesc),
|
||||||
text:
|
|
||||||
"Choose from a wide variety of Marriott hotels — from elegant urban hideaways and premium city-centre locations to luxurious five-star experiences — all designed to make your trip ",
|
|
||||||
),
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "effortless, comfortable",
|
text: l10n.effortlessComfortable,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(text: " and "),
|
TextSpan(text: l10n.andMiddle),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "memorable",
|
text: l10n.memorableKeyword,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -202,7 +200,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Simply use your CityCards",
|
text: l10n.simplyUseYourCityCards,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.25.sp,
|
fontSize: 26.25.sp,
|
||||||
color: Color(0xFF1F2937),
|
color: Color(0xFF1F2937),
|
||||||
@@ -210,7 +208,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " booking link to:",
|
text: l10n.bookingLinkTo,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.25.sp,
|
fontSize: 26.25.sp,
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
@@ -226,20 +224,20 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
|
|
||||||
_featureCard(
|
_featureCard(
|
||||||
icon: "assets/icons/discount_percent.png",
|
icon: "assets/icons/discount_percent.png",
|
||||||
title: "Access 20% off best available rates",
|
title: l10n.access20OffRates,
|
||||||
subtitle: "Save on your stay at premium Marriott properties",
|
subtitle: l10n.saveOnYourStay,
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
_featureCard(
|
_featureCard(
|
||||||
icon: "assets/icons/discount_clock.png",
|
icon: "assets/icons/discount_clock.png",
|
||||||
title: "Enjoy priority check-in and late checkout",
|
title: l10n.enjoyPriorityCheckIn,
|
||||||
subtitle: "Subject to availability for your convenience",
|
subtitle: l10n.subjectToAvailability,
|
||||||
),
|
),
|
||||||
SizedBox(height: 28.h),
|
SizedBox(height: 28.h),
|
||||||
_featureCard(
|
_featureCard(
|
||||||
icon: "assets/icons/discount_crown.png",
|
icon: "assets/icons/discount_crown.png",
|
||||||
title: "Receive exclusive seasonal offers",
|
title: l10n.receiveExclusiveSeasonalOffers,
|
||||||
subtitle: "Designed specially for CityCards travellers",
|
subtitle: l10n.designedSpeciallyForCityCards,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 56.h),
|
SizedBox(height: 56.h),
|
||||||
@@ -248,18 +246,18 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 30.w),
|
padding: EdgeInsets.symmetric(horizontal: 30.w),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
textAlign : TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "It's just one more way",
|
text: l10n.itsJustOneMoreWay,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
color: Color(0xFF1F2937),
|
color: Color(0xFF1F2937),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " CityCards",
|
text: l10n.cityCardsKeywordSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -267,7 +265,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " makes exploring",
|
text: l10n.makesExploring,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -275,7 +273,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " smarter",
|
text: l10n.smarterKeywordSpace,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -291,7 +289,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " simpler",
|
text: l10n.simplerKeyword,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -307,7 +305,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " more rewarding",
|
text: l10n.moreRewarding,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 21.sp,
|
fontSize: 21.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -331,7 +329,7 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: CustomFilledButton(
|
child: CustomFilledButton(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
label: "Get your CityCards today",
|
label: l10n.getYourCityCardsToday,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
height: 59.h,
|
height: 59.h,
|
||||||
width: 291.w,
|
width: 291.w,
|
||||||
@@ -363,7 +361,6 @@ class HotelOfferView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Image.asset(icon, scale: 4),
|
Image.asset(icon, scale: 4),
|
||||||
SizedBox(height: 21.h),
|
SizedBox(height: 21.h),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:citycards_customer/common_packages/custom_text.dart';
|
|||||||
import 'package:citycards_customer/core/route_constants.dart';
|
import 'package:citycards_customer/core/route_constants.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ItineraryCreationStartPage extends StatelessWidget {
|
class ItineraryCreationStartPage extends StatelessWidget {
|
||||||
const ItineraryCreationStartPage({super.key});
|
const ItineraryCreationStartPage({super.key});
|
||||||
@@ -33,7 +34,7 @@ class ItineraryCreationStartPage extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Create your",
|
text: AppLocalizations.of(context)!.createYourLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF101828),
|
color: const Color(0xFF101828),
|
||||||
fontSize: 22.sp,
|
fontSize: 22.sp,
|
||||||
@@ -41,7 +42,7 @@ class ItineraryCreationStartPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: " magic itinerary",
|
text: AppLocalizations.of(context)!.magicItinerarySubtitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFFF95F62),
|
color: const Color(0xFFF95F62),
|
||||||
fontSize: 22.sp,
|
fontSize: 22.sp,
|
||||||
@@ -64,7 +65,7 @@ class ItineraryCreationStartPage extends StatelessWidget {
|
|||||||
|
|
||||||
/// Description
|
/// Description
|
||||||
Text(
|
Text(
|
||||||
"Hey there! Just answer a couple of fun questions, and we’ll whip up a travel experience that’s totally tailored to you! ✈️✨",
|
AppLocalizations.of(context)!.itineraryCreationStartDescription,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: const Color(0xFF4A5565),
|
color: const Color(0xFF4A5565),
|
||||||
@@ -82,14 +83,14 @@ class ItineraryCreationStartPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
label: "Let’s explore together!",
|
label: AppLocalizations.of(context)!.letsExploreTogether,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 10.h),
|
SizedBox(height: 10.h),
|
||||||
|
|
||||||
/// Footer Text
|
/// Footer Text
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Takes only 2 minutes ⏱️",
|
text: AppLocalizations.of(context)!.takesOnlyTwoMinutes,
|
||||||
color: const Color(0xFF6A7282),
|
color: const Color(0xFF6A7282),
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class DateSelectionView extends StatelessWidget {
|
class DateSelectionView extends StatelessWidget {
|
||||||
const DateSelectionView({super.key});
|
const DateSelectionView({super.key});
|
||||||
@@ -17,7 +18,7 @@ class DateSelectionView extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Hey there! When are you planning to visit?",
|
AppLocalizations.of(context)!.whenAreYouPlanningToVisitLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF101828),
|
color: Color(0xFF101828),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -51,7 +52,7 @@ class DateSelectionView extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return CustomText(
|
return CustomText(
|
||||||
// Show the human-readable display date
|
// Show the human-readable display date
|
||||||
text: state.selectedDisplayDate ?? "Select a date",
|
text: state.selectedDisplayDate ?? AppLocalizations.of(context)!.selectADateLabel,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Color(0xFF101828),
|
color: Color(0xFF101828),
|
||||||
);
|
);
|
||||||
@@ -79,7 +80,7 @@ class DateSelectionView extends StatelessWidget {
|
|||||||
ItineraryStepNavigationNextEvent(),
|
ItineraryStepNavigationNextEvent(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: "Continue",
|
label: AppLocalizations.of(context)!.continueTitle,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,20 +4,14 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selec
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class DietarySelectionView extends StatelessWidget {
|
class DietarySelectionView extends StatelessWidget {
|
||||||
const DietarySelectionView({super.key});
|
const DietarySelectionView({super.key});
|
||||||
|
|
||||||
static const Color _accentColor = Color(0xFFF95F62);
|
static const Color _accentColor = Color(0xFFF95F62);
|
||||||
|
|
||||||
final List<Map<String, String>> options = const [
|
// Options generated dynamically
|
||||||
{"icon": "assets/icons/no_restrictions_food.png", "name": "No Restrictions", "value": "no-restriction"},
|
|
||||||
{"icon": "assets/icons/veg.png", "name": "Vegetarian", "value": "veg"},
|
|
||||||
{"icon": "assets/icons/vegan.png", "name": "Vegan", "value": "vegan"},
|
|
||||||
{"icon": "assets/icons/pesc.png", "name": "Pescatarian", "value": "pescatarian"},
|
|
||||||
{"icon": "assets/icons/halal.png", "name": "Halal", "value": "halal"},
|
|
||||||
{"icon": "assets/icons/kosher.png", "name": "Kosher", "value": "kosher"},
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -27,7 +21,7 @@ class DietarySelectionView extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Do you follow any dietary preference?",
|
AppLocalizations.of(context)!.doYouFollowDietaryPreferenceLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF101828),
|
color: const Color(0xFF101828),
|
||||||
fontSize: 20.sp,
|
fontSize: 20.sp,
|
||||||
@@ -46,8 +40,16 @@ class DietarySelectionView extends StatelessWidget {
|
|||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
childAspectRatio: 1.4,
|
childAspectRatio: 1.4,
|
||||||
),
|
),
|
||||||
itemCount: options.length,
|
itemCount: 6,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
final options = [
|
||||||
|
{"icon": "assets/icons/no_restrictions_food.png", "name": AppLocalizations.of(context)!.noRestrictionsLabel, "value": "no-restriction"},
|
||||||
|
{"icon": "assets/icons/veg.png", "name": AppLocalizations.of(context)!.vegetarianLabel, "value": "veg"},
|
||||||
|
{"icon": "assets/icons/vegan.png", "name": AppLocalizations.of(context)!.veganLabel, "value": "vegan"},
|
||||||
|
{"icon": "assets/icons/pesc.png", "name": AppLocalizations.of(context)!.pescatarianLabel, "value": "pescatarian"},
|
||||||
|
{"icon": "assets/icons/halal.png", "name": AppLocalizations.of(context)!.halalLabel, "value": "halal"},
|
||||||
|
{"icon": "assets/icons/kosher.png", "name": AppLocalizations.of(context)!.kosherLabel, "value": "kosher"},
|
||||||
|
];
|
||||||
final item = options[index];
|
final item = options[index];
|
||||||
final isSelected = state.selectedDietary == item['value'];
|
final isSelected = state.selectedDietary == item['value'];
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,14 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selec
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class EnergySelectionView extends StatelessWidget {
|
class EnergySelectionView extends StatelessWidget {
|
||||||
const EnergySelectionView({super.key});
|
const EnergySelectionView({super.key});
|
||||||
|
|
||||||
static const Color _accentColor = Color(0xFFF95F62);
|
static const Color _accentColor = Color(0xFFF95F62);
|
||||||
|
|
||||||
final List<Map<String, String>> options = const [
|
// Options resolved dynamically
|
||||||
{"img": "assets/icons/relaxed.png", "name": "Relaxed & Chill", "value": "relaxed"},
|
|
||||||
{"img": "assets/icons/balanced.png", "name": "Balanced Mix", "value": "balanced"},
|
|
||||||
{"img": "assets/icons/active.png", "name": "Active & Energetic", "value": "active"},
|
|
||||||
{"img": "assets/icons/adventure.png", "name": "Full Adventure!", "value": "adventure"},
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -26,7 +22,7 @@ class EnergySelectionView extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"What kind of energy are you after on this trip?",
|
AppLocalizations.of(context)!.whatKindOfEnergyLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF101828),
|
color: const Color(0xFF101828),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -41,7 +37,13 @@ class EnergySelectionView extends StatelessWidget {
|
|||||||
crossAxisSpacing: 12.w,
|
crossAxisSpacing: 12.w,
|
||||||
mainAxisSpacing: 12.h,
|
mainAxisSpacing: 12.h,
|
||||||
childAspectRatio: 1.3,
|
childAspectRatio: 1.3,
|
||||||
children: List.generate(options.length, (index) {
|
children: List.generate(4, (index) {
|
||||||
|
final options = [
|
||||||
|
{"img": "assets/icons/relaxed.png", "name": AppLocalizations.of(context)!.relaxedAndChillLabel, "value": "relaxed"},
|
||||||
|
{"img": "assets/icons/balanced.png", "name": AppLocalizations.of(context)!.balancedMixLabel, "value": "balanced"},
|
||||||
|
{"img": "assets/icons/active.png", "name": AppLocalizations.of(context)!.activeAndEnergeticLabel, "value": "active"},
|
||||||
|
{"img": "assets/icons/adventure.png", "name": AppLocalizations.of(context)!.fullAdventureLabel, "value": "adventure"},
|
||||||
|
];
|
||||||
final item = options[index];
|
final item = options[index];
|
||||||
final isSelected = state.selectedEnergy == item['value'];
|
final isSelected = state.selectedEnergy == item['value'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,260 +1,435 @@
|
|||||||
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_detail_bloc.dart';
|
import 'dart:async';
|
||||||
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart';
|
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_detail_bloc.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:citycards_customer/common_packages/custom_text.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
|
import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import '../../../core/route_constants.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
import '../../bloc/createItinerary/create_itinerary_bloc.dart';
|
import '../../../core/route_constants.dart';
|
||||||
|
import '../../bloc/createItinerary/create_itinerary_bloc.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ItineraryCompletionView extends StatefulWidget {
|
final _kProgressSteps = [0.25, 0.50, 0.75, 0.90];
|
||||||
const ItineraryCompletionView({super.key});
|
|
||||||
|
|
||||||
@override
|
class ItineraryCompletionView extends StatefulWidget {
|
||||||
State<ItineraryCompletionView> createState() => _ItineraryCompletionViewState();
|
const ItineraryCompletionView({super.key});
|
||||||
}
|
|
||||||
|
|
||||||
class _ItineraryCompletionViewState extends State<ItineraryCompletionView>
|
@override
|
||||||
with SingleTickerProviderStateMixin {
|
State<ItineraryCompletionView> createState() => _ItineraryCompletionViewState();
|
||||||
late AnimationController _fadeController;
|
|
||||||
late Animation<double> _fadeAnimation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_fadeController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 700),
|
|
||||||
);
|
|
||||||
_fadeAnimation = CurvedAnimation(
|
|
||||||
parent: _fadeController,
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
class _ItineraryCompletionViewState extends State<ItineraryCompletionView>
|
||||||
void dispose() {
|
with TickerProviderStateMixin {
|
||||||
_fadeController.dispose();
|
// Fade controller (content ↔ loading overlay)
|
||||||
super.dispose();
|
late AnimationController _fadeController;
|
||||||
}
|
late Animation<double> _fadeAnimation;
|
||||||
|
|
||||||
void _triggerLoadingFade(bool isLoading) {
|
// Progress bar smooth animation
|
||||||
if (isLoading) {
|
late AnimationController _barController;
|
||||||
_fadeController.forward();
|
late Animation<double> _barAnimation;
|
||||||
} else {
|
|
||||||
_fadeController.reverse();
|
int _stepIndex = 0;
|
||||||
|
Timer? _stepTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_fadeController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 700),
|
||||||
|
);
|
||||||
|
_fadeAnimation = CurvedAnimation(
|
||||||
|
parent: _fadeController,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
_barController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 600),
|
||||||
|
);
|
||||||
|
_barAnimation = Tween<double>(begin: 0.0, end: 0.0).animate(
|
||||||
|
CurvedAnimation(parent: _barController, curve: Curves.easeInOut),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void dispose() {
|
||||||
return BlocListener<CreateItineraryBloc, CreateItineraryState>(
|
_fadeController.dispose();
|
||||||
listener: (context, state) {
|
_barController.dispose();
|
||||||
if (state is CreateItinerarySuccess) {
|
_stepTimer?.cancel();
|
||||||
Navigator.of(
|
super.dispose();
|
||||||
context,
|
}
|
||||||
).pushReplacementNamed(RouteConstants.yourItinerary, arguments: state.data['id']);
|
|
||||||
} else if (state is CreateItineraryFailure) {
|
void _animateBarTo(double target) {
|
||||||
_fadeController.reverse();
|
final from = _barAnimation.value;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
_barAnimation = Tween<double>(begin: from, end: target).animate(
|
||||||
SnackBar(
|
CurvedAnimation(parent: _barController, curve: Curves.easeInOut),
|
||||||
content: Text(state.errorMessage),
|
);
|
||||||
backgroundColor: Colors.red,
|
_barController.forward(from: 0.0);
|
||||||
),
|
}
|
||||||
);
|
|
||||||
|
void _startSteps() {
|
||||||
|
_stepIndex = 0;
|
||||||
|
_animateBarTo(_kProgressSteps[0]);
|
||||||
|
|
||||||
|
_stepTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
|
||||||
|
if (!mounted) {
|
||||||
|
timer.cancel();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
if (_stepIndex < _kProgressSteps.length - 1) {
|
||||||
child: Scaffold(
|
setState(() => _stepIndex++);
|
||||||
backgroundColor: const Color(0xFFFFF5F5),
|
_animateBarTo(_kProgressSteps[_stepIndex]);
|
||||||
body: BlocBuilder<CreateItineraryBloc, CreateItineraryState>(
|
} else {
|
||||||
builder: (context, createState) {
|
// Stuck at 90% — waiting for API response
|
||||||
final isLoading = createState is CreateItineraryLoading;
|
timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger fade animation based on loading state
|
void _resetSteps() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
_stepTimer?.cancel();
|
||||||
_triggerLoadingFade(isLoading);
|
setState(() => _stepIndex = 0);
|
||||||
|
_animateBarTo(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _completeAndNavigate(VoidCallback onDone) {
|
||||||
|
_stepTimer?.cancel();
|
||||||
|
_animateBarTo(1.0);
|
||||||
|
Future.delayed(const Duration(milliseconds: 650), onDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _triggerLoadingFade(bool isLoading) {
|
||||||
|
if (isLoading) {
|
||||||
|
_fadeController.forward();
|
||||||
|
_startSteps();
|
||||||
|
} else {
|
||||||
|
_fadeController.reverse();
|
||||||
|
_resetSteps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<CreateItineraryBloc, CreateItineraryState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
// ✅ START loading (ONLY once)
|
||||||
|
if (state is CreateItineraryLoading) {
|
||||||
|
_triggerLoadingFade(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ SUCCESS
|
||||||
|
else if (state is CreateItinerarySuccess) {
|
||||||
|
_completeAndNavigate(() {
|
||||||
|
Navigator.of(context).pushReplacementNamed(
|
||||||
|
RouteConstants.yourItinerary,
|
||||||
|
arguments: state.data['id'],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Stack(
|
// ❌ FAILURE
|
||||||
children: [
|
else if (state is CreateItineraryFailure) {
|
||||||
// Normal content — fades out when loading
|
_triggerLoadingFade(false);
|
||||||
FadeTransition(
|
|
||||||
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(_fadeAnimation),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
child: IgnorePointer(
|
SnackBar(
|
||||||
ignoring: isLoading,
|
content: Text(state.errorMessage),
|
||||||
child: SingleChildScrollView(
|
backgroundColor: Colors.red,
|
||||||
child: Column(
|
),
|
||||||
children: [
|
);
|
||||||
SizedBox(height: 120.h),
|
}
|
||||||
Column(
|
|
||||||
children: [
|
// 🔄 fallback safety
|
||||||
SizedBox(height: 26.h),
|
else {
|
||||||
RichText(
|
_triggerLoadingFade(false);
|
||||||
textAlign: TextAlign.center,
|
}
|
||||||
text: TextSpan(
|
},
|
||||||
style: GoogleFonts.poppins(fontSize: 20.sp),
|
|
||||||
children: const [
|
child: Scaffold(
|
||||||
TextSpan(
|
backgroundColor: const Color(0xFFFFF5F5),
|
||||||
text: 'Your ',
|
|
||||||
style: TextStyle(color: Color(0xFF787A86)),
|
body: BlocBuilder<CreateItineraryBloc, CreateItineraryState>(
|
||||||
),
|
builder: (context, createState) {
|
||||||
TextSpan(
|
final isLoading = createState is CreateItineraryLoading;
|
||||||
text: 'Magic Itinerary',
|
|
||||||
style: TextStyle(
|
// ❌ REMOVE THIS (VERY IMPORTANT)
|
||||||
color: Color(0xFFE8645A),
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fontWeight: FontWeight.bold,
|
// _triggerLoadingFade(isLoading);
|
||||||
),
|
// });
|
||||||
),
|
|
||||||
TextSpan(
|
return Stack(
|
||||||
text: ' is ',
|
children: [
|
||||||
style: TextStyle(color: Color(0xFF787A86)),
|
// ── Normal content ─────────────────────────
|
||||||
),
|
FadeTransition(
|
||||||
TextSpan(
|
opacity: Tween<double>(begin: 1.0, end: 0.0)
|
||||||
text: 'Ready ',
|
.animate(_fadeAnimation),
|
||||||
style: TextStyle(
|
child: IgnorePointer(
|
||||||
color: Color(0xFFE8645A),
|
ignoring: isLoading,
|
||||||
fontWeight: FontWeight.bold,
|
child: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
),
|
children: [
|
||||||
TextSpan(
|
SizedBox(height: 120.h),
|
||||||
text: '✨',
|
Column(
|
||||||
style: TextStyle(color: Color(0xFFF5A623)),
|
children: [
|
||||||
),
|
SizedBox(height: 26.h),
|
||||||
],
|
|
||||||
),
|
/// Title
|
||||||
),
|
RichText(
|
||||||
SizedBox(height: 4.h),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
|
||||||
child: const Text(
|
|
||||||
"We've got everything we need to plan your perfect trip",
|
|
||||||
style: TextStyle(color: Color(0xFF6A7282), fontSize: 14),
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
text: TextSpan(
|
||||||
),
|
style: GoogleFonts.poppins(fontSize: 20.sp),
|
||||||
SizedBox(height: 32.h),
|
children: [
|
||||||
OutlinedButton(
|
TextSpan(
|
||||||
style: OutlinedButton.styleFrom(
|
text: AppLocalizations.of(context)!.yourLabel,
|
||||||
side: const BorderSide(
|
style: const TextStyle(color: Color(0xFF787A86)),
|
||||||
color: Color(0xFFE5E7EB),
|
),
|
||||||
width: 1.1,
|
TextSpan(
|
||||||
),
|
text: AppLocalizations.of(context)!.magicItineraryLabel,
|
||||||
shape: RoundedRectangleBorder(
|
style: const TextStyle(
|
||||||
borderRadius: BorderRadius.circular(40),
|
color: Color(0xFFE8645A),
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
minimumSize: Size(double.infinity, 42.h),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
|
||||||
.read<ItineraryStepNavigationBloc>()
|
|
||||||
.add(ItineraryStepStartOver());
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
"assets/icons/refresh.png",
|
|
||||||
height: 18,
|
|
||||||
width: 18,
|
|
||||||
color: const Color(0xFF364153),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8.w),
|
|
||||||
CustomText(
|
|
||||||
text: "Start Over",
|
|
||||||
size: 16.sp,
|
|
||||||
color: const Color(0xFF364153),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 12.h),
|
|
||||||
BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
|
|
||||||
builder: (context, detailState) {
|
|
||||||
return CustomFilledButton(
|
|
||||||
width: double.infinity,
|
|
||||||
label: "Get My Trip Plan",
|
|
||||||
showArrow: true,
|
|
||||||
onTap: () {
|
|
||||||
context.read<CreateItineraryBloc>().add(
|
|
||||||
CreateItinerarySubmitted(
|
|
||||||
startDate: detailState.selectedApiDate ?? "",
|
|
||||||
tripEnergy: detailState.selectedEnergy ?? "",
|
|
||||||
travelingWithKids:
|
|
||||||
(detailState.withKid ?? "").toLowerCase() == "yes",
|
|
||||||
dietaryPreferences: detailState.selectedDietary != null
|
|
||||||
? [detailState.selectedDietary!]
|
|
||||||
: [],
|
|
||||||
preferences: {
|
|
||||||
if (detailState.museumRating != null)
|
|
||||||
"artAndMuseums":
|
|
||||||
int.tryParse(detailState.museumRating!) ?? 0,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
TextSpan(
|
||||||
);
|
text: AppLocalizations.of(context)!.isLabel,
|
||||||
},
|
style: const TextStyle(color: Color(0xFF787A86)),
|
||||||
),
|
),
|
||||||
],
|
TextSpan(
|
||||||
),
|
text: AppLocalizations.of(context)!.readyLabel,
|
||||||
SizedBox(height: 32.h),
|
style: const TextStyle(
|
||||||
],
|
color: Color(0xFFE8645A),
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const TextSpan(
|
||||||
|
text: '✨',
|
||||||
// Loading overlay — fades in when loading
|
style: TextStyle(color: Color(0xFFF5A623)),
|
||||||
FadeTransition(
|
),
|
||||||
opacity: _fadeAnimation,
|
],
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: !isLoading,
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Lottie.asset(
|
|
||||||
'assets/intro/itinerary_creating.json',
|
|
||||||
width: 260.w,
|
|
||||||
height: 260.w,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
SizedBox(height: 24.h),
|
|
||||||
RichText(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
text: TextSpan(
|
|
||||||
style: GoogleFonts.poppins(fontSize: 24.sp),
|
|
||||||
children: const [
|
|
||||||
TextSpan(
|
|
||||||
text: 'Creating\n',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFF364153),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
|
||||||
text: 'Your Itinerary',
|
SizedBox(height: 4.h),
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFFE8645A),
|
/// Subtitle
|
||||||
fontWeight: FontWeight.bold,
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.weHaveGotEverythingWeNeed,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF6A7282),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
|
/// Start Over
|
||||||
|
OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: const BorderSide(
|
||||||
|
color: Color(0xFFE5E7EB),
|
||||||
|
width: 1.1,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
),
|
||||||
|
minimumSize: Size(double.infinity, 42.h),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<ItineraryStepNavigationBloc>()
|
||||||
|
.add(ItineraryStepStartOver());
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
"assets/icons/refresh.png",
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
color: const Color(0xFF364153),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
CustomText(
|
||||||
|
text: AppLocalizations.of(context)!.startOverLabel,
|
||||||
|
size: 16.sp,
|
||||||
|
color: const Color(0xFF364153),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
|
/// Button
|
||||||
|
BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
|
||||||
|
builder: (context, detailState) {
|
||||||
|
return CustomFilledButton(
|
||||||
|
width: double.infinity,
|
||||||
|
label: AppLocalizations.of(context)!.getMyTripPlan,
|
||||||
|
showArrow: true,
|
||||||
|
onTap: () {
|
||||||
|
context.read<CreateItineraryBloc>().add(
|
||||||
|
CreateItinerarySubmitted(
|
||||||
|
startDate:
|
||||||
|
detailState.selectedApiDate ?? "",
|
||||||
|
tripEnergy:
|
||||||
|
detailState.selectedEnergy ?? "",
|
||||||
|
travelingWithKids:
|
||||||
|
(detailState.withKid ?? "")
|
||||||
|
.toLowerCase() ==
|
||||||
|
"yes",
|
||||||
|
dietaryPreferences:
|
||||||
|
detailState.selectedDietary != null
|
||||||
|
? [detailState.selectedDietary!]
|
||||||
|
: [],
|
||||||
|
preferences: {
|
||||||
|
if (detailState.museumRating != null)
|
||||||
|
"artAndMuseums": int.tryParse(
|
||||||
|
detailState.museumRating!) ??
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 32.h),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
// ── Loading overlay ─────────────────────────
|
||||||
);
|
FadeTransition(
|
||||||
},
|
opacity: _fadeAnimation,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: !isLoading,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Lottie.asset(
|
||||||
|
'assets/intro/itinerary_creating.json',
|
||||||
|
width: 260.w,
|
||||||
|
height: 260.w,
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
|
/// Title
|
||||||
|
RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: GoogleFonts.poppins(fontSize: 24.sp),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.creatingLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF364153),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: AppLocalizations.of(context)!.yourItineraryLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFE8645A),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 28.h),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
/// Step Text
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
child: Text(
|
||||||
|
[
|
||||||
|
AppLocalizations.of(context)!.gatheringPreferencesLabel,
|
||||||
|
AppLocalizations.of(context)!.findingBestSpotsLabel,
|
||||||
|
AppLocalizations.of(context)!.buildingScheduleLabel,
|
||||||
|
AppLocalizations.of(context)!.almostThereLabel,
|
||||||
|
][_stepIndex],
|
||||||
|
key: ValueKey(_stepIndex),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: const Color(0xFF787A86),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
|
||||||
|
/// Progress bar
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _barAnimation,
|
||||||
|
builder: (_, __) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: _barAnimation.value,
|
||||||
|
minHeight: 8.h,
|
||||||
|
backgroundColor:
|
||||||
|
const Color(0xFFE5E7EB),
|
||||||
|
valueColor:
|
||||||
|
const AlwaysStoppedAnimation<Color>(
|
||||||
|
Color(0xFFE8645A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
|
||||||
|
/// Percentage
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _barAnimation,
|
||||||
|
builder: (_, __) {
|
||||||
|
final percent =
|
||||||
|
(_barAnimation.value * 100).toInt();
|
||||||
|
return Text(
|
||||||
|
'$percent%',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: const Color(0xFF787A86),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -4,16 +4,14 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selec
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class KidsSelectionView extends StatelessWidget {
|
class KidsSelectionView extends StatelessWidget {
|
||||||
const KidsSelectionView({super.key});
|
const KidsSelectionView({super.key});
|
||||||
|
|
||||||
static const Color _accentColor = Color(0xFFF95F62);
|
static const Color _accentColor = Color(0xFFF95F62);
|
||||||
|
|
||||||
final List<Map<String, String>> options = const [
|
// We will build options dynamically based on context
|
||||||
{"img": "assets/icons/traveling_with_kids.png", "option": "Traveling with\n kids", "value": "with_kids"},
|
|
||||||
{"img": "assets/icons/no_kids.png", "option": "No kids with\n me", "value": "no_kids"},
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -24,7 +22,7 @@ class KidsSelectionView extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Are you travelling with kids?",
|
AppLocalizations.of(context)!.areYouTravellingWithKidsLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF101828),
|
color: const Color(0xFF101828),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -33,7 +31,11 @@ class KidsSelectionView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 32.h),
|
SizedBox(height: 32.h),
|
||||||
Row(
|
Row(
|
||||||
children: List.generate(options.length, (index) {
|
children: List.generate(2, (index) {
|
||||||
|
final options = [
|
||||||
|
{"img": "assets/icons/traveling_with_kids.png", "option": AppLocalizations.of(context)!.travelingWithKidsLabel, "value": "with_kids"},
|
||||||
|
{"img": "assets/icons/no_kids.png", "option": AppLocalizations.of(context)!.noKidsWithMeLabel, "value": "no_kids"},
|
||||||
|
];
|
||||||
final item = options[index];
|
final item = options[index];
|
||||||
final isSelected = state.withKid == item['value'];
|
final isSelected = state.withKid == item['value'];
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,14 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selec
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ArtGallerySelectionView extends StatelessWidget {
|
class ArtGallerySelectionView extends StatelessWidget {
|
||||||
const ArtGallerySelectionView({super.key});
|
const ArtGallerySelectionView({super.key});
|
||||||
|
|
||||||
static const Color _accentColor = Color(0xFFF95F62);
|
static const Color _accentColor = Color(0xFFF95F62);
|
||||||
|
|
||||||
final List<Map<String, String>> options = const [
|
// Resolving options dynamically
|
||||||
{"img": "assets/icons/not_interested.png", "name": "Not Interested", "star": "⭐"},
|
|
||||||
{"img": "assets/icons/maybe.png", "name": "Maybe One or Two", "star": "⭐⭐"},
|
|
||||||
{"img": "assets/icons/sounds_good.png", "name": "Yes, Sounds Good!", "star": "⭐⭐⭐"},
|
|
||||||
{"img": "assets/icons/love_them.png", "name": "Absolutely Love Them!", "star": "⭐⭐⭐⭐"},
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -26,7 +22,7 @@ class ArtGallerySelectionView extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Do you enjoy visiting museums and art galleries?",
|
AppLocalizations.of(context)!.doYouEnjoyMuseumsLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF101828),
|
color: const Color(0xFF101828),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -41,7 +37,13 @@ class ArtGallerySelectionView extends StatelessWidget {
|
|||||||
crossAxisSpacing: 12.w,
|
crossAxisSpacing: 12.w,
|
||||||
mainAxisSpacing: 12.h,
|
mainAxisSpacing: 12.h,
|
||||||
childAspectRatio: 1.3,
|
childAspectRatio: 1.3,
|
||||||
children: List.generate(options.length, (index) {
|
children: List.generate(4, (index) {
|
||||||
|
final options = [
|
||||||
|
{"img": "assets/icons/not_interested.png", "name": AppLocalizations.of(context)!.notInterestedLabel, "star": "⭐"},
|
||||||
|
{"img": "assets/icons/maybe.png", "name": AppLocalizations.of(context)!.maybeOneOrTwoLabel, "star": "⭐⭐"},
|
||||||
|
{"img": "assets/icons/sounds_good.png", "name": AppLocalizations.of(context)!.yesSoundsGoodLabel, "star": "⭐⭐⭐"},
|
||||||
|
{"img": "assets/icons/love_them.png", "name": AppLocalizations.of(context)!.absolutelyLoveThemLabel, "star": "⭐⭐⭐⭐"},
|
||||||
|
];
|
||||||
final item = options[index];
|
final item = options[index];
|
||||||
final isSelected = state.museumRating == item['star'];
|
final isSelected = state.museumRating == item['star'];
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'itinerary_creation_steps/kids_selection_view.dart';
|
|||||||
import 'itinerary_creation_steps/scenic_viewpoints_rating_view.dart';
|
import 'itinerary_creation_steps/scenic_viewpoints_rating_view.dart';
|
||||||
import 'itinerary_creation_steps/shopping_rating_view.dart';
|
import 'itinerary_creation_steps/shopping_rating_view.dart';
|
||||||
import 'itinerary_creation_steps/wildlife_rating_view.dart';
|
import 'itinerary_creation_steps/wildlife_rating_view.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ItineraryCreationPage extends StatefulWidget {
|
class ItineraryCreationPage extends StatefulWidget {
|
||||||
const ItineraryCreationPage({super.key});
|
const ItineraryCreationPage({super.key});
|
||||||
@@ -58,7 +59,7 @@ class _ItineraryCreationPageState extends State<ItineraryCreationPage> {
|
|||||||
Icon(Icons.arrow_back, color: Colors.black87),
|
Icon(Icons.arrow_back, color: Colors.black87),
|
||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
Text(
|
Text(
|
||||||
"Back",
|
AppLocalizations.of(context)!.backTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
@@ -158,7 +159,7 @@ class _ItineraryCreationPageState extends State<ItineraryCreationPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Magic Itinerary ✨",
|
AppLocalizations.of(context)!.magicItineraryTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
@@ -212,12 +213,12 @@ class _ItineraryCreationPageState extends State<ItineraryCreationPage> {
|
|||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: "Step "),
|
TextSpan(text: AppLocalizations.of(context)!.stepPrefix),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "${state.selectedIndex}",
|
text: "${state.selectedIndex}",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
TextSpan(text: " of "),
|
TextSpan(text: AppLocalizations.of(context)!.ofSuffix),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "5",
|
text: "5",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
|||||||
@@ -40,12 +40,13 @@ class MagicItineraryEmptyView extends StatelessWidget {
|
|||||||
CustomFilledButton(onTap: (){
|
CustomFilledButton(onTap: (){
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ItineraryCreationStartPage(),
|
builder: (context) => ItineraryCreationStartPage(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: "Create My Itinerary", showArrow: true,)
|
label: "Create My Magic Itinerary", showArrow: true,)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import '../../common_bloc/bottom_navigation_bloc.dart';
|
import '../../common_bloc/bottom_navigation_bloc.dart';
|
||||||
import '../../login/view/login_email_bottomsheet.dart';
|
import '../../login/view/login_email_bottomsheet.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class MagicItineraryView extends StatefulWidget {
|
class MagicItineraryView extends StatefulWidget {
|
||||||
const MagicItineraryView({super.key});
|
const MagicItineraryView({super.key});
|
||||||
@@ -83,13 +84,13 @@ class _MagicItineraryViewState extends State<MagicItineraryView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Plan your next adventure",
|
text: AppLocalizations.of(context)!.planYourNextAdventure,
|
||||||
color: Color(0xFF656565),
|
color: Color(0xFF656565),
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
CustomFilledButton(
|
CustomFilledButton(
|
||||||
label: "Create My Itinerary",
|
label: AppLocalizations.of(context)!.createMyMagicItinerary,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -160,7 +161,7 @@ class NotLoggedInItineraryView extends StatelessWidget {
|
|||||||
SizedBox(height: 32.h),
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You have not Logged in Yet! ☹️",
|
text: AppLocalizations.of(context)!.notLoggedInYet,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -171,7 +172,7 @@ class NotLoggedInItineraryView extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "Log in or purchase a pass to unlock the magic itinerary!",
|
text: AppLocalizations.of(context)!.loginOrPurchasePass,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Color(0xFF656565),
|
color: Color(0xFF656565),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -192,7 +193,7 @@ class NotLoggedInItineraryView extends StatelessWidget {
|
|||||||
builder: (_) => const LoginEmailBottomsheet(),
|
builder: (_) => const LoginEmailBottomsheet(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: "Log In",
|
label: AppLocalizations.of(context)!.logInLabel,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -219,7 +220,7 @@ class RequiresUnlimitedPassView extends StatelessWidget {
|
|||||||
SizedBox(height: 32.h),
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You do not possess an Unlimited Pass! 😔",
|
text: AppLocalizations.of(context)!.noUnlimitedPass,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -230,7 +231,7 @@ class RequiresUnlimitedPassView extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "Get your Unlimited Pass and create a custom itinerary!",
|
text: AppLocalizations.of(context)!.getUnlimitedPass,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: Color(0xFF656565),
|
color: Color(0xFF656565),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -243,7 +244,7 @@ class RequiresUnlimitedPassView extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<NavigationBloc>().add(NavigationTabChanged(0));
|
context.read<NavigationBloc>().add(NavigationTabChanged(0));
|
||||||
},
|
},
|
||||||
label: "Buy Unlimited CityCard",
|
label: AppLocalizations.of(context)!.buyUnlimitedCityCard,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -275,7 +276,7 @@ class NoItineraryView extends StatelessWidget {
|
|||||||
|
|
||||||
/// Title
|
/// Title
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "You Don’t have an Itinerary Yet! 😟",
|
text: AppLocalizations.of(context)!.noItineraryYet,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -285,8 +286,7 @@ class NoItineraryView extends StatelessWidget {
|
|||||||
|
|
||||||
/// Subtitle
|
/// Subtitle
|
||||||
CustomText(
|
CustomText(
|
||||||
text:
|
text: AppLocalizations.of(context)!.createPersonalizedItinerary,
|
||||||
"Create your own personalized magic itinerary that suites your travel needs",
|
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: const Color(0xFF656565),
|
color: const Color(0xFF656565),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -304,7 +304,7 @@ class NoItineraryView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: "Create My Itinerary",
|
label: AppLocalizations.of(context)!.createMyMagicItinerary,
|
||||||
showArrow: true,
|
showArrow: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -340,7 +340,7 @@ class ErrorItineraryView extends StatelessWidget {
|
|||||||
SizedBox(height: 32.h),
|
SizedBox(height: 32.h),
|
||||||
|
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Oops! Something went wrong",
|
text: AppLocalizations.of(context)!.oopsSomethingWentWrong,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -362,7 +362,7 @@ class ErrorItineraryView extends StatelessWidget {
|
|||||||
|
|
||||||
CustomFilledButton(
|
CustomFilledButton(
|
||||||
onTap: onRetry,
|
onTap: onRetry,
|
||||||
label: "Try Again",
|
label: AppLocalizations.of(context)!.tryAgain,
|
||||||
showArrow: false,
|
showArrow: false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -423,7 +423,7 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "$cityName Travel Plan",
|
text: "$cityName${AppLocalizations.of(context)!.travelPlan}",
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -437,7 +437,9 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(100.r),
|
borderRadius: BorderRadius.circular(100.r),
|
||||||
),
|
),
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: itinerary.isActive ? "Active" : "Inactive",
|
text: itinerary.isActive
|
||||||
|
? AppLocalizations.of(context)!.activeLabel
|
||||||
|
: AppLocalizations.of(context)!.inactiveLabel,
|
||||||
size: 11.sp,
|
size: 11.sp,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -456,7 +458,7 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
Image.asset("assets/icons/calender_filled.png", width: 16.sp),
|
Image.asset("assets/icons/calender_filled.png", width: 16.sp),
|
||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "${itinerary.totalDays} days",
|
text: "${itinerary.totalDays}${AppLocalizations.of(context)!.daysLabel}",
|
||||||
color: Color(0xFF8E8E8E),
|
color: Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -472,7 +474,7 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "$totalAttractions attractions",
|
text: "$totalAttractions${AppLocalizations.of(context)!.attractionsCountLabel}",
|
||||||
color: Color(0xFF8E8E8E),
|
color: Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -484,7 +486,7 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
Icon(Icons.watch_later, color: Color(0xFF8E8E8E), size: 16.sp),
|
Icon(Icons.watch_later, color: Color(0xFF8E8E8E), size: 16.sp),
|
||||||
SizedBox(width: 4.w),
|
SizedBox(width: 4.w),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Created ${_formatDate(itinerary.createdAt)}",
|
text: "${AppLocalizations.of(context)!.createdOnLabel}${_formatDate(itinerary.createdAt)}",
|
||||||
color: Color(0xFF8E8E8E),
|
color: Color(0xFF8E8E8E),
|
||||||
size: 12.sp,
|
size: 12.sp,
|
||||||
),
|
),
|
||||||
@@ -521,7 +523,7 @@ class ItineraryFilledCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CustomText(
|
child: CustomText(
|
||||||
text: "View Itinerary",
|
text: AppLocalizations.of(context)!.viewItineraryLabel,
|
||||||
size: 16.sp,
|
size: 16.sp,
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
|
|||||||
1095
lib/l10n/app_en.arb
Normal file
671
lib/l10n/app_es.arb
Normal file
@@ -0,0 +1,671 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "es",
|
||||||
|
"profileTitle": "Mi perfil",
|
||||||
|
"supportAndLegal": "Soporte y Legal",
|
||||||
|
"contactUs": "Contáctenos",
|
||||||
|
"termsAndConditions": "Términos y condiciones",
|
||||||
|
"faq": "Preguntas frecuentes",
|
||||||
|
"privacyPolicy": "Política de privacidad",
|
||||||
|
"logout": "Cerrar sesión",
|
||||||
|
"signIn": "Iniciar sesión",
|
||||||
|
"heyStranger": "¡Hola, extraño! 👋",
|
||||||
|
"guestSubtitle1": "Estamos encantados de tenerte en nuestra app.",
|
||||||
|
"guestSubtitle2": "¿Por qué no lo haces oficial?",
|
||||||
|
"accountSettings": "Configuración de cuenta",
|
||||||
|
"editProfile": "Editar perfil",
|
||||||
|
"changeLanguage": "Cambiar idioma",
|
||||||
|
"retry": "Reintentar",
|
||||||
|
"failedToLoadProfile": "Error al cargar el perfil",
|
||||||
|
"changeLanguageTitle": "Cambiar idioma",
|
||||||
|
"searchLanguages": "Buscar idiomas",
|
||||||
|
"save": "Guardar",
|
||||||
|
"oopsSomethingWentWrong": "¡Ups! Algo salió mal",
|
||||||
|
"tryAgain": "Intentar otra vez",
|
||||||
|
"defaultCityName": "Nombre de la ciudad",
|
||||||
|
"defaultCityDescription": "Descripción de la ciudad",
|
||||||
|
"noHighlightsAvailable": "No hay atractivos disponibles",
|
||||||
|
"popular": "Populares ",
|
||||||
|
"attractions": "Atracciones",
|
||||||
|
"viewAll": "Ver todo",
|
||||||
|
"createMyMagicItinerary": "Crear mi itinerario mágico",
|
||||||
|
"claimOffersWithCityCards": "Reclama ofertas con tus Tarjetas de Ciudad",
|
||||||
|
"offerSubtitleDummy": "Lorem ipsum dolor sit amet...",
|
||||||
|
"noAttractionsAvailable": "No hay atracciones disponibles",
|
||||||
|
"explore": "Explorar ",
|
||||||
|
"moreCities": "Más ciudades.",
|
||||||
|
"stay": "Mantente ",
|
||||||
|
"connected": "Conectado",
|
||||||
|
"everywhere": "En todas partes",
|
||||||
|
"claimEsimOffers": "Reclama ofertas de e-Sim con\nCityCard",
|
||||||
|
"chooseYourCard": "Elige tu tarjeta",
|
||||||
|
"diveIntoExtensiveSelection": "Sumérgete en una amplia selección de destinos...",
|
||||||
|
"from": "Desde ",
|
||||||
|
"getACard": "Obtener una Tarjeta",
|
||||||
|
"marriott": "MARRIOTT ",
|
||||||
|
"moments": "MOMENTOS",
|
||||||
|
"citycard": "CITYCARD ",
|
||||||
|
"prices": "PRECIOS",
|
||||||
|
"premium": "Estancias ",
|
||||||
|
"stays": "Premium",
|
||||||
|
"citycardPrices": "Precios de CityCard",
|
||||||
|
"claimHotelDiscountOffers": "Reclama descuentos en hoteles\ncon CityCard",
|
||||||
|
"back": "Atrás",
|
||||||
|
"selectACity": "Selecciona una ciudad",
|
||||||
|
"searchCities": "Buscar ciudades",
|
||||||
|
"errorLoadingCities": "Error al cargar ciudades",
|
||||||
|
"noCitiesFound": "No se encontraron ciudades",
|
||||||
|
"individualTickets": "Boletos individuales:",
|
||||||
|
"cityCardLabel": "Tarjeta City Card:",
|
||||||
|
"getYourCard": "Obtén tu cuenta",
|
||||||
|
"fromLabel": "Desde",
|
||||||
|
"perAdult": " /Adulto",
|
||||||
|
"planYour": "Planifica tu ",
|
||||||
|
"dreamJourney": "Viaje de Ensueño",
|
||||||
|
"inJust3Seconds": "\nen solo 3 segundos",
|
||||||
|
"navExplore": "Explorar",
|
||||||
|
"navMagicItinerary": "Itinerario Mágico",
|
||||||
|
"navMyCards": "Mis Tarjetas",
|
||||||
|
"navPostcard": "Postal",
|
||||||
|
"planYourNextAdventure": "Planifica tu próxima aventura",
|
||||||
|
"createMyItinerary": "Crear Mi Itinerario",
|
||||||
|
"notLoggedInYet": "¡Aún no has iniciado sesión!",
|
||||||
|
"loginOrPurchasePass": "¡Inicia sesión o compra un pase para desbloquear el itinerario mágico!",
|
||||||
|
"logInLabel": "Iniciar Sesión",
|
||||||
|
"noUnlimitedPass": "¡No posees un Pase Ilimitado! ὡ4",
|
||||||
|
"getUnlimitedPass": "¡Obtén tu Pase Ilimitado y crea un itinerario personalizado!",
|
||||||
|
"buyUnlimitedCityCard": "Comprar CityCard Ilimitada",
|
||||||
|
"noItineraryYet": "¡Aún no tienes un Itinerario! ὡF",
|
||||||
|
"createPersonalizedItinerary": "Crea tu propio itinerario mágico personalizado que se adapte a tus necesidades de viaje",
|
||||||
|
"travelPlan": " Plan de Viaje",
|
||||||
|
"activeLabel": "Activo",
|
||||||
|
"inactiveLabel": "Inactivo",
|
||||||
|
"daysLabel": " días",
|
||||||
|
"attractionsCountLabel": " atracciones",
|
||||||
|
"createdOnLabel": "Creado ",
|
||||||
|
"viewItineraryLabel": "Ver Itinerario",
|
||||||
|
"allFilter": "Todo",
|
||||||
|
"latestFilter": "Más reciente",
|
||||||
|
"oldestFilter": "Más antiguo",
|
||||||
|
"flexiCardFilter": "Tarjeta Flexi",
|
||||||
|
"unlimitedCardFilter": "Tarjeta Ilimitada",
|
||||||
|
"sortByLabel": "Ordenar por",
|
||||||
|
"pleaseLogInToViewPasses": "Inicia sesión para ver tus pases",
|
||||||
|
"logInAccessPasses": "Inicia sesión para acceder a tus pases, ofertas exclusivas y descuentos en tu viaje.",
|
||||||
|
"noPassYet": "¡Aún no tienes un pase! 😕",
|
||||||
|
"getAPassUnlockOffers": "Obtén un pase y desbloquea ofertas exclusivas, descuentos y más.",
|
||||||
|
"buyAPass": "Comprar un Pase",
|
||||||
|
"attractionSingular": "Atracción",
|
||||||
|
"attractionsPlural": "Atracciones",
|
||||||
|
"daySingular": "Día",
|
||||||
|
"daysPlural": "Días",
|
||||||
|
"cardSuffixLabel": " Tarjeta",
|
||||||
|
"adultsPrefix": "Adultos-",
|
||||||
|
"kidsPrefix": " • Niños-",
|
||||||
|
"validTillPrefix": "Válido hasta: ",
|
||||||
|
"noPostcardOrdersYet": "¡Parece que aún no has pedido\nninguna postal!",
|
||||||
|
"whipUpFunPostcardOrders": "¿Qué tal si preparamos una postal divertida para enviarla a tus seres queridos? ¡Comencemos con eso!",
|
||||||
|
"searchOrdersHint": "Buscar pedidos...",
|
||||||
|
"foundPrefix": "Encontrado ",
|
||||||
|
"ofMiddle": " de ",
|
||||||
|
"ordersSuffix": " pedidos",
|
||||||
|
"noOrdersFound": "No se encontraron pedidos",
|
||||||
|
"tryAdjustingSearchQuery": "Intenta ajustar tu búsqueda",
|
||||||
|
"errorLoadingOrders": "Error al cargar los pedidos",
|
||||||
|
"retryButtonLabel": "Reintentar",
|
||||||
|
"statusLabel": "Estado:",
|
||||||
|
"previewButtonLabel": "Vista previa",
|
||||||
|
"noPostcardDraftsYet": "¡Parece que aún no has creado\nninguna postal!",
|
||||||
|
"whipUpFunPostcardDrafts": "¿Por qué no preparas una postal y se la envías a alguien especial que esté lejos?",
|
||||||
|
"searchDraftsHint": "Buscar borradores...",
|
||||||
|
"draftsSuffix": " borradores",
|
||||||
|
"noSearchAvailable": "Búsqueda no disponible",
|
||||||
|
"tryDifferentKeywords": "Intenta buscar con diferentes palabras clave",
|
||||||
|
"errorLoadingDrafts": "Error al cargar los borradores",
|
||||||
|
"deleteButtonLabel": "Eliminar",
|
||||||
|
"editButtonLabel": "Editar",
|
||||||
|
"sendButtonLabel": "Enviar",
|
||||||
|
"myDraftsTab": "Mis borradores",
|
||||||
|
"myOrdersTab": "Mis pedidos",
|
||||||
|
"createPostcardButton": "Crear postal",
|
||||||
|
"makeMostOfTrip": "Aprovecha al máximo tu viaje",
|
||||||
|
"designUniquePostcards": "Diseña tus propias postales únicas para\natesorar tus momentos inolvidables.",
|
||||||
|
"letsCreateButton": "Vamos a crear ",
|
||||||
|
"notLoggedInPostCards": "¡Aún no has iniciado sesión!",
|
||||||
|
"loginToDesignPostcards": "Para diseñar tus propias postales únicas, inicia\nsesión y compra un pase ilimitado",
|
||||||
|
"errorLoadingDataTitle": "Error al cargar datos",
|
||||||
|
"buyACardTitle": "Comprar una tarjeta",
|
||||||
|
"memberPrivilegesTitle": "Privilegios para miembros",
|
||||||
|
"noOffersAvailable": "No hay ofertas disponibles",
|
||||||
|
"availableAttractionsTitle": "Atracciones disponibles",
|
||||||
|
"featureAccessToAttractions": "Acceso a atracciones",
|
||||||
|
"featureEntryToAttractions": "Entrada a atracciones",
|
||||||
|
"featureAccessToExperiences": "Acceso a experiencias",
|
||||||
|
"featureEntryToSites": "Entrada a sitios",
|
||||||
|
"featureAccessToVenues": "Acceso a lugares",
|
||||||
|
"featureEntryToEvents": "Entrada a eventos",
|
||||||
|
"featureAccessToItineraryCreation": "Acceso a la creación de itinerarios",
|
||||||
|
"featureAccessToPostcardCreation": "Acceso a la creación de postales",
|
||||||
|
"featuresTitle": "Características",
|
||||||
|
"unlimitedTitle": "Ilimitado",
|
||||||
|
"fromPrefix": "Desde ",
|
||||||
|
"perAdultSuffix": " /Adulto",
|
||||||
|
"andPrefix": "y ",
|
||||||
|
"perChildSuffix": " /niño",
|
||||||
|
"diveIntoSelection": "¡Sumérgete en una amplia selección de destinos emocionantes!",
|
||||||
|
"noOfAdultsLabel": "No. de adultos",
|
||||||
|
"noOfChildrenLabel": "No. de niños",
|
||||||
|
"noOfDaysLabel": "No. de días",
|
||||||
|
"noOfAttractionsLabel": "No. de atracciones",
|
||||||
|
"youPayLabel": "Tú pagas",
|
||||||
|
"pleaseWaitLabel": "Por favor espera...",
|
||||||
|
"proceedToPayLabel": "Proceder al pago",
|
||||||
|
"atLeastOneAdultRequired": "Se requiere al menos 1 adulto",
|
||||||
|
"cannotGoBelowZero": "No puede ser menor que 0",
|
||||||
|
"noCheckoutDataAvailable": "No hay datos de pago disponibles",
|
||||||
|
"goBackButtonLabel": "Regresar",
|
||||||
|
"completePaymentTitle": "Completar pago",
|
||||||
|
"processingPaymentMessage": "Procesando el pago de tu pase...",
|
||||||
|
"paymentSuccessfulMessage": "¡Pago exitoso!\nTu pase está listo.",
|
||||||
|
"paymentFailedMessage": "Pago fallido",
|
||||||
|
"paymentCancelledMessage": "Pago cancelado",
|
||||||
|
"paymentConfirmedMessage": "¡Pago confirmado con éxito!",
|
||||||
|
"checkoutTitle": "Caja",
|
||||||
|
"adultLabelSingular": "adulto",
|
||||||
|
"adultLabelPlural": "adultos",
|
||||||
|
"kidLabelSingular": "Niño",
|
||||||
|
"kidLabelPlural": "Niños",
|
||||||
|
"loadingCoupons": "Cargando cupones...",
|
||||||
|
"errorLoadingCoupons": "Error al cargar los cupones",
|
||||||
|
"couponAppliedPrefix": "Cupón aplicado: ",
|
||||||
|
"discountOnPrefix": " descuento en ",
|
||||||
|
"noCouponsAvailable": "No hay cupones disponibles",
|
||||||
|
"viewAllCoupons": "Ver todos los cupones",
|
||||||
|
"pleaseLoginToApplyCoupon": "Por favor, inicia sesión para aplicar el cupón",
|
||||||
|
"applyingLabel": "Aplicando...",
|
||||||
|
"removeLabel": "Eliminar",
|
||||||
|
"applyLabel": "Aplicar",
|
||||||
|
"subtotalLabel": "Subtotal",
|
||||||
|
"discountLabel": "Descuento",
|
||||||
|
"totalLabel": "Total",
|
||||||
|
"includingTaxesPrefix": "Incluyendo ",
|
||||||
|
"inTaxesSuffix": " en impuestos",
|
||||||
|
"allCouponsTitle": "Todos los cupones",
|
||||||
|
"applyCouponLabel": "Aplicar cupón",
|
||||||
|
"purchaseDetailsTitle": "Detalles de compra",
|
||||||
|
"buyPassForMyselfTitle": "Comprar pase para mí",
|
||||||
|
"editDetailsLabel": "Editar detalles",
|
||||||
|
"giftThePassTitle": "Regalar el pase",
|
||||||
|
"giftThePassDescription": "Regalar el pase a otra persona",
|
||||||
|
"proceedLabel": "Proceder",
|
||||||
|
"processingLabel": "Procesando...",
|
||||||
|
"payPrefixLabel": "Pagar ",
|
||||||
|
"loginToCheckoutLabel": "Iniciar sesión para Pagar",
|
||||||
|
"yourCartTitle": "Tu carrito",
|
||||||
|
"myCardsTab": "Mis tarjetas",
|
||||||
|
"myPostCardsTab": "Mis postales",
|
||||||
|
"yourCartIsEmpty": "Tu carrito está vacío",
|
||||||
|
"attractionsLabelSuffix": " Atracciones",
|
||||||
|
"daysLabelSuffix": " Días",
|
||||||
|
"youDoNotHaveAnyPasses": "No tienes pases",
|
||||||
|
"emptyPassesDescription": "Obtén un pase y disfruta de ofertas, descuentos y más en tu viaje a tu ciudad favorita",
|
||||||
|
"buyAPassLabel": "Comprar un Pase",
|
||||||
|
"errorLoadingCart": "Error al cargar el carrito",
|
||||||
|
"purchaseOnePostcardAtTime": "Puedes comprar una postal a la vez",
|
||||||
|
"proceedToCheckoutLabel": "Proceder al pago",
|
||||||
|
"loginToAccessPostcardsCart": "Por favor, inicia sesión para acceder a tu carrito de postales",
|
||||||
|
"youDoNotHaveAnyPostcards": "No tienes postales",
|
||||||
|
"emptyPostcardsDescription": "Aún no posees postales ni has enviado a nadie",
|
||||||
|
"designMyPostcardLabel": "Diseñar mi postal",
|
||||||
|
"somethingWentWrong": "Algo salió mal",
|
||||||
|
"retryLabel": "Reintentar",
|
||||||
|
"editDraftLabel": "Editar borrador",
|
||||||
|
"backTitle": "Atrás",
|
||||||
|
"magicItineraryTitle": "Itinerario Mágico ✨",
|
||||||
|
"stepPrefix": "Paso ",
|
||||||
|
"ofSuffix": " de ",
|
||||||
|
"createYourLabel": "Crea tu",
|
||||||
|
"magicItinerarySubtitle": " itinerario mágico",
|
||||||
|
"itineraryCreationStartDescription": "¡Hola! Solo responde algunas preguntas divertidas y crearemos una experiencia de viaje totalmente adaptada a ti. ✈️✨",
|
||||||
|
"letsExploreTogether": "¡Exploremos juntos!",
|
||||||
|
"takesOnlyTwoMinutes": "Toma solo 2 minutos ⏱️",
|
||||||
|
"gatheringPreferencesLabel": "Recopilando tus preferencias...",
|
||||||
|
"findingBestSpotsLabel": "Buscando los mejores lugares...",
|
||||||
|
"buildingScheduleLabel": "Armando tu horario...",
|
||||||
|
"almostThereLabel": "Casi listo...",
|
||||||
|
"yourLabel": "Tu ",
|
||||||
|
"magicItineraryLabel": "Itinerario Mágico",
|
||||||
|
"isLabel": " está ",
|
||||||
|
"readyLabel": "Listo ",
|
||||||
|
"weHaveGotEverythingWeNeed": "Tenemos todo lo que necesitamos para planear tu viaje perfecto",
|
||||||
|
"startOverLabel": "Empezar de nuevo",
|
||||||
|
"getMyTripPlan": "Obtener mi plan de viaje",
|
||||||
|
"creatingLabel": "Creando\n",
|
||||||
|
"yourItineraryLabel": "Tu itinerario",
|
||||||
|
"travelingWithKidsLabel": "Viajando con\n niños",
|
||||||
|
"noKidsWithMeLabel": "Sin niños",
|
||||||
|
"areYouTravellingWithKidsLabel": "¿Viajas con niños?",
|
||||||
|
"noRestrictionsLabel": "Sin restricciones",
|
||||||
|
"vegetarianLabel": "Vegetariano",
|
||||||
|
"veganLabel": "Vegano",
|
||||||
|
"pescatarianLabel": "Pescetariano",
|
||||||
|
"halalLabel": "Halal",
|
||||||
|
"kosherLabel": "Kosher",
|
||||||
|
"doYouFollowDietaryPreferenceLabel": "¿Sigues alguna preferencia dietética?",
|
||||||
|
"whenAreYouPlanningToVisitLabel": "¡Hola! ¿Cuándo planeas visitar?",
|
||||||
|
"selectADateLabel": "Selecciona una fecha",
|
||||||
|
"continueTitle": "Continuar",
|
||||||
|
"relaxedAndChillLabel": "Relajado y tranquilo",
|
||||||
|
"balancedMixLabel": "Mezcla equilibrada",
|
||||||
|
"activeAndEnergeticLabel": "Activo y enérgico",
|
||||||
|
"fullAdventureLabel": "¡Aventura total!",
|
||||||
|
"whatKindOfEnergyLabel": "¿Qué tipo de energía buscas en este viaje?",
|
||||||
|
"notInterestedLabel": "No me interesa",
|
||||||
|
"maybeOneOrTwoLabel": "Tal vez uno o dos",
|
||||||
|
"yesSoundsGoodLabel": "¡Sí, suena bien!",
|
||||||
|
"absolutelyLoveThemLabel": "¡Me encantan!",
|
||||||
|
"doYouEnjoyMuseumsLabel": "¿Disfrutas visitando museos y galerías de arte?",
|
||||||
|
"@esimOfferTitle": {},
|
||||||
|
"esimOfferTitle": "Conéctate al instante con tu eSIM gratuita",
|
||||||
|
"@esimOfferSubtitle": {},
|
||||||
|
"esimOfferSubtitle": "Cada gran viaje comienza con una conectividad fluida.",
|
||||||
|
"@viewPlans": {},
|
||||||
|
"viewPlans": "Ver planes",
|
||||||
|
"@withYour": {},
|
||||||
|
"withYour": "Con tu ",
|
||||||
|
"@esimKeyword": {},
|
||||||
|
"esimKeyword": "eSIM",
|
||||||
|
"@youCanKeyword": {},
|
||||||
|
"youCanKeyword": ", puedes:",
|
||||||
|
"@navigateCity": {},
|
||||||
|
"navigateCity": "Navega por la ciudad con facilidad",
|
||||||
|
"@navigateCityDesc": {},
|
||||||
|
"navigateCityDesc": "Accede a mapas y direcciones en tiempo real dondequiera que vayas",
|
||||||
|
"@bookRides": {},
|
||||||
|
"bookRides": "Reserva viajes, accede a mapas y encuentra atracciones en tiempo real",
|
||||||
|
"@bookRidesDesc": {},
|
||||||
|
"bookRidesDesc": "Mantente conectado a todos los servicios de viaje esenciales",
|
||||||
|
"@sharePhotos": {},
|
||||||
|
"sharePhotos": "Comparte fotos y recuerdos al instante",
|
||||||
|
"@sharePhotosDesc": {},
|
||||||
|
"sharePhotosDesc": "Sube y comparte tus momentos de viaje sin demora",
|
||||||
|
"@stayConnectedFeatures": {},
|
||||||
|
"stayConnectedFeatures": "Mantente conectado con amigos, familiares y planes de viaje",
|
||||||
|
"@stayConnectedFeaturesDesc": {},
|
||||||
|
"stayConnectedFeaturesDesc": "No te pierdas nunca actualizaciones o mensajes importantes mientras viajas",
|
||||||
|
"@simpleKeyword": {},
|
||||||
|
"simpleKeyword": "Simple ",
|
||||||
|
"@threeStepProcess": {},
|
||||||
|
"threeStepProcess": "Proceso de 3 pasos",
|
||||||
|
"@getConnectedInSeconds": {},
|
||||||
|
"getConnectedInSeconds": "Conéctate en segundos",
|
||||||
|
"@receiveQrCode": {},
|
||||||
|
"receiveQrCode": "Recibir código QR",
|
||||||
|
"@receiveQrCodeDesc": {},
|
||||||
|
"receiveQrCodeDesc": "Obtén tu código QR de eSIM único con tu CityCard",
|
||||||
|
"@scanCode": {},
|
||||||
|
"scanCode": "Escanear código",
|
||||||
|
"@scanCodeDesc": {},
|
||||||
|
"scanCodeDesc": "Abre la cámara de tu teléfono y escanea el código QR",
|
||||||
|
"@connectedStep": {},
|
||||||
|
"connectedStep": "Conectado",
|
||||||
|
"@connectedStepDesc": {},
|
||||||
|
"connectedStepDesc": "Estás en línea al instante - ¡empieza a explorar!",
|
||||||
|
"@itsOneMoreWay": {},
|
||||||
|
"itsOneMoreWay": "Es una forma más",
|
||||||
|
"@cityCardsKeywordSpace": {},
|
||||||
|
"cityCardsKeywordSpace": " CityCards",
|
||||||
|
"@makesYourJourney": {},
|
||||||
|
"makesYourJourney": "hace que tu viaje sea",
|
||||||
|
"@smarterKeywordSpace": {},
|
||||||
|
"smarterKeywordSpace": " más inteligente",
|
||||||
|
"@andMoreSpace": {},
|
||||||
|
"andMoreSpace": "y más",
|
||||||
|
"@effortlessSpace": {},
|
||||||
|
"effortlessSpace": " sin esfuerzo",
|
||||||
|
"@startYourJourneyToday": {},
|
||||||
|
"startYourJourneyToday": "Comienza tu viaje hoy",
|
||||||
|
"@enjoy20OffMarriott": {},
|
||||||
|
"enjoy20OffMarriott": "Disfruta de un 20% de descuento en hoteles icónicos de Marriott - Exclusivo con CityCards",
|
||||||
|
"@makeEveryStayUnforgettable": {},
|
||||||
|
"makeEveryStayUnforgettable": "Haz que cada estancia sea tan inolvidable como la ciudad que estás explorando.",
|
||||||
|
"@bookNow": {},
|
||||||
|
"bookNow": "Reserva ahora",
|
||||||
|
"@yourCityCardsUnlocksMore": {},
|
||||||
|
"yourCityCardsUnlocksMore": "Tu CityCard desbloquea más que solo atracciones: también abre las puertas a estancias excepcionales.",
|
||||||
|
"@thanksToOurExclusivePartnership": {},
|
||||||
|
"thanksToOurExclusivePartnership": "Gracias a nuestra asociación exclusiva con ",
|
||||||
|
"@marriottHotelsKeyword": {},
|
||||||
|
"marriottHotelsKeyword": "Marriott Hotels",
|
||||||
|
"@cityCardsHoldersEnjoy": {},
|
||||||
|
"cityCardsHoldersEnjoy": ", los titulares de CityCards disfrutan de un ",
|
||||||
|
"@twentyPercentOffRates": {},
|
||||||
|
"twentyPercentOffRates": "20% de descuento en las mejores tarifas disponibles",
|
||||||
|
"@acrossCuratedProperties": {},
|
||||||
|
"acrossCuratedProperties": " en una cuidada selección de propiedades en la ciudad.",
|
||||||
|
"@chooseFromAKeyword": {},
|
||||||
|
"chooseFromAKeyword": "Elige entre una ",
|
||||||
|
"@wideVarietyBreak": {},
|
||||||
|
"wideVarietyBreak": "Gran \nVariedad",
|
||||||
|
"@chooseFromAWideVarietyDesc": {},
|
||||||
|
"chooseFromAWideVarietyDesc": "Elige entre una gran variedad de hoteles Marriott, desde elegantes refugios urbanos y ubicaciones premium en el centro de la ciudad hasta lujosas experiencias de cinco estrellas, todo diseñado para que tu viaje sea ",
|
||||||
|
"@effortlessComfortable": {},
|
||||||
|
"effortlessComfortable": "sencillo, cómodo",
|
||||||
|
"@andMiddle": {},
|
||||||
|
"andMiddle": " y ",
|
||||||
|
"@memorableKeyword": {},
|
||||||
|
"memorableKeyword": "memorable",
|
||||||
|
"@simplyUseYourCityCards": {},
|
||||||
|
"simplyUseYourCityCards": "Simplemente usa tu ",
|
||||||
|
"@bookingLinkTo": {},
|
||||||
|
"bookingLinkTo": " enlace de reserva de CityCards para:",
|
||||||
|
"@access20OffRates": {},
|
||||||
|
"access20OffRates": "Accede a un 20% de descuento en las mejores tarifas disponibles",
|
||||||
|
"@saveOnYourStay": {},
|
||||||
|
"saveOnYourStay": "Ahorra en tu estancia en propiedades premium de Marriott",
|
||||||
|
"@enjoyPriorityCheckIn": {},
|
||||||
|
"enjoyPriorityCheckIn": "Disfruta de check-in prioritario y late checkout",
|
||||||
|
"@subjectToAvailability": {},
|
||||||
|
"subjectToAvailability": "Sujeto a disponibilidad para tu comodidad",
|
||||||
|
"@receiveExclusiveSeasonalOffers": {},
|
||||||
|
"receiveExclusiveSeasonalOffers": "Recibe ofertas de temporada exclusivas",
|
||||||
|
"@designedSpeciallyForCityCards": {},
|
||||||
|
"designedSpeciallyForCityCards": "Diseñado especialmente para los viajeros de CityCards",
|
||||||
|
"@itsJustOneMoreWay": {},
|
||||||
|
"itsJustOneMoreWay": "Es solo una forma más en la que",
|
||||||
|
"@makesExploring": {},
|
||||||
|
"makesExploring": " hace que explorar sea",
|
||||||
|
"@simplerKeyword": {},
|
||||||
|
"simplerKeyword": " más sencillo",
|
||||||
|
"@moreRewarding": {},
|
||||||
|
"moreRewarding": " más gratificante",
|
||||||
|
"@getYourCityCardsToday": {},
|
||||||
|
"getYourCityCardsToday": "Obtén tu CityCard hoy mismo",
|
||||||
|
"shareSubject": "Mira esto",
|
||||||
|
"scanAtAttractionSite": "Escanea esto en el sitio de la atracción",
|
||||||
|
"codeCopied": "Código copiado al portapapeles",
|
||||||
|
"checkedIn": "Registrado",
|
||||||
|
"checkIn": "Registrarse",
|
||||||
|
"problemsRedeemingPass": "¿Tienes problemas para canjear el pase? ",
|
||||||
|
"clickHere": "Haz clic aquí",
|
||||||
|
"about": "Acerca de",
|
||||||
|
"howToMakeBooking": "¿Cómo hacer una reserva?",
|
||||||
|
"contactNumber": "Número de contacto",
|
||||||
|
"tapToCall": "Toca para llamar",
|
||||||
|
"email": "Correo electrónico",
|
||||||
|
"tapToEmail": "Toca para enviar un correo",
|
||||||
|
"datesNotAvailable": "Fechas no disponibles",
|
||||||
|
"viaCityCards": "Vía CityCards",
|
||||||
|
"createBookingViaApp": "Crear una reserva vía la aplicación",
|
||||||
|
"whatIsIncluded": "Qué está incluido",
|
||||||
|
"exactLocation": "Ubicación exacta",
|
||||||
|
"viewLocationOnMap": "Ver la ubicación en el mapa",
|
||||||
|
"peopleFrequentlyAsk": "La gente pregunta con frecuencia",
|
||||||
|
"errorMessage": "Error: {message}",
|
||||||
|
"suggestedAttractions": "Atracciones sugeridas",
|
||||||
|
"viewAllAttractions": "Ver todas las atracciones",
|
||||||
|
"recommendedOffers": "Ofertas recomendadas",
|
||||||
|
"viewAllOffers": "Ver todas las ofertas",
|
||||||
|
"learnAboutPolicies": "Más información sobre las políticas",
|
||||||
|
"pricePerPerson": "{price}$/persona",
|
||||||
|
"priceNotAvailable": "Precio no disponible",
|
||||||
|
"bookingRequired": "Reserva obligatoria",
|
||||||
|
"adultsLabel": "Adultos",
|
||||||
|
"kidsLabel": "Niños",
|
||||||
|
"checkedInSuccessful": "Registro exitoso",
|
||||||
|
"readyToCheckIn": "¿Listo para registrarte?",
|
||||||
|
"entranceActivationNote": "Actívalo solo cuando estés en la entrada de ",
|
||||||
|
"minuteTimer": "Temporizador de {minutes} minutos",
|
||||||
|
"timerActivationWarning": "Una vez activado, el pase es válido por {minutes} minutos. Esta acción no se puede deshacer",
|
||||||
|
"activatePassNow": "Activar pase ahora",
|
||||||
|
"notAtEntranceYet": "Aún no estoy en la entrada",
|
||||||
|
"howToRedeemTitle": "¿Cómo canjeo mi pase de atracción?",
|
||||||
|
"howToRedeemDescription": "Para canjear su pase de atracción, presente el código QR en la entrada. Nuestro personal lo escaneará, otorgándole acceso a las maravillas del interior. Disfrute de su aventura en ",
|
||||||
|
"contactSupport": "Contactar con soporte",
|
||||||
|
"passAttractionsTitle": "Atracciones del pase",
|
||||||
|
"searchAttractionsHint": "Buscar atracciones...",
|
||||||
|
"noAttractionsFound": "No se encontraron atracciones",
|
||||||
|
"noAttractionsMatchSearch": "No hay atracciones que coincidan con su búsqueda",
|
||||||
|
"selectiveCardFilter": "Tarjeta Selectiva",
|
||||||
|
"offersWithCardTitle": "Ofertas con {cardName}",
|
||||||
|
"searchOffersHint": "Buscar ofertas",
|
||||||
|
"noOffersFound": "No se encontraron ofertas",
|
||||||
|
"codeCopiedMessage": "Código copiado: {code}",
|
||||||
|
"noDataAvailable": "No hay datos disponibles",
|
||||||
|
"userPossessive": "De {firstName}",
|
||||||
|
"tripDetails": "DETALLES DEL VIAJE:",
|
||||||
|
"daysCount": "{count} Días",
|
||||||
|
"stopsCount": "{count} paradas",
|
||||||
|
"adultsCount": "{count} adultos",
|
||||||
|
"kidsCount": "{count} niños",
|
||||||
|
"shareLabel": "Compartir",
|
||||||
|
"preparingPdfToShare": "Preparando PDF para compartir...",
|
||||||
|
"myItinerarySubject": "Mi itinerario - {title}",
|
||||||
|
"failedToShare": "Error al compartir: {error}",
|
||||||
|
"downloadLabel": "Descargar",
|
||||||
|
"downloadingLabel": "Descargando...",
|
||||||
|
"downloadingPdf": "Descargando PDF...",
|
||||||
|
"dailyView": "Vista diaria",
|
||||||
|
"summary": "Resumen",
|
||||||
|
"dayNumberLabel": "Día {number}",
|
||||||
|
"pleaseFillAllFields": "Por favor llene todos los campos",
|
||||||
|
"enterValidEmail": "Por favor ingrese un correo electrónico válido",
|
||||||
|
"enterValidPhoneForIsd": "Ingrese un número de teléfono válido para {isdCode}",
|
||||||
|
"failedToSubmitDetails": "Error al enviar los detalles",
|
||||||
|
"addDetailsTitle": "Añadir detalles",
|
||||||
|
"aboutRecipient": "Cuéntanos sobre el destinatario",
|
||||||
|
"firstNameLabelWithStar": "Nombre *",
|
||||||
|
"firstNameHint": "Ingrese el nombre del destinatario",
|
||||||
|
"lastNameLabelWithStar": "Apellido *",
|
||||||
|
"lastNameHint": "Ingrese el apellido del destinatario",
|
||||||
|
"emailLabelWithStar": "Correo electrónico *",
|
||||||
|
"emailHint": "Ingrese el correo electrónico del destinatario",
|
||||||
|
"phoneNumberLabelWithStar": "Número de teléfono *",
|
||||||
|
"phoneNumberHint": "Ingrese el número de teléfono",
|
||||||
|
"searchCountryHint": "Buscar país...",
|
||||||
|
"cityLabelWithStar": "Ciudad *",
|
||||||
|
"cityHint": "Ingrese el nombre de la ciudad",
|
||||||
|
"countryLabelWithStar": "País *",
|
||||||
|
"countryHint": "Ingrese el nombre del país",
|
||||||
|
"submittingLabel": "Enviando...",
|
||||||
|
"yourAttractionTitle": "Tu atracción",
|
||||||
|
"perPersonSuffix": "/persona",
|
||||||
|
"checkThisOut": "Mira esto",
|
||||||
|
"whatIsIncluded": "Qué está incluido",
|
||||||
|
"peopleFrequentlyAsk": "Preguntas frecuentes",
|
||||||
|
"aboutTitle": "Acerca de",
|
||||||
|
"exactLocation": "Ubicación exacta",
|
||||||
|
"viewOnMap": "Ver la ubicación en el mapa",
|
||||||
|
"selectImageSource": "Seleccionar fuente de imagen",
|
||||||
|
"cameraLabel": "Cámara",
|
||||||
|
"galleryLabel": "Galería",
|
||||||
|
"failedToPickImage": "Error al seleccionar la imagen: {error}",
|
||||||
|
"userIdNotFound": "ID de usuario no encontrado",
|
||||||
|
"changeProfilePicture": "Cambiar foto de perfil",
|
||||||
|
"enterYourFirstName": "Ingrese su nombre",
|
||||||
|
"firstNameRequired": "El nombre es obligatorio",
|
||||||
|
"enterYourLastName": "Ingrese su apellido",
|
||||||
|
"lastNameRequired": "El apellido es obligatorio",
|
||||||
|
"enterYourEmail": "Ingrese su correo electrónico",
|
||||||
|
"enterYourPhoneNumber": "Ingrese su número de teléfono",
|
||||||
|
"addressLabelWithStar": "Dirección *",
|
||||||
|
"addressHint": "Ingrese la dirección manualmente o toque para buscar",
|
||||||
|
"address2Optional": "Dirección 2 (Opcional)",
|
||||||
|
"address2Hint": "Ingrese detalles adicionales de la dirección",
|
||||||
|
"zipCodeLabelWithStar": "Código postal *",
|
||||||
|
"zipCodeHint": "Ingrese el código postal donde reside",
|
||||||
|
"enterYourCity": "Ingrese el nombre de su ciudad",
|
||||||
|
"stateLabelWithStar": "Estado *",
|
||||||
|
"enterYourState": "Ingrese su estado",
|
||||||
|
"enterYourCountry": "Ingrese su país",
|
||||||
|
"editProfileTitle": "Editar perfil",
|
||||||
|
"personalDetails": "Detalles personales",
|
||||||
|
"locationDetails": "Detalles de ubicación",
|
||||||
|
"cancelTitle": "Cancelar",
|
||||||
|
"saveTitle": "Guardar",
|
||||||
|
"contactUsTitle": "Contáctenos",
|
||||||
|
"contactUsMessage": "Puede ponerse en contacto con nosotros a través de las siguientes plataformas. Nuestro equipo se pondrá en contacto con usted en breve",
|
||||||
|
"customerSupport": "Atención al cliente",
|
||||||
|
"contactNumberLabel": "Número de contacto",
|
||||||
|
"tapToCall": "Toca para llamar",
|
||||||
|
"emailLabel": "Correo electrónico",
|
||||||
|
"tapToEmail": "Toca para enviar correo",
|
||||||
|
"locationLabel": "Ubicación",
|
||||||
|
"enterYourEmailAddress": "Ingrese su dirección de correo electrónico",
|
||||||
|
"emailRequired": "El correo electrónico es obligatorio",
|
||||||
|
"enterValidEmail": "Ingrese una dirección de correo electrónico válida",
|
||||||
|
"phoneNumberRequired": "El número de teléfono es obligatorio",
|
||||||
|
"descriptionLabelWithStar": "Descripción *",
|
||||||
|
"writeMessageHint": "Escribe tu mensaje aquí",
|
||||||
|
"submitTicket": "Enviar ticket",
|
||||||
|
"termsAndConditionsTitle": "Términos y condiciones",
|
||||||
|
"privacyPolicyTitle": "Política de privacidad",
|
||||||
|
"faqTitle": "FAQ",
|
||||||
|
"noTermsContent": "No hay contenido de términos y condiciones disponible.",
|
||||||
|
"noPrivacyContent": "No hay contenido de política de privacidad disponible.",
|
||||||
|
"retryLabel": "Reintentar",
|
||||||
|
"errorLabel": "Error",
|
||||||
|
"offersWithCard": "Ofertas con tarjeta {cardName}",
|
||||||
|
"@offersWithCard": {
|
||||||
|
"placeholders": {
|
||||||
|
"cardName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"searchOffersHint": "Buscar ofertas",
|
||||||
|
"noOffersFound": "No se encontraron ofertas",
|
||||||
|
"aboutPartner": "Acerca de {partnerName}",
|
||||||
|
"@aboutPartner": {
|
||||||
|
"placeholders": {
|
||||||
|
"partnerName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howToMakeBooking": "¿Cómo hacer una reserva?",
|
||||||
|
"bookingStep1": "Verifique la fecha de vencimiento de su cupón para asegurarse de que aún sea válido.",
|
||||||
|
"bookingStep2": "Visite la tienda o el sitio web donde se puede canjear el cupón.",
|
||||||
|
"bookingStep3": "Si compra en línea, agregue artículos a su carrito y proceda al pago.",
|
||||||
|
"bookingStep4": "Busque un campo con la etiqueta 'Código de cupón' o 'Código de promoción' durante el pago.",
|
||||||
|
"bookingStep5": "Ingrese su código de cupón exactamente como aparece, incluidos los caracteres especiales.",
|
||||||
|
"couponCodeCopied": "¡Código de cupón copiado!",
|
||||||
|
"createYourAccount": "Crea tu cuenta",
|
||||||
|
"personalInformation": "Información personal",
|
||||||
|
"locationDetailsWithStar": "Detalles de ubicación *",
|
||||||
|
"enterYourAddress": "Ingrese su dirección",
|
||||||
|
"autoFillZipNote": "La ciudad, el estado y el país se completarán automáticamente a partir del código postal",
|
||||||
|
"creatingLabelEllipsis": "Creando...",
|
||||||
|
"createAccount": "Crear cuenta",
|
||||||
|
"getStarted": "Empezar",
|
||||||
|
"enterEmailToBegin": "Ingrese su correo electrónico para comenzar su viaje CityCards",
|
||||||
|
"emailExampleHint": "john.doe@gmail.com",
|
||||||
|
"pleaseEnterEmail": "Por favor ingrese su dirección de correo electrónico",
|
||||||
|
"sendingLabel": "Enviando...",
|
||||||
|
"otpVerifiedSuccess": "¡OTP verificado con éxito!",
|
||||||
|
"completeProfileNote": "Por favor complete su perfil",
|
||||||
|
"otpResentSuccess": "¡OTP reenviado con éxito!",
|
||||||
|
"verifyYourPhone": "Verifica tu teléfono",
|
||||||
|
"enterVerificationCodeNote": "Ingrese el código de verificación enviado a su correo electrónico",
|
||||||
|
"resendingLabel": "Reenviando...",
|
||||||
|
"resendOtpLabel": "Reenviar OTP",
|
||||||
|
"resendOtpWithTimer": "Reenviar OTP ({seconds}s)",
|
||||||
|
"pleaseEnterCompleteOtp": "Por favor ingrese el OTP completo",
|
||||||
|
"verifyingLabel": "Verificando...",
|
||||||
|
"ok": "OK",
|
||||||
|
"uploadAPhoto": "Subir una foto",
|
||||||
|
"orLabel": "O",
|
||||||
|
"takeAPhoto": "Tomar una foto",
|
||||||
|
"uploadAgain": "Subir de nuevo",
|
||||||
|
"nextTitle": "Siguiente",
|
||||||
|
"addImage": "Añadir imagen",
|
||||||
|
"addAFilter": "Añadir un filtro",
|
||||||
|
"chooseFavoriteFilter": "Elige tu filtro favorito y mejora tu postal.",
|
||||||
|
"filterOriginal": "Original",
|
||||||
|
"filterBW": "Blanco y negro",
|
||||||
|
"filterSepia": "Sepia",
|
||||||
|
"filterVintage": "Vintage",
|
||||||
|
"filterCoolTone": "Tono frío",
|
||||||
|
"filterContrast": "Contraste",
|
||||||
|
"filterSoftGlow": "Resplandor suave",
|
||||||
|
"writeYourMessage": "Escribe tu mensaje",
|
||||||
|
"failedToFetchEditDetails": "Error al recuperar los detalles de la edición",
|
||||||
|
"saveChanges": "Guardar cambios",
|
||||||
|
"writeAMessage": "Escribe un mensaje",
|
||||||
|
"addYourMessageHere": "Añade tu mensaje aquí",
|
||||||
|
"fontDefault": "Predeterminado",
|
||||||
|
"previewYourPostcard": "Vista previa de tu postal",
|
||||||
|
"flipLabel": "Voltear",
|
||||||
|
"buyPostcardForMyself": "Comprar postal para mí",
|
||||||
|
"editDetailsLabel": "Editar detalles",
|
||||||
|
"giftPostcardForSomeoneElse": "Regalar la postal a otra persona",
|
||||||
|
"addTitleLabel": "Añadir título",
|
||||||
|
"enterTitleHint": "Ingresar título",
|
||||||
|
"pleaseEnterTitle": "Por favor ingrese un título",
|
||||||
|
"yourDetailsTitle": "Tus detalles",
|
||||||
|
"senderDetailsDescription": "Ingresa tus datos como remitente de esta postal",
|
||||||
|
"fullNameLabel": "Nombre completo",
|
||||||
|
"enterFullNameHint": "Ingresa el nombre completo",
|
||||||
|
"cityLabel": "Ciudad",
|
||||||
|
"enterCityHint": "Ingresa el nombre de tu ciudad",
|
||||||
|
"countryLabel": "País",
|
||||||
|
"enterCountryHint": "Ingresa tu país",
|
||||||
|
"recipientDetailsTitle": "Detalles del destinatario",
|
||||||
|
"recipientDetailsDescription": "Ingresa la dirección de la persona que recibirá esta postal",
|
||||||
|
"selfDetailsDescription": "Ingresa tus datos de contacto para esta postal.",
|
||||||
|
"recipientNameLabel": "Nombre del destinatario",
|
||||||
|
"enterRecipientNameHint": "Ingresa el nombre del destinatario",
|
||||||
|
"addressLabel": "Dirección",
|
||||||
|
"enterRecipientAddressHint": "Ingresa la dirección del destinatario",
|
||||||
|
"zipCodeLabel": "Código postal",
|
||||||
|
"enterZipCodeHint": "Ingresa el código postal donde resides",
|
||||||
|
"pleaseEnterZipCode": "Por favor ingrese el código postal",
|
||||||
|
"stateLabel": "Estado/Provincia",
|
||||||
|
"enterStateHint": "Ingresa tu estado",
|
||||||
|
"pleaseEnterField": "Por favor ingrese {fieldName}",
|
||||||
|
"invalidEmailError": "Por favor ingrese una dirección de correo electrónico válida",
|
||||||
|
"onlyNumbersAllowed": "Solo se permiten números",
|
||||||
|
"mobileNumberLengthError": "El número de móvil debe tener {length} dígitos",
|
||||||
|
"onlyLettersAllowed": "Solo se permiten letras",
|
||||||
|
"spacesNotAllowed": "No se permiten espacios",
|
||||||
|
"specialCharsNotAllowed": "No se permiten caracteres especiales",
|
||||||
|
"saveAsDraftLabel": "Guardar como borrador",
|
||||||
|
"deliveryAddressLabel": "Dirección de entrega",
|
||||||
|
"paymentSummaryLabel": "Resumen de pago",
|
||||||
|
"grandTotalLabel": "Total general",
|
||||||
|
"paymentFailedError": "Pago fallido: {error}",
|
||||||
|
"paymentInitializationFailedError": "Error: falló la inicialización del pago",
|
||||||
|
"draftSavedSuccess": "¡Borrador guardado exitosamente!",
|
||||||
|
"payAmountLabel": "Pagar ${amount}",
|
||||||
|
"orderPlacedSuccess": "¡Pedido realizado con éxito!",
|
||||||
|
"orderPlacedDescription": "Tu pedido ha sido realizado. Tu ID de pedido es {orderId}",
|
||||||
|
"deliveryTimelineNote": "Será entregado en 2-3 días hábiles.",
|
||||||
|
"goToMyOrdersLabel": "Ir a Mis Pedidos",
|
||||||
|
"messagePreviewLabel": "Vista previa del mensaje",
|
||||||
|
"postcardLabel": "Postal",
|
||||||
|
"homeTitle": "CityCards.\nVer más,\nGastar menos.",
|
||||||
|
"homeSubtitle": "Acceso QR instantáneo a más de 40 atracciones,\nbeneficios exclusivos y ahorros de hasta el 30%",
|
||||||
|
"getYourCityCards": "Obtén tus CityCards",
|
||||||
|
"exploreCitiesTitleExplore": "Explorar ",
|
||||||
|
"exploreCitiesTitleCities": "Ciudades",
|
||||||
|
"upcomingCitiesTitleUpcoming": "Próximas ",
|
||||||
|
"upcomingCitiesTitleCities": "Ciudades",
|
||||||
|
"exploreDescription": "Explora tu destino de ensueño y experimenta diversas atracciones.",
|
||||||
|
"noCitiesAvailable": "No hay ciudades disponibles",
|
||||||
|
"notAvailableLabel": "N/A",
|
||||||
|
"editPostcardTitle": "Editar postal",
|
||||||
|
"uploadImageTitle": "Subir imagen",
|
||||||
|
"editPostcardUploadDescription": "Edita tus propias postales únicas subiendo imágenes que capturen tus momentos inolvidables.",
|
||||||
|
"editFiltersLabel": "Editar filtros",
|
||||||
|
"editTitleLabel": "Editar título ",
|
||||||
|
"editTitleDescription": "Dale otro título a tu postal",
|
||||||
|
"titleMaxLengthError": "El título puede tener un máximo de 10 letras",
|
||||||
|
"editMessageLabel": "Editar mensaje ",
|
||||||
|
"editMessageDescription": "Edita tus propias postales únicas para atesorar tus momentos inolvidables.",
|
||||||
|
"fullNameLabelWithStar": "Nombre completo *",
|
||||||
|
"recipientLabelWithStar": "Destinatario *",
|
||||||
|
"pleaseEnterMessage": "Por favor ingrese un mensaje",
|
||||||
|
"messageMaxLengthError": "El mensaje puede tener un máximo de 400 caracteres",
|
||||||
|
"fontPatrickHand": "Patrick Hand",
|
||||||
|
"fontIndieFlower": "Indie Flower",
|
||||||
|
"fontGloriaHallelujah": "Gloria Hallelujah"
|
||||||
|
}
|
||||||
671
lib/l10n/app_fr.arb
Normal file
@@ -0,0 +1,671 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "fr",
|
||||||
|
"profileTitle": "Mon profil",
|
||||||
|
"supportAndLegal": "Support & Juridique",
|
||||||
|
"contactUs": "Contactez-nous",
|
||||||
|
"termsAndConditions": "Conditions générales",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"privacyPolicy": "Politique de confidentialité",
|
||||||
|
"logout": "Se déconnecter",
|
||||||
|
"signIn": "Se connecter",
|
||||||
|
"heyStranger": "Salut, étranger ! 👋",
|
||||||
|
"guestSubtitle1": "Nous sommes ravis de vous avoir sur notre application.",
|
||||||
|
"guestSubtitle2": "Pourquoi ne pas le rendre officiel ?",
|
||||||
|
"accountSettings": "Paramètres du compte",
|
||||||
|
"editProfile": "Modifier le profil",
|
||||||
|
"changeLanguage": "Changer de langue",
|
||||||
|
"retry": "Réessayer",
|
||||||
|
"failedToLoadProfile": "Échec du chargement du profil",
|
||||||
|
"changeLanguageTitle": "Changer de langue",
|
||||||
|
"searchLanguages": "Rechercher des langues",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"oopsSomethingWentWrong": "Oups ! Quelque chose s'est mal passé",
|
||||||
|
"tryAgain": "Réessayer",
|
||||||
|
"defaultCityName": "Nom de la ville",
|
||||||
|
"defaultCityDescription": "Description de la ville",
|
||||||
|
"noHighlightsAvailable": "Aucun point fort disponible",
|
||||||
|
"popular": "Populaires ",
|
||||||
|
"attractions": "Attractions",
|
||||||
|
"viewAll": "Voir tout",
|
||||||
|
"createMyMagicItinerary": "Créer mon itinéraire magique",
|
||||||
|
"claimOffersWithCityCards": "Réclamez des offres avec vos City Cards",
|
||||||
|
"offerSubtitleDummy": "Lorem ipsum dolor sit amet...",
|
||||||
|
"noAttractionsAvailable": "Aucune attraction disponible",
|
||||||
|
"explore": "Explorer ",
|
||||||
|
"moreCities": "Plus de villes.",
|
||||||
|
"stay": "Restez ",
|
||||||
|
"connected": "Connecté",
|
||||||
|
"everywhere": "Partout",
|
||||||
|
"claimEsimOffers": "Réclamez des offres e-Sim avec\nCityCard",
|
||||||
|
"chooseYourCard": "Choisissez votre carte",
|
||||||
|
"diveIntoExtensiveSelection": "Plongez dans une vaste sélection...",
|
||||||
|
"from": "À partir de ",
|
||||||
|
"getACard": "Obtenir une carte",
|
||||||
|
"marriott": "MARRIOTT ",
|
||||||
|
"moments": "MOMENTS",
|
||||||
|
"citycard": "CITYCARD ",
|
||||||
|
"prices": "PRIX",
|
||||||
|
"premium": "Séjours ",
|
||||||
|
"stays": "Premium",
|
||||||
|
"citycardPrices": "Prix CityCard",
|
||||||
|
"claimHotelDiscountOffers": "Réclamez des réductions d'hôtel\navec CityCard",
|
||||||
|
"back": "Retour",
|
||||||
|
"selectACity": "Sélectionnez une ville",
|
||||||
|
"searchCities": "Rechercher des villes",
|
||||||
|
"errorLoadingCities": "Erreur de chargement des villes",
|
||||||
|
"noCitiesFound": "Aucune ville trouvée",
|
||||||
|
"individualTickets": "Billets individuels:",
|
||||||
|
"cityCardLabel": "City Card :",
|
||||||
|
"getYourCard": "Obtenez votre carte",
|
||||||
|
"fromLabel": "De",
|
||||||
|
"perAdult": " /Adulte",
|
||||||
|
"planYour": "Planifiez votre ",
|
||||||
|
"dreamJourney": "Voyage de Rêve",
|
||||||
|
"inJust3Seconds": "\nen seulement 3 secondes",
|
||||||
|
"navExplore": "Explorer",
|
||||||
|
"navMagicItinerary": "Itinéraire Magique",
|
||||||
|
"navMyCards": "Mes Cartes",
|
||||||
|
"navPostcard": "Carte Postale",
|
||||||
|
"planYourNextAdventure": "Planifiez votre prochaine aventure",
|
||||||
|
"createMyItinerary": "Créer Mon Itinéraire",
|
||||||
|
"notLoggedInYet": "Vous n'êtes pas encore connecté !",
|
||||||
|
"loginOrPurchasePass": "Connectez-vous ou achetez un pass pour débloquer l'itinéraire magique !",
|
||||||
|
"logInLabel": "Connexion",
|
||||||
|
"noUnlimitedPass": "Vous ne possédez pas de Pass Illimité ! ὡ4",
|
||||||
|
"getUnlimitedPass": "Obtenez votre Pass Illimité et créez un itinéraire personnalisé !",
|
||||||
|
"buyUnlimitedCityCard": "Acheter une CityCard Illimitée",
|
||||||
|
"noItineraryYet": "Vous n'avez pas d'Itinéraire pour le moment ! ὡF",
|
||||||
|
"createPersonalizedItinerary": "Créez votre itinéraire magique personnalisé qui répond à vos besoins de voyage",
|
||||||
|
"travelPlan": " Plan de Voyage",
|
||||||
|
"activeLabel": "Actif",
|
||||||
|
"inactiveLabel": "Inactif",
|
||||||
|
"daysLabel": " jours",
|
||||||
|
"attractionsCountLabel": " attractions",
|
||||||
|
"createdOnLabel": "Créé le ",
|
||||||
|
"viewItineraryLabel": "Voir l'Itinéraire",
|
||||||
|
"allFilter": "Tout",
|
||||||
|
"latestFilter": "Plus récent",
|
||||||
|
"oldestFilter": "Plus ancien",
|
||||||
|
"flexiCardFilter": "Carte Flexi",
|
||||||
|
"unlimitedCardFilter": "Carte Illimitée",
|
||||||
|
"sortByLabel": "Trier par",
|
||||||
|
"pleaseLogInToViewPasses": "Veuillez vous connecter pour voir vos pass",
|
||||||
|
"logInAccessPasses": "Connectez-vous pour accéder à vos pass, offres exclusives et réductions.",
|
||||||
|
"noPassYet": "Vous n'avez pas encore de Pass ! 😕",
|
||||||
|
"getAPassUnlockOffers": "Obtenez un pass et débloquez des offres exclusives, des réductions et plus.",
|
||||||
|
"buyAPass": "Acheter un Pass",
|
||||||
|
"attractionSingular": "Attraction",
|
||||||
|
"attractionsPlural": "Attractions",
|
||||||
|
"daySingular": "Jour",
|
||||||
|
"daysPlural": "Jours",
|
||||||
|
"cardSuffixLabel": " Carte",
|
||||||
|
"adultsPrefix": "Adultes-",
|
||||||
|
"kidsPrefix": " • Enfants-",
|
||||||
|
"validTillPrefix": "Valable jusqu'au : ",
|
||||||
|
"noPostcardOrdersYet": "On dirait que vous n'avez pas encore commandé\nde cartes postales !",
|
||||||
|
"whipUpFunPostcardOrders": "Que diriez-vous de préparer une carte postale amusante à envoyer à vos proches ? Commençons !",
|
||||||
|
"searchOrdersHint": "Rechercher des commandes...",
|
||||||
|
"foundPrefix": "Trouvé ",
|
||||||
|
"ofMiddle": " sur ",
|
||||||
|
"ordersSuffix": " commandes",
|
||||||
|
"noOrdersFound": "Aucune commande trouvée",
|
||||||
|
"tryAdjustingSearchQuery": "Essayez d'ajuster votre requête de recherche",
|
||||||
|
"errorLoadingOrders": "Erreur lors du chargement des commandes",
|
||||||
|
"retryButtonLabel": "Réessayer",
|
||||||
|
"statusLabel": "Statut :",
|
||||||
|
"previewButtonLabel": "Aperçu",
|
||||||
|
"noPostcardDraftsYet": "On dirait que vous n'avez encore\ncréé aucune carte postale !",
|
||||||
|
"whipUpFunPostcardDrafts": "Pourquoi ne pas préparer une carte postale et l'envoyer à quelqu'un de spécial qui est loin ?",
|
||||||
|
"searchDraftsHint": "Rechercher des brouillons...",
|
||||||
|
"draftsSuffix": " brouillons",
|
||||||
|
"noSearchAvailable": "Aucune recherche disponible",
|
||||||
|
"tryDifferentKeywords": "Essayez de rechercher avec d'autres mots-clés",
|
||||||
|
"errorLoadingDrafts": "Erreur lors du chargement des brouillons",
|
||||||
|
"deleteButtonLabel": "Supprimer",
|
||||||
|
"editButtonLabel": "Modifier",
|
||||||
|
"sendButtonLabel": "Envoyer",
|
||||||
|
"myDraftsTab": "Mes brouillons",
|
||||||
|
"myOrdersTab": "Mes commandes",
|
||||||
|
"createPostcardButton": "Créer une carte postale",
|
||||||
|
"makeMostOfTrip": "Profitez au maximum de votre voyage",
|
||||||
|
"designUniquePostcards": "Concevez vos propres cartes postales uniques pour\nchérir vos moments inoubliables.",
|
||||||
|
"letsCreateButton": "Créons ",
|
||||||
|
"notLoggedInPostCards": "Vous n'êtes pas encore connecté !",
|
||||||
|
"loginToDesignPostcards": "Pour concevoir vos propres cartes postales uniques, connectez-vous\net achetez un pass illimité",
|
||||||
|
"errorLoadingDataTitle": "Erreur de chargement des données",
|
||||||
|
"buyACardTitle": "Acheter une carte",
|
||||||
|
"memberPrivilegesTitle": "Privilèges de membre",
|
||||||
|
"noOffersAvailable": "Aucune offre disponible",
|
||||||
|
"availableAttractionsTitle": "Attractions disponibles",
|
||||||
|
"featureAccessToAttractions": "Accès aux attractions",
|
||||||
|
"featureEntryToAttractions": "Entrée aux attractions",
|
||||||
|
"featureAccessToExperiences": "Accès aux expériences",
|
||||||
|
"featureEntryToSites": "Entrée aux sites",
|
||||||
|
"featureAccessToVenues": "Accès aux lieux",
|
||||||
|
"featureEntryToEvents": "Entrée aux événements",
|
||||||
|
"featureAccessToItineraryCreation": "Accès à la création d'itinéraires",
|
||||||
|
"featureAccessToPostcardCreation": "Accès à la création de cartes postales",
|
||||||
|
"featuresTitle": "Caractéristiques",
|
||||||
|
"unlimitedTitle": "Illimité",
|
||||||
|
"fromPrefix": "À partir de ",
|
||||||
|
"perAdultSuffix": " /Adulte",
|
||||||
|
"andPrefix": "et ",
|
||||||
|
"perChildSuffix": " /enfant",
|
||||||
|
"diveIntoSelection": "Plongez dans une vaste sélection de destinations passionnantes !",
|
||||||
|
"noOfAdultsLabel": "Nb. d'adultes",
|
||||||
|
"noOfChildrenLabel": "Nb. d'enfants",
|
||||||
|
"noOfDaysLabel": "Nb. de jours",
|
||||||
|
"noOfAttractionsLabel": "Nb. d'attractions",
|
||||||
|
"youPayLabel": "Vous payez",
|
||||||
|
"pleaseWaitLabel": "Veuillez patienter...",
|
||||||
|
"proceedToPayLabel": "Procéder au paiement",
|
||||||
|
"atLeastOneAdultRequired": "Au moins 1 adulte est requis",
|
||||||
|
"cannotGoBelowZero": "Ne peut pas être inférieur à 0",
|
||||||
|
"noCheckoutDataAvailable": "Aucune donnée de paiement disponible",
|
||||||
|
"goBackButtonLabel": "Retourner",
|
||||||
|
"completePaymentTitle": "Completar paiement",
|
||||||
|
"processingPaymentMessage": "Traitement du paiement de votre pass...",
|
||||||
|
"paymentSuccessfulMessage": "Paiement réussi !\nVotre pass est prêt.",
|
||||||
|
"paymentFailedMessage": "Échec du paiement",
|
||||||
|
"paymentCancelledMessage": "Paiement annulé",
|
||||||
|
"paymentConfirmedMessage": "Paiement confirmé avec succès !",
|
||||||
|
"checkoutTitle": "Caisse",
|
||||||
|
"adultLabelSingular": "adulte",
|
||||||
|
"adultLabelPlural": "adultes",
|
||||||
|
"kidLabelSingular": "Enfant",
|
||||||
|
"kidLabelPlural": "Enfants",
|
||||||
|
"loadingCoupons": "Chargement des coupons...",
|
||||||
|
"errorLoadingCoupons": "Erreur lors du chargement des coupons",
|
||||||
|
"couponAppliedPrefix": "Coupon appliqué : ",
|
||||||
|
"discountOnPrefix": " remise sur ",
|
||||||
|
"noCouponsAvailable": "Aucun coupon disponible",
|
||||||
|
"viewAllCoupons": "Voir tous les coupons",
|
||||||
|
"pleaseLoginToApplyCoupon": "Veuillez vous connecter pour appliquer le coupon",
|
||||||
|
"applyingLabel": "Application...",
|
||||||
|
"removeLabel": "Retirer",
|
||||||
|
"applyLabel": "Appliquer",
|
||||||
|
"subtotalLabel": "Sous-total",
|
||||||
|
"discountLabel": "Remise",
|
||||||
|
"totalLabel": "Total",
|
||||||
|
"includingTaxesPrefix": "Dont ",
|
||||||
|
"inTaxesSuffix": " de taxes",
|
||||||
|
"allCouponsTitle": "Tous les coupons",
|
||||||
|
"applyCouponLabel": "Appliquer le coupon",
|
||||||
|
"purchaseDetailsTitle": "Détails de l'achat",
|
||||||
|
"buyPassForMyselfTitle": "Acheter un pass pour moi",
|
||||||
|
"editDetailsLabel": "Modifier les détails",
|
||||||
|
"giftThePassTitle": "Offrir le pass",
|
||||||
|
"giftThePassDescription": "Offrir le pass à quelqu'un",
|
||||||
|
"proceedLabel": "Procéder",
|
||||||
|
"processingLabel": "Traitement...",
|
||||||
|
"payPrefixLabel": "Payer ",
|
||||||
|
"loginToCheckoutLabel": "Connectez-vous pour payer",
|
||||||
|
"yourCartTitle": "Votre panier",
|
||||||
|
"myCardsTab": "Mes cartes",
|
||||||
|
"myPostCardsTab": "Mes cartes postales",
|
||||||
|
"yourCartIsEmpty": "Votre panier est vide",
|
||||||
|
"attractionsLabelSuffix": " Attractions",
|
||||||
|
"daysLabelSuffix": " Jours",
|
||||||
|
"youDoNotHaveAnyPasses": "Vous n'avez aucun pass",
|
||||||
|
"emptyPassesDescription": "Obtenez un pass et profitez d'offres, de réductions et plus encore lors de votre voyage dans votre ville préférée",
|
||||||
|
"buyAPassLabel": "Acheter un Pass",
|
||||||
|
"errorLoadingCart": "Erreur lors du chargement du panier",
|
||||||
|
"purchaseOnePostcardAtTime": "Vous pouvez acheter une carte postale à la fois",
|
||||||
|
"proceedToCheckoutLabel": "Procéder au paiement",
|
||||||
|
"loginToAccessPostcardsCart": "Pour accéder à votre panier de cartes postales, veuillez vous connecter",
|
||||||
|
"youDoNotHaveAnyPostcards": "Vous n'avez pas de cartes postales",
|
||||||
|
"emptyPostcardsDescription": "Vous ne possédez encore aucune carte postale et n'en avez envoyé à personne",
|
||||||
|
"designMyPostcardLabel": "Concevoir ma carte postale",
|
||||||
|
"somethingWentWrong": "Un problème est survenu",
|
||||||
|
"retryLabel": "Réessayer",
|
||||||
|
"editDraftLabel": "Modifier le brouillon",
|
||||||
|
"backTitle": "Retour",
|
||||||
|
"magicItineraryTitle": "Itinéraire Magique ✨",
|
||||||
|
"stepPrefix": "Étape ",
|
||||||
|
"ofSuffix": " sur ",
|
||||||
|
"createYourLabel": "Créez votre",
|
||||||
|
"magicItinerarySubtitle": " itinéraire magique",
|
||||||
|
"itineraryCreationStartDescription": "Salut ! Répondez juste à quelques questions amusantes et nous vous préparerons un voyage totalement adapté à vous ! ✈️✨",
|
||||||
|
"letsExploreTogether": "Explorons ensemble !",
|
||||||
|
"takesOnlyTwoMinutes": "Prend seulement 2 minutes ⏱️",
|
||||||
|
"gatheringPreferencesLabel": "Collecte de vos préférences...",
|
||||||
|
"findingBestSpotsLabel": "Recherche des meilleurs endroits...",
|
||||||
|
"buildingScheduleLabel": "Création de votre programme...",
|
||||||
|
"almostThereLabel": "Presque là...",
|
||||||
|
"yourLabel": "Votre ",
|
||||||
|
"magicItineraryLabel": "Itinéraire Magique",
|
||||||
|
"isLabel": " est ",
|
||||||
|
"readyLabel": "Prêt ",
|
||||||
|
"weHaveGotEverythingWeNeed": "Nous avons tout ce qu'il faut pour planifier votre voyage parfait",
|
||||||
|
"startOverLabel": "Recommencer",
|
||||||
|
"getMyTripPlan": "Obtenir mon plan de voyage",
|
||||||
|
"creatingLabel": "Création\n",
|
||||||
|
"yourItineraryLabel": "Votre Itinéraire",
|
||||||
|
"travelingWithKidsLabel": "Voyage avec\n enfants",
|
||||||
|
"noKidsWithMeLabel": "Pas d'enfants",
|
||||||
|
"areYouTravellingWithKidsLabel": "Voyagez-vous avec des enfants ?",
|
||||||
|
"noRestrictionsLabel": "Aucune restriction",
|
||||||
|
"vegetarianLabel": "Végétarien",
|
||||||
|
"veganLabel": "Vététalien",
|
||||||
|
"pescatarianLabel": "Pescétarien",
|
||||||
|
"halalLabel": "Halal",
|
||||||
|
"kosherLabel": "Casher",
|
||||||
|
"doYouFollowDietaryPreferenceLabel": "Suivez-vous un régime alimentaire particulier ?",
|
||||||
|
"whenAreYouPlanningToVisitLabel": "Salut ! Quand prévoyez-vous de visiter ?",
|
||||||
|
"selectADateLabel": "Sélectionner une date",
|
||||||
|
"continueTitle": "Continuer",
|
||||||
|
"relaxedAndChillLabel": "Détendu et calme",
|
||||||
|
"balancedMixLabel": "Mélange équilibré",
|
||||||
|
"activeAndEnergeticLabel": "Actif et énergique",
|
||||||
|
"fullAdventureLabel": "Aventure totale !",
|
||||||
|
"whatKindOfEnergyLabel": "Quelle énergie recherchez-vous pour ce voyage ?",
|
||||||
|
"notInterestedLabel": "Pas intéressé",
|
||||||
|
"maybeOneOrTwoLabel": "Peut-être un ou deux",
|
||||||
|
"yesSoundsGoodLabel": "Oui, ça a l'air bien !",
|
||||||
|
"absolutelyLoveThemLabel": "J'adore absolument !",
|
||||||
|
"doYouEnjoyMuseumsLabel": "Aimez-vous visiter les musées et galeries d'art ?",
|
||||||
|
"@esimOfferTitle": {},
|
||||||
|
"esimOfferTitle": "Connectez-vous instantanément avec votre eSIM gratuite",
|
||||||
|
"@esimOfferSubtitle": {},
|
||||||
|
"esimOfferSubtitle": "Chaque grand voyage commence par une connectivité fluide.",
|
||||||
|
"@viewPlans": {},
|
||||||
|
"viewPlans": "Voir les plans",
|
||||||
|
"@withYour": {},
|
||||||
|
"withYour": "Avec votre ",
|
||||||
|
"@esimKeyword": {},
|
||||||
|
"esimKeyword": "eSIM",
|
||||||
|
"@youCanKeyword": {},
|
||||||
|
"youCanKeyword": ", vous pouvez :",
|
||||||
|
"@navigateCity": {},
|
||||||
|
"navigateCity": "Naviguez dans la ville en toute simplicité",
|
||||||
|
"@navigateCityDesc": {},
|
||||||
|
"navigateCityDesc": "Accédez à des cartes et des directions en temps réel partout où vous allez",
|
||||||
|
"@bookRides": {},
|
||||||
|
"bookRides": "Réservez des trajets, accédez à des cartes et trouvez des attractions en temps réel",
|
||||||
|
"@bookRidesDesc": {},
|
||||||
|
"bookRidesDesc": "Restez connecté à tous les services de voyage essentiels",
|
||||||
|
"@sharePhotos": {},
|
||||||
|
"sharePhotos": "Partagez instantanément photos et souvenirs",
|
||||||
|
"@sharePhotosDesc": {},
|
||||||
|
"sharePhotosDesc": "Téléchargez et partagez vos moments de voyage sans attendre",
|
||||||
|
"@stayConnectedFeatures": {},
|
||||||
|
"stayConnectedFeatures": "Restez connecté avec vos amis, votre famille et vos projets de voyage",
|
||||||
|
"@stayConnectedFeaturesDesc": {},
|
||||||
|
"stayConnectedFeaturesDesc": "Ne manquez jamais les mises à jour ou les messages importants pendant votre voyage",
|
||||||
|
"@simpleKeyword": {},
|
||||||
|
"simpleKeyword": "Simple ",
|
||||||
|
"@threeStepProcess": {},
|
||||||
|
"threeStepProcess": "Processus en 3 étapes",
|
||||||
|
"@getConnectedInSeconds": {},
|
||||||
|
"getConnectedInSeconds": "Connectez-vous en quelques secondes",
|
||||||
|
"@receiveQrCode": {},
|
||||||
|
"receiveQrCode": "Recevoir le code QR",
|
||||||
|
"@receiveQrCodeDesc": {},
|
||||||
|
"receiveQrCodeDesc": "Obtenez votre code QR eSIM unique avec votre CityCard",
|
||||||
|
"@scanCode": {},
|
||||||
|
"scanCode": "Scanner le code",
|
||||||
|
"@scanCodeDesc": {},
|
||||||
|
"scanCodeDesc": "Ouvrez l'appareil photo de votre téléphone et scannez le code QR",
|
||||||
|
"@connectedStep": {},
|
||||||
|
"connectedStep": "Connecté",
|
||||||
|
"@connectedStepDesc": {},
|
||||||
|
"connectedStepDesc": "Vous êtes en ligne instantanément - commencez à explorer !",
|
||||||
|
"@itsOneMoreWay": {},
|
||||||
|
"itsOneMoreWay": "C'est une façon de plus",
|
||||||
|
"@cityCardsKeywordSpace": {},
|
||||||
|
"cityCardsKeywordSpace": " CityCards",
|
||||||
|
"@makesYourJourney": {},
|
||||||
|
"makesYourJourney": "rend votre voyage",
|
||||||
|
"@smarterKeywordSpace": {},
|
||||||
|
"smarterKeywordSpace": " plus intelligent",
|
||||||
|
"@andMoreSpace": {},
|
||||||
|
"andMoreSpace": "et plus",
|
||||||
|
"@effortlessSpace": {},
|
||||||
|
"effortlessSpace": " sans effort",
|
||||||
|
"@startYourJourneyToday": {},
|
||||||
|
"startYourJourneyToday": "Commencez votre voyage aujourd'hui",
|
||||||
|
"@enjoy20OffMarriott": {},
|
||||||
|
"enjoy20OffMarriott": "Profitez de -20 % sur les hôtels Marriott emblématiques - exclusivement avec CityCards",
|
||||||
|
"@makeEveryStayUnforgettable": {},
|
||||||
|
"makeEveryStayUnforgettable": "Faites de chaque séjour un moment aussi inoubliable que la ville que vous explorez.",
|
||||||
|
"@bookNow": {},
|
||||||
|
"bookNow": "Réserver maintenant",
|
||||||
|
"@yourCityCardsUnlocksMore": {},
|
||||||
|
"yourCityCardsUnlocksMore": "Votre CityCards débloque plus que de simples attractions — elle ouvre également les portes de séjours exceptionnels.",
|
||||||
|
"@thanksToOurExclusivePartnership": {},
|
||||||
|
"thanksToOurExclusivePartnership": "Grâce à notre partenariat exclusif avec ",
|
||||||
|
"@marriottHotelsKeyword": {},
|
||||||
|
"marriottHotelsKeyword": "Marriott Hotels",
|
||||||
|
"@cityCardsHoldersEnjoy": {},
|
||||||
|
"cityCardsHoldersEnjoy": ", les détenteurs de CityCards bénéficient de ",
|
||||||
|
"@twentyPercentOffRates": {},
|
||||||
|
"twentyPercentOffRates": "-20 % sur les meilleurs tarifs disponibles",
|
||||||
|
"@acrossCuratedProperties": {},
|
||||||
|
"acrossCuratedProperties": " dans une sélection d'établissements de la ville.",
|
||||||
|
"@chooseFromAKeyword": {},
|
||||||
|
"chooseFromAKeyword": "Choisissez parmi une ",
|
||||||
|
"@wideVarietyBreak": {},
|
||||||
|
"wideVarietyBreak": "Grande \nVariété",
|
||||||
|
"@chooseFromAWideVarietyDesc": {},
|
||||||
|
"chooseFromAWideVarietyDesc": "Choisissez parmi une grande variété d'hôtels Marriott — des retraites urbaines élégantes aux emplacements premium en centre-ville, en passant par des expériences luxueuses cinq étoiles — tous conçus pour rendre votre voyage ",
|
||||||
|
"@effortlessComfortable": {},
|
||||||
|
"effortlessComfortable": "facile, confortable",
|
||||||
|
"@andMiddle": {},
|
||||||
|
"andMiddle": " et ",
|
||||||
|
"@memorableKeyword": {},
|
||||||
|
"memorableKeyword": "mémorable",
|
||||||
|
"@simplyUseYourCityCards": {},
|
||||||
|
"simplyUseYourCityCards": "Utilisez simplement votre ",
|
||||||
|
"@bookingLinkTo": {},
|
||||||
|
"bookingLinkTo": " lien de réservation CityCards pour :",
|
||||||
|
"@access20OffRates": {},
|
||||||
|
"access20OffRates": "Accédez à -20 % sur les meilleurs tarifs disponibles",
|
||||||
|
"@saveOnYourStay": {},
|
||||||
|
"saveOnYourStay": "Économisez sur votre séjour dans les établissements Marriott premium",
|
||||||
|
"@enjoyPriorityCheckIn": {},
|
||||||
|
"enjoyPriorityCheckIn": "Profitez d'un enregistrement prioritaire et d'un départ tardif",
|
||||||
|
"@subjectToAvailability": {},
|
||||||
|
"subjectToAvailability": "Sous réserve de disponibilité pour votre confort",
|
||||||
|
"@receiveExclusiveSeasonalOffers": {},
|
||||||
|
"receiveExclusiveSeasonalOffers": "Recevez des offres saisonnières exclusives",
|
||||||
|
"@designedSpeciallyForCityCards": {},
|
||||||
|
"designedSpeciallyForCityCards": "Conçu spécialement pour les voyageurs CityCards",
|
||||||
|
"@itsJustOneMoreWay": {},
|
||||||
|
"itsJustOneMoreWay": "C'est une façon de plus dont",
|
||||||
|
"@makesExploring": {},
|
||||||
|
"makesExploring": " rend l'exploration",
|
||||||
|
"@simplerKeyword": {},
|
||||||
|
"simplerKeyword": " plus simple",
|
||||||
|
"@moreRewarding": {},
|
||||||
|
"moreRewarding": " plus gratifiante",
|
||||||
|
"@getYourCityCardsToday": {},
|
||||||
|
"getYourCityCardsToday": "Obtenez votre CityCards aujourd'hui",
|
||||||
|
"shareSubject": "Regardez ça",
|
||||||
|
"scanAtAttractionSite": "Scannez ceci sur le site de l'attraction",
|
||||||
|
"codeCopied": "Code copié dans le presse-papiers",
|
||||||
|
"checkedIn": "Enregistré",
|
||||||
|
"checkIn": "S'enregistrer",
|
||||||
|
"problemsRedeemingPass": "Vous avez des problèmes pour utiliser le pass ? ",
|
||||||
|
"clickHere": "Cliquez ici",
|
||||||
|
"about": "À propos",
|
||||||
|
"howToMakeBooking": "Comment faire une réservation ?",
|
||||||
|
"contactNumber": "Numéro de contact",
|
||||||
|
"tapToCall": "Appuyez pour appeler",
|
||||||
|
"email": "E-mail",
|
||||||
|
"tapToEmail": "Appuyez pour envoyer un e-mail",
|
||||||
|
"datesNotAvailable": "Dates non disponibles",
|
||||||
|
"viaCityCards": "Via CityCards",
|
||||||
|
"createBookingViaApp": "Créer une réservation via l'application",
|
||||||
|
"whatIsIncluded": "Ce qui est inclus",
|
||||||
|
"exactLocation": "Emplacement exact",
|
||||||
|
"viewLocationOnMap": "Voir l'emplacement sur la carte",
|
||||||
|
"peopleFrequentlyAsk": "Les gens demandent souvent",
|
||||||
|
"errorMessage": "Erreur : {message}",
|
||||||
|
"suggestedAttractions": "Attractions suggérées",
|
||||||
|
"viewAllAttractions": "Voir toutes les attractions",
|
||||||
|
"recommendedOffers": "Offres recommandées",
|
||||||
|
"viewAllOffers": "Voir toutes les offres",
|
||||||
|
"learnAboutPolicies": "En savoir plus sur les politiques",
|
||||||
|
"pricePerPerson": "{price}$/personne",
|
||||||
|
"priceNotAvailable": "Prix non disponible",
|
||||||
|
"bookingRequired": "Réservation obligatoire",
|
||||||
|
"adultsLabel": "Adultes",
|
||||||
|
"kidsLabel": "Enfants",
|
||||||
|
"checkedInSuccessful": "Enregistrement réussi",
|
||||||
|
"readyToCheckIn": "Prêt à s'enregistrer ?",
|
||||||
|
"entranceActivationNote": "N'activez que lorsque vous êtes à l'entrée de ",
|
||||||
|
"minuteTimer": "Minuteur de {minutes} minutes",
|
||||||
|
"timerActivationWarning": "Une fois activé, le pass est valable pendant {minutes} minutes. Cette action est irréversible",
|
||||||
|
"activatePassNow": "Activer le pass maintenant",
|
||||||
|
"notAtEntranceYet": "Je ne suis pas encore à l'entrée",
|
||||||
|
"howToRedeemTitle": "Comment échanger mon pass attraction ?",
|
||||||
|
"howToRedeemDescription": "Pour échanger votre pass attraction, présentez le code QR à l'entrée. Notre personnel le scannera, vous donnant accès aux merveilles à l'intérieur. Profitez de votre aventure à ",
|
||||||
|
"contactSupport": "Contacter le support",
|
||||||
|
"passAttractionsTitle": "Attractions du pass",
|
||||||
|
"searchAttractionsHint": "Rechercher des attractions...",
|
||||||
|
"noAttractionsFound": "Aucune attraction trouvée",
|
||||||
|
"noAttractionsMatchSearch": "Aucune attraction ne correspond à votre recherche",
|
||||||
|
"selectiveCardFilter": "Carte Sélective",
|
||||||
|
"offersWithCardTitle": "Offres avec {cardName}",
|
||||||
|
"searchOffersHint": "Rechercher des offres",
|
||||||
|
"noOffersFound": "Aucune offre trouvée",
|
||||||
|
"codeCopiedMessage": "Code copié : {code}",
|
||||||
|
"noDataAvailable": "Aucune donnée disponible",
|
||||||
|
"userPossessive": "{firstName}",
|
||||||
|
"tripDetails": "DÉTAILS DU VOYAGE :",
|
||||||
|
"daysCount": "{count} Jours",
|
||||||
|
"stopsCount": "{count} arrêts",
|
||||||
|
"adultsCount": "{count} adultes",
|
||||||
|
"kidsCount": "{count} enfants",
|
||||||
|
"shareLabel": "Partager",
|
||||||
|
"preparingPdfToShare": "Préparation du PDF à partager...",
|
||||||
|
"myItinerarySubject": "Mon itinéraire - {title}",
|
||||||
|
"failedToShare": "Échec du partage : {error}",
|
||||||
|
"downloadLabel": "Télécharger",
|
||||||
|
"downloadingLabel": "Téléchargement...",
|
||||||
|
"downloadingPdf": "Téléchargement du PDF...",
|
||||||
|
"dailyView": "Vue par jour",
|
||||||
|
"summary": "Résumé",
|
||||||
|
"dayNumberLabel": "Jour {number}",
|
||||||
|
"pleaseFillAllFields": "Veuillez remplir tous les champs",
|
||||||
|
"enterValidEmail": "Veuillez saisir une adresse e-mail valide",
|
||||||
|
"enterValidPhoneForIsd": "Saisissez un numéro de téléphone valide pour {isdCode}",
|
||||||
|
"failedToSubmitDetails": "Échec de l'envoi des détails",
|
||||||
|
"addDetailsTitle": "Ajouter des détails",
|
||||||
|
"aboutRecipient": "Parlez-nous du destinataire",
|
||||||
|
"firstNameLabelWithStar": "Prénom *",
|
||||||
|
"firstNameHint": "Saisissez le prénom du destinataire",
|
||||||
|
"lastNameLabelWithStar": "Nom *",
|
||||||
|
"lastNameHint": "Saisissez le nom du destinataire",
|
||||||
|
"emailLabelWithStar": "E-mail *",
|
||||||
|
"emailHint": "Saisissez l'adresse e-mail du destinataire",
|
||||||
|
"phoneNumberLabelWithStar": "Numéro de téléphone *",
|
||||||
|
"phoneNumberHint": "Saisissez le numéro de téléphone",
|
||||||
|
"searchCountryHint": "Rechercher un pays...",
|
||||||
|
"cityLabelWithStar": "Ville *",
|
||||||
|
"cityHint": "Saisissez le nom de la ville",
|
||||||
|
"countryLabelWithStar": "Pays *",
|
||||||
|
"countryHint": "Saisissez le nom du pays",
|
||||||
|
"submittingLabel": "Envoi en cours...",
|
||||||
|
"yourAttractionTitle": "Votre attraction",
|
||||||
|
"perPersonSuffix": "/personne",
|
||||||
|
"checkThisOut": "Regardez ça",
|
||||||
|
"whatIsIncluded": "Ce qui est inclus",
|
||||||
|
"peopleFrequentlyAsk": "Les gens demandent souvent",
|
||||||
|
"aboutTitle": "À propos",
|
||||||
|
"exactLocation": "Emplacement exact",
|
||||||
|
"viewOnMap": "Voir l'emplacement sur la carte",
|
||||||
|
"selectImageSource": "Sélectionner la source de l'image",
|
||||||
|
"cameraLabel": "Appareil photo",
|
||||||
|
"galleryLabel": "Galerie",
|
||||||
|
"failedToPickImage": "Échec de la sélection de l'image : {error}",
|
||||||
|
"userIdNotFound": "ID utilisateur non trouvé",
|
||||||
|
"changeProfilePicture": "Changer la photo de profil",
|
||||||
|
"enterYourFirstName": "Saisissez votre prénom",
|
||||||
|
"firstNameRequired": "Le prénom est obligatoire",
|
||||||
|
"enterYourLastName": "Saisissez votre nom",
|
||||||
|
"lastNameRequired": "Le nom est obligatoire",
|
||||||
|
"enterYourEmail": "Saisissez votre e-mail",
|
||||||
|
"enterYourPhoneNumber": "Saisissez votre numéro de téléphone",
|
||||||
|
"addressLabelWithStar": "Adresse *",
|
||||||
|
"addressHint": "Saisissez l'adresse manuellement ou appuyez pour rechercher",
|
||||||
|
"address2Optional": "Adresse 2 (Facultatif)",
|
||||||
|
"address2Hint": "Saisissez des détails d'adresse supplémentaires",
|
||||||
|
"zipCodeLabelWithStar": "Code postal *",
|
||||||
|
"zipCodeHint": "Saisissez le code postal de votre résidence",
|
||||||
|
"enterYourCity": "Saisissez le nom de votre ville",
|
||||||
|
"stateLabelWithStar": "État *",
|
||||||
|
"enterYourState": "Saisissez votre état",
|
||||||
|
"enterYourCountry": "Saisissez votre pays",
|
||||||
|
"editProfileTitle": "Modifier le profil",
|
||||||
|
"personalDetails": "Détails personnels",
|
||||||
|
"locationDetails": "Détails de l'emplacement",
|
||||||
|
"cancelTitle": "Annuler",
|
||||||
|
"saveTitle": "Enregistrer",
|
||||||
|
"contactUsTitle": "Contactez-nous",
|
||||||
|
"contactUsMessage": "Vous pouvez nous contacter via les plateformes ci-dessous. Notre équipe vous contactera sous peu",
|
||||||
|
"customerSupport": "Service client",
|
||||||
|
"contactNumberLabel": "Numéro de contact",
|
||||||
|
"tapToCall": "Appuyez pour appeler",
|
||||||
|
"emailLabel": "E-mail",
|
||||||
|
"tapToEmail": "Appuyez pour envoyer un e-mail",
|
||||||
|
"locationLabel": "Emplacement",
|
||||||
|
"enterYourEmailAddress": "Saisissez votre adresse e-mail",
|
||||||
|
"emailRequired": "L'e-mail est obligatoire",
|
||||||
|
"enterValidEmail": "Saisissez une adresse e-mail valide",
|
||||||
|
"phoneNumberRequired": "Le numéro de téléphone est obligatoire",
|
||||||
|
"descriptionLabelWithStar": "Description *",
|
||||||
|
"writeMessageHint": "Écrivez votre message ici",
|
||||||
|
"submitTicket": "Envoyer le ticket",
|
||||||
|
"termsAndConditionsTitle": "Termes et conditions",
|
||||||
|
"privacyPolicyTitle": "Politique de confidentialité",
|
||||||
|
"faqTitle": "FAQ",
|
||||||
|
"noTermsContent": "Aucun contenu des termes et conditions disponible.",
|
||||||
|
"noPrivacyContent": "Aucun contenu de politique de confidentialité disponible.",
|
||||||
|
"retryLabel": "Réessayer",
|
||||||
|
"errorLabel": "Erreur",
|
||||||
|
"offersWithCard": "Offres avec carte {cardName}",
|
||||||
|
"@offersWithCard": {
|
||||||
|
"placeholders": {
|
||||||
|
"cardName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"searchOffersHint": "Rechercher des offres",
|
||||||
|
"noOffersFound": "Aucune offre trouvée",
|
||||||
|
"aboutPartner": "À propos de {partnerName}",
|
||||||
|
"@aboutPartner": {
|
||||||
|
"placeholders": {
|
||||||
|
"partnerName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howToMakeBooking": "Comment faire une réservation ?",
|
||||||
|
"bookingStep1": "Vérifiez la date d'expiration de votre coupon pour vous assurer qu'il est toujours valide.",
|
||||||
|
"bookingStep2": "Visitez le magasin ou le site Web où le coupon peut être utilisé.",
|
||||||
|
"bookingStep3": "Si vous achetez en ligne, ajoutez des articles à votre panier et passez à la caisse.",
|
||||||
|
"bookingStep4": "Recherchez un champ intitulé 'Code promo' ou 'Code de réduction' lors du paiement.",
|
||||||
|
"bookingStep5": "Saisissez votre code promo exactement tel qu'il apparaît, y compris les caractères spéciaux.",
|
||||||
|
"couponCodeCopied": "Code promo copié !",
|
||||||
|
"createYourAccount": "Créez votre compte",
|
||||||
|
"personalInformation": "Informations personnelles",
|
||||||
|
"locationDetailsWithStar": "Détails de l'emplacement *",
|
||||||
|
"enterYourAddress": "Entrez votre adresse",
|
||||||
|
"autoFillZipNote": "La ville, l'état et le pays seront remplis automatiquement à partir du code postal",
|
||||||
|
"creatingLabelEllipsis": "Création...",
|
||||||
|
"createAccount": "Créer un compte",
|
||||||
|
"getStarted": "Commencer",
|
||||||
|
"enterEmailToBegin": "Entrez votre e-mail pour commencer votre voyage CityCards",
|
||||||
|
"emailExampleHint": "john.doe@gmail.com",
|
||||||
|
"pleaseEnterEmail": "Veuillez entrer votre adresse e-mail",
|
||||||
|
"sendingLabel": "Envoi en cours...",
|
||||||
|
"otpVerifiedSuccess": "OTP vérifié avec succès !",
|
||||||
|
"completeProfileNote": "Veuillez compléter votre profil",
|
||||||
|
"otpResentSuccess": "OTP renvoyé avec succès !",
|
||||||
|
"verifyYourPhone": "Vérifiez votre téléphone",
|
||||||
|
"enterVerificationCodeNote": "Entrez le code de vérification envoyé à votre adresse e-mail",
|
||||||
|
"resendingLabel": "Renvoi en cours...",
|
||||||
|
"resendOtpLabel": "Renvoyer l'OTP",
|
||||||
|
"resendOtpWithTimer": "Renvoyer l'OTP ({seconds}s)",
|
||||||
|
"pleaseEnterCompleteOtp": "Veuillez entrer l'OTP complet",
|
||||||
|
"verifyingLabel": "Vérification...",
|
||||||
|
"ok": "OK",
|
||||||
|
"uploadAPhoto": "Télécharger une photo",
|
||||||
|
"orLabel": "OU",
|
||||||
|
"takeAPhoto": "Prendre une photo",
|
||||||
|
"uploadAgain": "Télécharger à nouveau",
|
||||||
|
"nextTitle": "Suivant",
|
||||||
|
"addImage": "Ajouter une image",
|
||||||
|
"addAFilter": "Ajouter un filtre",
|
||||||
|
"chooseFavoriteFilter": "Choisissez votre filtre préféré et améliorez votre carte postale.",
|
||||||
|
"filterOriginal": "Original",
|
||||||
|
"filterBW": "Noir et blanc",
|
||||||
|
"filterSepia": "Sépia",
|
||||||
|
"filterVintage": "Vintage",
|
||||||
|
"filterCoolTone": "Ton froid",
|
||||||
|
"filterContrast": "Contraste",
|
||||||
|
"filterSoftGlow": "Lueur douce",
|
||||||
|
"writeYourMessage": "Écrivez votre message",
|
||||||
|
"failedToFetchEditDetails": "Échec de la récupération des détails de l'édition",
|
||||||
|
"saveChanges": "Enregistrer les modifications",
|
||||||
|
"writeAMessage": "Écrire un message",
|
||||||
|
"addYourMessageHere": "Ajoutez votre message ici",
|
||||||
|
"fontDefault": "Par défaut",
|
||||||
|
"previewYourPostcard": "Aperçu de votre carte postale",
|
||||||
|
"flipLabel": "Retourner",
|
||||||
|
"buyPostcardForMyself": "Acheter une carte postale pour moi-même",
|
||||||
|
"editDetailsLabel": "Modifier les détails",
|
||||||
|
"giftPostcardForSomeoneElse": "Offrir la carte postale à quelqu'un d'autre",
|
||||||
|
"addTitleLabel": "Ajouter un titre",
|
||||||
|
"enterTitleHint": "Entrer le titre",
|
||||||
|
"pleaseEnterTitle": "Veuillez entrer un titre",
|
||||||
|
"yourDetailsTitle": "Vos coordonnées",
|
||||||
|
"senderDetailsDescription": "Entrez vos coordonnées en tant qu'expéditeur de cette carte postale",
|
||||||
|
"fullNameLabel": "Nom complet",
|
||||||
|
"enterFullNameHint": "Entrez le nom complet",
|
||||||
|
"cityLabel": "Ville",
|
||||||
|
"enterCityHint": "Entrez le nom de votre ville",
|
||||||
|
"countryLabel": "Pays",
|
||||||
|
"enterCountryHint": "Entrez votre pays",
|
||||||
|
"recipientDetailsTitle": "Coordonnées du destinataire",
|
||||||
|
"recipientDetailsDescription": "Entrez l'adresse de la personne qui recevra cette carte postale",
|
||||||
|
"selfDetailsDescription": "Entrez vos coordonnées pour cette carte postale.",
|
||||||
|
"recipientNameLabel": "Nom du destinataire",
|
||||||
|
"enterRecipientNameHint": "Entrez le nom du destinataire",
|
||||||
|
"addressLabel": "Adresse",
|
||||||
|
"enterRecipientAddressHint": "Entrez l'adresse du destinataire",
|
||||||
|
"zipCodeLabel": "Code postal",
|
||||||
|
"enterZipCodeHint": "Entrez le code postal où vous résidez",
|
||||||
|
"pleaseEnterZipCode": "Veuillez entrer le code postal",
|
||||||
|
"stateLabel": "État/Province",
|
||||||
|
"enterStateHint": "Entrer votre état",
|
||||||
|
"pleaseEnterField": "Veuillez entrer {fieldName}",
|
||||||
|
"invalidEmailError": "Veuillez entrer une adresse e-mail valide",
|
||||||
|
"onlyNumbersAllowed": "Seuls les chiffres sont autorisés",
|
||||||
|
"mobileNumberLengthError": "Le numéro de mobile doit comporter {length} chiffres",
|
||||||
|
"onlyLettersAllowed": "Seules les lettres sont autorisées",
|
||||||
|
"spacesNotAllowed": "Les espaces ne sont pas autorisés",
|
||||||
|
"specialCharsNotAllowed": "Les caractères spéciaux ne sont pas autorisés",
|
||||||
|
"saveAsDraftLabel": "Enregistrer comme brouillon",
|
||||||
|
"deliveryAddressLabel": "Adresse de livraison",
|
||||||
|
"paymentSummaryLabel": "Résumé du paiement",
|
||||||
|
"grandTotalLabel": "Total général",
|
||||||
|
"paymentFailedError": "Échec du paiement : {error}",
|
||||||
|
"paymentInitializationFailedError": "Erreur : Échec de l'initialisation du paiement",
|
||||||
|
"draftSavedSuccess": "Brouillon enregistré avec succès !",
|
||||||
|
"payAmountLabel": "Payer ${amount}",
|
||||||
|
"orderPlacedSuccess": "Commande passée avec succès !",
|
||||||
|
"orderPlacedDescription": "Votre commande a été passée. Votre numéro de commande est {orderId}",
|
||||||
|
"deliveryTimelineNote": "Elle sera livrée sous 2 à 3 jours ouvrables.",
|
||||||
|
"goToMyOrdersLabel": "Aller à Mes Commandes",
|
||||||
|
"messagePreviewLabel": "Aperçu du message",
|
||||||
|
"postcardLabel": "Carte postale",
|
||||||
|
"homeTitle": "CityCards.\nVoir plus,\nDépenser moins.",
|
||||||
|
"homeSubtitle": "Accès QR instantané à plus de 40 attractions,\navantages exclusifs et jusqu'à 30% d'économie",
|
||||||
|
"getYourCityCards": "Obtenez vos CityCards",
|
||||||
|
"exploreCitiesTitleExplore": "Explorer ",
|
||||||
|
"exploreCitiesTitleCities": "Villes",
|
||||||
|
"upcomingCitiesTitleUpcoming": "Prochainement ",
|
||||||
|
"upcomingCitiesTitleCities": "Villes",
|
||||||
|
"exploreDescription": "Explorez votre destination de rêve et découvrez diverses attractions.",
|
||||||
|
"noCitiesAvailable": "Aucune ville disponible",
|
||||||
|
"notAvailableLabel": "N/A",
|
||||||
|
"editPostcardTitle": "Modifier la carte postale",
|
||||||
|
"uploadImageTitle": "Charger une image",
|
||||||
|
"editPostcardUploadDescription": "Concevez vos propres cartes postales uniques en téléchargeant des images qui capturent vos moments inoubliables.",
|
||||||
|
"editFiltersLabel": "Modifier les filtres",
|
||||||
|
"editTitleLabel": "Modifier le titre ",
|
||||||
|
"editTitleDescription": "Donnez un autre titre à votre carte postale",
|
||||||
|
"titleMaxLengthError": "Le titre peut comporter au maximum 10 lettres",
|
||||||
|
"editMessageLabel": "Modifier le message ",
|
||||||
|
"editMessageDescription": "Concevez vos propres cartes postales uniques pour chérir vos moments inoubliables.",
|
||||||
|
"fullNameLabelWithStar": "Nom complet *",
|
||||||
|
"recipientLabelWithStar": "Destinataire *",
|
||||||
|
"pleaseEnterMessage": "Veuillez saisir un message",
|
||||||
|
"messageMaxLengthError": "Le message peut comporter au maximum 400 caractères",
|
||||||
|
"fontPatrickHand": "Patrick Hand",
|
||||||
|
"fontIndieFlower": "Indie Flower",
|
||||||
|
"fontGloriaHallelujah": "Gloria Hallelujah"
|
||||||
|
}
|
||||||
671
lib/l10n/app_ja.arb
Normal file
@@ -0,0 +1,671 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "ja",
|
||||||
|
"profileTitle": "マイプロフィール",
|
||||||
|
"supportAndLegal": "サポートと法律",
|
||||||
|
"contactUs": "お問い合わせ",
|
||||||
|
"termsAndConditions": "利用規約",
|
||||||
|
"faq": "よくある質問",
|
||||||
|
"privacyPolicy": "プライバシーポリシー",
|
||||||
|
"logout": "ログアウト",
|
||||||
|
"signIn": "サインイン",
|
||||||
|
"heyStranger": "こんにちは! 👋",
|
||||||
|
"guestSubtitle1": "アプリをご利用いただきありがとうございます。",
|
||||||
|
"guestSubtitle2": "登録してみませんか?",
|
||||||
|
"accountSettings": "アカウント設定",
|
||||||
|
"editProfile": "プロフィール編集",
|
||||||
|
"changeLanguage": "言語を変更",
|
||||||
|
"retry": "再試行",
|
||||||
|
"failedToLoadProfile": "プロフィールの読み込みに失敗しました",
|
||||||
|
"changeLanguageTitle": "言語を変更",
|
||||||
|
"searchLanguages": "言語を検索",
|
||||||
|
"save": "保存",
|
||||||
|
"oopsSomethingWentWrong": "おっと!何かが間違っていました",
|
||||||
|
"tryAgain": "やり直す",
|
||||||
|
"defaultCityName": "都市名",
|
||||||
|
"defaultCityDescription": "都市の説明",
|
||||||
|
"noHighlightsAvailable": "ハイライトはありません",
|
||||||
|
"popular": "人気の ",
|
||||||
|
"attractions": "アトラクション",
|
||||||
|
"viewAll": "すべて見る",
|
||||||
|
"createMyMagicItinerary": "魔法の旅程を作成",
|
||||||
|
"claimOffersWithCityCards": "シティカードでオファーを請求する",
|
||||||
|
"offerSubtitleDummy": "Lorem ipsum dolor sit amet...",
|
||||||
|
"noAttractionsAvailable": "利用可能なアトラクションはありません",
|
||||||
|
"explore": "探検 ",
|
||||||
|
"moreCities": "もっと都市を。",
|
||||||
|
"stay": "滞在 ",
|
||||||
|
"connected": "接続中",
|
||||||
|
"everywhere": "どこでも",
|
||||||
|
"claimEsimOffers": "CityCardでe-Simオファーを\n受け取る",
|
||||||
|
"chooseYourCard": "カードを選ぶ",
|
||||||
|
"diveIntoExtensiveSelection": "完璧な休暇を見つけるために...",
|
||||||
|
"from": "から ",
|
||||||
|
"getACard": "カードを取得",
|
||||||
|
"marriott": "MARRIOTT ",
|
||||||
|
"moments": "MOMENTS",
|
||||||
|
"citycard": "CITYCARD ",
|
||||||
|
"prices": "PRICES",
|
||||||
|
"premium": "プレミアム",
|
||||||
|
"stays": "ステイ",
|
||||||
|
"citycardPrices": "CityCard料金",
|
||||||
|
"claimHotelDiscountOffers": "CityCardでホテルの\n割引オファーを受け取る",
|
||||||
|
"back": "戻る",
|
||||||
|
"selectACity": "都市を選択",
|
||||||
|
"searchCities": "都市を検索",
|
||||||
|
"errorLoadingCities": "都市の読み込みエラー",
|
||||||
|
"noCitiesFound": "都市が見つかりません",
|
||||||
|
"individualTickets": "個別チケット:",
|
||||||
|
"cityCardLabel": "City Card:",
|
||||||
|
"getYourCard": "カードを取得する",
|
||||||
|
"fromLabel": "から",
|
||||||
|
"perAdult": " /大人",
|
||||||
|
"planYour": "あなたの ",
|
||||||
|
"dreamJourney": "夢の旅を計画する",
|
||||||
|
"inJust3Seconds": "\nたった3秒で",
|
||||||
|
"navExplore": "探す",
|
||||||
|
"navMagicItinerary": "魔法の旅程",
|
||||||
|
"navMyCards": "マイカード",
|
||||||
|
"navPostcard": "ポストカード",
|
||||||
|
"planYourNextAdventure": "次の冒険を計画する",
|
||||||
|
"createMyItinerary": "私の旅程を作成する",
|
||||||
|
"notLoggedInYet": "まだログインしていません!",
|
||||||
|
"loginOrPurchasePass": "魔法の旅程のロックを解除するには、ログインするかパスを購入してください!",
|
||||||
|
"logInLabel": "ログイン",
|
||||||
|
"noUnlimitedPass": "アンリミテッドパスを持っていません!ὡ4",
|
||||||
|
"getUnlimitedPass": "アンリミテッドパスを取得してカスタム旅程を作成しましょう!",
|
||||||
|
"buyUnlimitedCityCard": "無制限のシティカードを購入する",
|
||||||
|
"noItineraryYet": "まだ旅程がありません!ὡF",
|
||||||
|
"createPersonalizedItinerary": "旅行のニーズに合ったパーソナライズされた魔法の旅程を作成する",
|
||||||
|
"travelPlan": " 旅行プラン",
|
||||||
|
"activeLabel": "アクティブ",
|
||||||
|
"inactiveLabel": "非アクティブ",
|
||||||
|
"daysLabel": " 日間",
|
||||||
|
"attractionsCountLabel": " アトラクション",
|
||||||
|
"createdOnLabel": "作成日 ",
|
||||||
|
"viewItineraryLabel": "旅程を見る",
|
||||||
|
"allFilter": "すべて",
|
||||||
|
"latestFilter": "最新",
|
||||||
|
"oldestFilter": "最も古い",
|
||||||
|
"flexiCardFilter": "フレキシカード",
|
||||||
|
"unlimitedCardFilter": "無制限カード",
|
||||||
|
"sortByLabel": "並び替え",
|
||||||
|
"pleaseLogInToViewPasses": "パスを表示するにはログインしてください",
|
||||||
|
"logInAccessPasses": "ログインしてパス、限定オファー、割引にアクセスしてください。",
|
||||||
|
"noPassYet": "まだパスがありません!😕",
|
||||||
|
"getAPassUnlockOffers": "パスを取得して、限定オファー、割引などのロックを解除しましょう。",
|
||||||
|
"buyAPass": "パスを購入する",
|
||||||
|
"attractionSingular": "アトラクション",
|
||||||
|
"attractionsPlural": "アトラクション",
|
||||||
|
"daySingular": "日",
|
||||||
|
"daysPlural": "日",
|
||||||
|
"cardSuffixLabel": "カード",
|
||||||
|
"adultsPrefix": "大人-",
|
||||||
|
"kidsPrefix": " • 子供-",
|
||||||
|
"validTillPrefix": "有効期限: ",
|
||||||
|
"noPostcardOrdersYet": "まだポストカードを注文していないようです!",
|
||||||
|
"whipUpFunPostcardOrders": "遠く離れた特別な人に送るための楽しいポストカードを作ってみませんか? さっそく始めましょう!",
|
||||||
|
"searchOrdersHint": "注文を検索...",
|
||||||
|
"foundPrefix": "見つかった数 ",
|
||||||
|
"ofMiddle": " / ",
|
||||||
|
"ordersSuffix": " 注文",
|
||||||
|
"noOrdersFound": "注文が見つかりません",
|
||||||
|
"tryAdjustingSearchQuery": "検索クエリを調整してみてください",
|
||||||
|
"errorLoadingOrders": "注文の読み込みエラー",
|
||||||
|
"retryButtonLabel": "再試行",
|
||||||
|
"statusLabel": "ステータス:",
|
||||||
|
"previewButtonLabel": "プレビュー",
|
||||||
|
"noPostcardDraftsYet": "まだポストカードを作成していないようです!",
|
||||||
|
"whipUpFunPostcardDrafts": "遠く離れた特別な人に送るポストカードを作ってみませんか?",
|
||||||
|
"searchDraftsHint": "下書きを検索...",
|
||||||
|
"draftsSuffix": " 下書き",
|
||||||
|
"noSearchAvailable": "検索できません",
|
||||||
|
"tryDifferentKeywords": "別のキーワードで検索してみてください",
|
||||||
|
"errorLoadingDrafts": "下書きの読み込みエラー",
|
||||||
|
"deleteButtonLabel": "削除",
|
||||||
|
"editButtonLabel": "編集",
|
||||||
|
"sendButtonLabel": "送信",
|
||||||
|
"myDraftsTab": "マイ ドラフト",
|
||||||
|
"myOrdersTab": "マイ オーダー",
|
||||||
|
"createPostcardButton": "ポストカードを作成",
|
||||||
|
"makeMostOfTrip": "旅を最大限に楽しむ",
|
||||||
|
"designUniquePostcards": "忘れられない瞬間を大切にするための\n独自のポストカードをデザインしてください。",
|
||||||
|
"letsCreateButton": "作成しましょう",
|
||||||
|
"notLoggedInPostCards": "まだログインしていません!",
|
||||||
|
"loginToDesignPostcards": "独自のポストカードをデザインするには、ログインして\n無制限パスを購入してください",
|
||||||
|
"errorLoadingDataTitle": "データの読み込みエラー",
|
||||||
|
"buyACardTitle": "カードを購入",
|
||||||
|
"memberPrivilegesTitle": "会員特典",
|
||||||
|
"noOffersAvailable": "利用可能なオファーはありません",
|
||||||
|
"availableAttractionsTitle": "利用可能なアトラクション",
|
||||||
|
"featureAccessToAttractions": "アトラクションへのアクセス",
|
||||||
|
"featureEntryToAttractions": "アトラクションへの入場",
|
||||||
|
"featureAccessToExperiences": "体験へのアクセス",
|
||||||
|
"featureEntryToSites": "サイトへの入場",
|
||||||
|
"featureAccessToVenues": "会場へのアクセス",
|
||||||
|
"featureEntryToEvents": "イベントへの入場",
|
||||||
|
"featureAccessToItineraryCreation": "旅程作成へのアクセス",
|
||||||
|
"featureAccessToPostcardCreation": "ポストカード作成へのアクセス",
|
||||||
|
"featuresTitle": "特徴",
|
||||||
|
"unlimitedTitle": "無制限",
|
||||||
|
"fromPrefix": "から ",
|
||||||
|
"perAdultSuffix": " /大人",
|
||||||
|
"andPrefix": "そして ",
|
||||||
|
"perChildSuffix": " /子供",
|
||||||
|
"diveIntoSelection": "スリリングな目的地を幅広くご紹介します!",
|
||||||
|
"noOfAdultsLabel": "大人の数",
|
||||||
|
"noOfChildrenLabel": "子供の数",
|
||||||
|
"noOfDaysLabel": "日数",
|
||||||
|
"noOfAttractionsLabel": "アトラクションの数",
|
||||||
|
"youPayLabel": "お支払い額",
|
||||||
|
"pleaseWaitLabel": "お待ちください...",
|
||||||
|
"proceedToPayLabel": "支払いに進む",
|
||||||
|
"atLeastOneAdultRequired": "最低1人の大人が必要です",
|
||||||
|
"cannotGoBelowZero": "0未満にはできません",
|
||||||
|
"noCheckoutDataAvailable": "チェックアウトデータはありません",
|
||||||
|
"goBackButtonLabel": "戻る",
|
||||||
|
"completePaymentTitle": "支払いを完了する",
|
||||||
|
"processingPaymentMessage": "パスの支払いを処理しています...",
|
||||||
|
"paymentSuccessfulMessage": "支払い成功!\nパスの準備ができました。",
|
||||||
|
"paymentFailedMessage": "支払いに失敗しました",
|
||||||
|
"paymentCancelledMessage": "支払いがキャンセルされました",
|
||||||
|
"paymentConfirmedMessage": "支払いが正常に確認されました!",
|
||||||
|
"checkoutTitle": "チェックアウト",
|
||||||
|
"adultLabelSingular": "大人",
|
||||||
|
"adultLabelPlural": "大人",
|
||||||
|
"kidLabelSingular": "子供",
|
||||||
|
"kidLabelPlural": "子供",
|
||||||
|
"loadingCoupons": "クーポンを読み込み中...",
|
||||||
|
"errorLoadingCoupons": "クーポンの読み込みエラー",
|
||||||
|
"couponAppliedPrefix": "適用されたクーポン: ",
|
||||||
|
"discountOnPrefix": " 割引 ",
|
||||||
|
"noCouponsAvailable": "利用可能なクーポンはありません",
|
||||||
|
"viewAllCoupons": "すべてのクーポンを見る",
|
||||||
|
"pleaseLoginToApplyCoupon": "クーポンを適用するにはログインしてください",
|
||||||
|
"applyingLabel": "適用中...",
|
||||||
|
"removeLabel": "削除",
|
||||||
|
"applyLabel": "適用",
|
||||||
|
"subtotalLabel": "小計",
|
||||||
|
"discountLabel": "割引",
|
||||||
|
"totalLabel": "合計",
|
||||||
|
"includingTaxesPrefix": "税込み ",
|
||||||
|
"inTaxesSuffix": " ",
|
||||||
|
"allCouponsTitle": "すべてのクーポン",
|
||||||
|
"applyCouponLabel": "クーポンを適用",
|
||||||
|
"purchaseDetailsTitle": "購入の詳細",
|
||||||
|
"buyPassForMyselfTitle": "自分用にパスを購入",
|
||||||
|
"editDetailsLabel": "詳細を編集",
|
||||||
|
"giftThePassTitle": "パスを贈る",
|
||||||
|
"giftThePassDescription": "他の人にパスを贈る",
|
||||||
|
"proceedLabel": "進む",
|
||||||
|
"processingLabel": "処理中...",
|
||||||
|
"payPrefixLabel": "支払う ",
|
||||||
|
"loginToCheckoutLabel": "ログインしてチェックアウト",
|
||||||
|
"yourCartTitle": "あなたのカート",
|
||||||
|
"myCardsTab": "マイカード",
|
||||||
|
"myPostCardsTab": "マイポストカード",
|
||||||
|
"yourCartIsEmpty": "カートは空です",
|
||||||
|
"attractionsLabelSuffix": " アトラクション",
|
||||||
|
"daysLabelSuffix": " 日間",
|
||||||
|
"youDoNotHaveAnyPasses": "パスを持っていません",
|
||||||
|
"emptyPassesDescription": "パスを入手して、お気に入りの都市への旅行でオファーや割引などを手に入れましょう",
|
||||||
|
"buyAPassLabel": "パスを購入",
|
||||||
|
"errorLoadingCart": "カートの読み込みエラー",
|
||||||
|
"purchaseOnePostcardAtTime": "ポストカードは一度に1枚ずつ購入できます",
|
||||||
|
"proceedToCheckoutLabel": "チェックアウトに進む",
|
||||||
|
"loginToAccessPostcardsCart": "ポストカードカートにアクセスするにはログインしてください",
|
||||||
|
"youDoNotHaveAnyPostcards": "ポストカードがありません",
|
||||||
|
"emptyPostcardsDescription": "まだポストカードを所有しておらず、誰にも送信していません",
|
||||||
|
"designMyPostcardLabel": "ポストカードをデザイン",
|
||||||
|
"somethingWentWrong": "問題が発生しました",
|
||||||
|
"retryLabel": "再試行",
|
||||||
|
"editDraftLabel": "下書きを編集",
|
||||||
|
"backTitle": "戻る",
|
||||||
|
"magicItineraryTitle": "魔法の旅程 ✨",
|
||||||
|
"stepPrefix": "ステップ ",
|
||||||
|
"ofSuffix": " / ",
|
||||||
|
"createYourLabel": "あなたの",
|
||||||
|
"magicItinerarySubtitle": " 魔法の旅程を作成",
|
||||||
|
"itineraryCreationStartDescription": "こんにちは!いくつかの楽しい質問に答えるだけで、完全にあなたに合わせた旅行体験を作成します! ✈️✨",
|
||||||
|
"letsExploreTogether": "一緒に探検しましょう!",
|
||||||
|
"takesOnlyTwoMinutes": "わずか2分で完了 ⏱️",
|
||||||
|
"gatheringPreferencesLabel": "設定を収集しています...",
|
||||||
|
"findingBestSpotsLabel": "最高のスポットを探しています...",
|
||||||
|
"buildingScheduleLabel": "スケジュールを構築しています...",
|
||||||
|
"almostThereLabel": "あともう少し...",
|
||||||
|
"yourLabel": "あなたの ",
|
||||||
|
"magicItineraryLabel": "魔法の旅程",
|
||||||
|
"isLabel": " の ",
|
||||||
|
"readyLabel": "準備完了 ",
|
||||||
|
"weHaveGotEverythingWeNeed": "完璧な旅行を計画するために必要なものはすべて揃いました",
|
||||||
|
"startOverLabel": "最初からやり直す",
|
||||||
|
"getMyTripPlan": "旅行プランを取得",
|
||||||
|
"creatingLabel": "作成中\n",
|
||||||
|
"yourItineraryLabel": "あなたの旅程",
|
||||||
|
"travelingWithKidsLabel": "子供と一緒に\n旅行",
|
||||||
|
"noKidsWithMeLabel": "子供はいません",
|
||||||
|
"areYouTravellingWithKidsLabel": "子供と一緒に旅行しますか?",
|
||||||
|
"noRestrictionsLabel": "制限なし",
|
||||||
|
"vegetarianLabel": "ベジタリアン",
|
||||||
|
"veganLabel": "ビーガン",
|
||||||
|
"pescatarianLabel": "ペスカタリアン",
|
||||||
|
"halalLabel": "ハラール",
|
||||||
|
"kosherLabel": "コーシャ",
|
||||||
|
"doYouFollowDietaryPreferenceLabel": "食事に関する希望はありますか?",
|
||||||
|
"whenAreYouPlanningToVisitLabel": "こんにちは!いつ訪問する予定ですか?",
|
||||||
|
"selectADateLabel": "日付を選択",
|
||||||
|
"continueTitle": "続ける",
|
||||||
|
"relaxedAndChillLabel": "リラックス&チル",
|
||||||
|
"balancedMixLabel": "バランスの取れたミックス",
|
||||||
|
"activeAndEnergeticLabel": "アクティブ&エネルギッシュ",
|
||||||
|
"fullAdventureLabel": "フルアドベンチャー!",
|
||||||
|
"whatKindOfEnergyLabel": "この旅行にはどのようなエネルギーを求めていますか?",
|
||||||
|
"notInterestedLabel": "興味なし",
|
||||||
|
"maybeOneOrTwoLabel": "1つか2つなら",
|
||||||
|
"yesSoundsGoodLabel": "はい、良さそうです!",
|
||||||
|
"absolutelyLoveThemLabel": "絶対に大好き!",
|
||||||
|
"doYouEnjoyMuseumsLabel": "博物館や美術館を訪れるのは好きですか?",
|
||||||
|
"@esimOfferTitle": {},
|
||||||
|
"esimOfferTitle": "無料のeSIMで瞬時に接続",
|
||||||
|
"@esimOfferSubtitle": {},
|
||||||
|
"esimOfferSubtitle": "素晴らしい旅は、スムーズな接続から始まります。",
|
||||||
|
"@viewPlans": {},
|
||||||
|
"viewPlans": "プランを見る",
|
||||||
|
"@withYour": {},
|
||||||
|
"withYour": "あなたの ",
|
||||||
|
"@esimKeyword": {},
|
||||||
|
"esimKeyword": "eSIM",
|
||||||
|
"@youCanKeyword": {},
|
||||||
|
"youCanKeyword": " で、できること:",
|
||||||
|
"@navigateCity": {},
|
||||||
|
"navigateCity": "街を簡単にナビゲート",
|
||||||
|
"@navigateCityDesc": {},
|
||||||
|
"navigateCityDesc": "どこにいてもリアルタイムの地図とルート案内にアクセス",
|
||||||
|
"@bookRides": {},
|
||||||
|
"bookRides": "配車予約、地図へのアクセス、アトラクションの検索をリアルタイムで",
|
||||||
|
"@bookRidesDesc": {},
|
||||||
|
"bookRidesDesc": "すべての必須旅行サービスに接続したまま",
|
||||||
|
"@sharePhotos": {},
|
||||||
|
"sharePhotos": "写真や思い出を瞬時に共有",
|
||||||
|
"@sharePhotosDesc": {},
|
||||||
|
"sharePhotosDesc": "旅行の瞬間を遅延なくアップロードして共有",
|
||||||
|
"@stayConnectedFeatures": {},
|
||||||
|
"stayConnectedFeatures": "友人、家族、旅行プランとの接続を維持",
|
||||||
|
"@stayConnectedFeaturesDesc": {},
|
||||||
|
"stayConnectedFeaturesDesc": "旅行中の重要な更新やメッセージを見逃さない",
|
||||||
|
"@simpleKeyword": {},
|
||||||
|
"simpleKeyword": "簡単な ",
|
||||||
|
"@threeStepProcess": {},
|
||||||
|
"threeStepProcess": "3ステップのプロセス",
|
||||||
|
"@getConnectedInSeconds": {},
|
||||||
|
"getConnectedInSeconds": "数秒で接続完了",
|
||||||
|
"@receiveQrCode": {},
|
||||||
|
"receiveQrCode": "QRコードを受け取る",
|
||||||
|
"@receiveQrCodeDesc": {},
|
||||||
|
"receiveQrCodeDesc": "CityCardで独自のeSIM QRコードを取得",
|
||||||
|
"@scanCode": {},
|
||||||
|
"scanCode": "コードをスキャン",
|
||||||
|
"@scanCodeDesc": {},
|
||||||
|
"scanCodeDesc": "スマートフォンのカメラを開いてQRコードをスキャン",
|
||||||
|
"@connectedStep": {},
|
||||||
|
"connectedStep": "接続完了",
|
||||||
|
"@connectedStepDesc": {},
|
||||||
|
"connectedStepDesc": "瞬時にオンラインに - 探索を始めましょう!",
|
||||||
|
"@itsOneMoreWay": {},
|
||||||
|
"itsOneMoreWay": "CityCardがあなたの旅を",
|
||||||
|
"@cityCardsKeywordSpace": {},
|
||||||
|
"cityCardsKeywordSpace": " CityCards",
|
||||||
|
"@makesYourJourney": {},
|
||||||
|
"makesYourJourney": "",
|
||||||
|
"@smarterKeywordSpace": {},
|
||||||
|
"smarterKeywordSpace": "よりスマートに",
|
||||||
|
"@andMoreSpace": {},
|
||||||
|
"andMoreSpace": "そして",
|
||||||
|
"@effortlessSpace": {},
|
||||||
|
"effortlessSpace": " 快適にするもう一つの方法です",
|
||||||
|
"@startYourJourneyToday": {},
|
||||||
|
"startYourJourneyToday": "今日から旅を始めましょう",
|
||||||
|
"@enjoy20OffMarriott": {},
|
||||||
|
"enjoy20OffMarriott": "アイコニックなマリオット ホテルが 20% オフ - CityCard 限定",
|
||||||
|
"@makeEveryStayUnforgettable": {},
|
||||||
|
"makeEveryStayUnforgettable": "すべての滞在を、探索している街と同じくらい忘れられないものにしてください。",
|
||||||
|
"@bookNow": {},
|
||||||
|
"bookNow": "今すぐ予約",
|
||||||
|
"@yourCityCardsUnlocksMore": {},
|
||||||
|
"yourCityCardsUnlocksMore": "CityCard はアトラクションだけでなく、特別な滞在への扉も開きます。",
|
||||||
|
"@thanksToOurExclusivePartnership": {},
|
||||||
|
"thanksToOurExclusivePartnership": "との独占的なパートナーシップにより、",
|
||||||
|
"@marriottHotelsKeyword": {},
|
||||||
|
"marriottHotelsKeyword": "マリオット ホテル",
|
||||||
|
"@cityCardsHoldersEnjoy": {},
|
||||||
|
"cityCardsHoldersEnjoy": "CityCard 所有者は ",
|
||||||
|
"@twentyPercentOffRates": {},
|
||||||
|
"twentyPercentOffRates": "各都市のプロパティのベスト料金から 20% オフ",
|
||||||
|
"@acrossCuratedProperties": {},
|
||||||
|
"acrossCuratedProperties": " をお楽しみいただけます。",
|
||||||
|
"@chooseFromAKeyword": {},
|
||||||
|
"chooseFromAKeyword": "",
|
||||||
|
"@wideVarietyBreak": {},
|
||||||
|
"wideVarietyBreak": "多彩なラインナップ\nから選ぶ",
|
||||||
|
"@chooseFromAWideVarietyDesc": {},
|
||||||
|
"chooseFromAWideVarietyDesc": "エレガントな都会の隠れ家やプレミアムな市内中心部のロケーションから、豪華な5つ星の体験まで、旅を ",
|
||||||
|
"@effortlessComfortable": {},
|
||||||
|
"effortlessComfortable": "よりスムーズで快適、",
|
||||||
|
"@andMiddle": {},
|
||||||
|
"andMiddle": "そして",
|
||||||
|
"@memorableKeyword": {},
|
||||||
|
"memorableKeyword": "思い出深いものにするための多彩なマリオット ホテルからお選びいただけます。",
|
||||||
|
"@simplyUseYourCityCards": {},
|
||||||
|
"simplyUseYourCityCards": "CityCard の",
|
||||||
|
"@bookingLinkTo": {},
|
||||||
|
"bookingLinkTo": " 予約リンクを使用するだけで:",
|
||||||
|
"@access20OffRates": {},
|
||||||
|
"access20OffRates": "ベスト料金から 20% オフで利用可能",
|
||||||
|
"@saveOnYourStay": {},
|
||||||
|
"saveOnYourStay": "プレミアムなマリオット プロパティでの滞在がお得に",
|
||||||
|
"@enjoyPriorityCheckIn": {},
|
||||||
|
"enjoyPriorityCheckIn": "優先チェックインとレイトチェックアウトをお楽しみください",
|
||||||
|
"@subjectToAvailability": {},
|
||||||
|
"subjectToAvailability": "空室状況によりご利用いただけない場合があります",
|
||||||
|
"@receiveExclusiveSeasonalOffers": {},
|
||||||
|
"receiveExclusiveSeasonalOffers": "限定の季節限定オファーを受け取る",
|
||||||
|
"@designedSpeciallyForCityCards": {},
|
||||||
|
"designedSpeciallyForCityCards": "CityCard 利用者向けに特別に用意されたオファー",
|
||||||
|
"@itsJustOneMoreWay": {},
|
||||||
|
"itsJustOneMoreWay": "CityCard が旅を",
|
||||||
|
"@makesExploring": {},
|
||||||
|
"makesExploring": "よりスマートに、",
|
||||||
|
"@simplerKeyword": {},
|
||||||
|
"simplerKeyword": "よりシンプルに、",
|
||||||
|
"@moreRewarding": {},
|
||||||
|
"moreRewarding": "そしてより魅力的にするもう一つの方法です。",
|
||||||
|
"@getYourCityCardsToday": {},
|
||||||
|
"getYourCityCardsToday": "今すぐ CityCard を手に入れましょう",
|
||||||
|
"shareSubject": "これを見てください",
|
||||||
|
"scanAtAttractionSite": "アトラクションの場所でこれをスキャンしてください",
|
||||||
|
"codeCopied": "コードをクリップボードにコピーしました",
|
||||||
|
"checkedIn": "チェックイン済み",
|
||||||
|
"checkIn": "チェックイン",
|
||||||
|
"problemsRedeemingPass": "パスの利用に問題がありますか? ",
|
||||||
|
"clickHere": "ここをクリック",
|
||||||
|
"about": "について",
|
||||||
|
"howToMakeBooking": "予約方法は?",
|
||||||
|
"contactNumber": "連絡先電話番号",
|
||||||
|
"tapToCall": "タップして電話する",
|
||||||
|
"email": "メール",
|
||||||
|
"tapToEmail": "タップしてメールを送る",
|
||||||
|
"datesNotAvailable": "利用可能な日付がありません",
|
||||||
|
"viaCityCards": "CityCards 経由",
|
||||||
|
"createBookingViaApp": "アプリで予約を作成する",
|
||||||
|
"whatIsIncluded": "含まれるもの",
|
||||||
|
"exactLocation": "正確な場所",
|
||||||
|
"viewLocationOnMap": "地図で場所を見る",
|
||||||
|
"peopleFrequentlyAsk": "よくある質問",
|
||||||
|
"errorMessage": "エラー: {message}",
|
||||||
|
"suggestedAttractions": "おすすめのアトラクション",
|
||||||
|
"viewAllAttractions": "すべてのアトラクションを見る",
|
||||||
|
"recommendedOffers": "おすすめのオファー",
|
||||||
|
"viewAllOffers": "すべてのオファーを見る",
|
||||||
|
"learnAboutPolicies": "ポリシーについて",
|
||||||
|
"pricePerPerson": "${price}/名様",
|
||||||
|
"priceNotAvailable": "価格はご利用いただけません",
|
||||||
|
"bookingRequired": "要予約",
|
||||||
|
"adultsLabel": "大人",
|
||||||
|
"kidsLabel": "子供",
|
||||||
|
"checkedInSuccessful": "チェックインに成功しました",
|
||||||
|
"readyToCheckIn": "チェックインの準備はできましたか?",
|
||||||
|
"entranceActivationNote": "アトラクションの入り口にいるときだけ有効にしてください:",
|
||||||
|
"minuteTimer": "{minutes}分タイマー",
|
||||||
|
"timerActivationWarning": "一度有効にすると、パスは{minutes}分間有効です。この操作は取り消せません",
|
||||||
|
"activatePassNow": "今すぐパスを有効にする",
|
||||||
|
"notAtEntranceYet": "まだ入り口にいません",
|
||||||
|
"howToRedeemTitle": "アトラクションパスの使い方は?",
|
||||||
|
"howToRedeemDescription": "アトラクションパスを使用するには、入り口でQRコードを提示してください。スタッフがスキャンし、中の素晴らしい体験へのアクセスを許可します。次での冒険をお楽しみください:",
|
||||||
|
"contactSupport": "サポートに連絡",
|
||||||
|
"passAttractionsTitle": "パスのアトラクション",
|
||||||
|
"searchAttractionsHint": "アトラクションを検索...",
|
||||||
|
"noAttractionsFound": "アトラクションが見つかりません",
|
||||||
|
"noAttractionsMatchSearch": "検索条件に一致するアトラクションはありません",
|
||||||
|
"selectiveCardFilter": "セレクティブ・カード",
|
||||||
|
"offersWithCardTitle": "{cardName}のオファー",
|
||||||
|
"searchOffersHint": "オファーを検索",
|
||||||
|
"noOffersFound": "オファーが見つかりません",
|
||||||
|
"codeCopiedMessage": "コードをコピーしました:{code}",
|
||||||
|
"noDataAvailable": "データがありません",
|
||||||
|
"userPossessive": "{firstName}さんの",
|
||||||
|
"tripDetails": "旅行の詳細:",
|
||||||
|
"daysCount": "{count}日間",
|
||||||
|
"stopsCount": "{count}ヶ所",
|
||||||
|
"adultsCount": "大人{count}名",
|
||||||
|
"kidsCount": "子供{count}名",
|
||||||
|
"shareLabel": "共有",
|
||||||
|
"preparingPdfToShare": "共有用のPDFを準備中...",
|
||||||
|
"myItinerarySubject": "私の旅程 - {title}",
|
||||||
|
"failedToShare": "共有に失敗しました: {error}",
|
||||||
|
"downloadLabel": "ダウンロード",
|
||||||
|
"downloadingLabel": "ダウンロード中...",
|
||||||
|
"downloadingPdf": "PDFをダウンロード中...",
|
||||||
|
"dailyView": "デイリービュー",
|
||||||
|
"summary": "概要",
|
||||||
|
"dayNumberLabel": "{number}日目",
|
||||||
|
"pleaseFillAllFields": "すべての項目を入力してください",
|
||||||
|
"enterValidEmail": "有効なメールアドレスを入力してください",
|
||||||
|
"enterValidPhoneForIsd": "{isdCode}の有効な電話番号を入力してください",
|
||||||
|
"failedToSubmitDetails": "詳細の送信に失敗しました",
|
||||||
|
"addDetailsTitle": "詳細を追加",
|
||||||
|
"aboutRecipient": "受取人について教えてください",
|
||||||
|
"firstNameLabelWithStar": "名前 *",
|
||||||
|
"firstNameHint": "受取人の名前を入力してください",
|
||||||
|
"lastNameLabelWithStar": "苗字 *",
|
||||||
|
"lastNameHint": "受取人の苗字を入力してください",
|
||||||
|
"emailLabelWithStar": "メールアドレス *",
|
||||||
|
"emailHint": "受取人のメールアドレスを入力してください",
|
||||||
|
"phoneNumberLabelWithStar": "電話番号 *",
|
||||||
|
"phoneNumberHint": "電話番号を入力してください",
|
||||||
|
"searchCountryHint": "国を検索...",
|
||||||
|
"cityLabelWithStar": "市区町村 *",
|
||||||
|
"cityHint": "市区町村名を入力してください",
|
||||||
|
"countryLabelWithStar": "国 *",
|
||||||
|
"countryHint": "国名を入力してください",
|
||||||
|
"submittingLabel": "送信中...",
|
||||||
|
"yourAttractionTitle": "あなたのアトラクション",
|
||||||
|
"perPersonSuffix": "/名",
|
||||||
|
"checkThisOut": "これを見てください",
|
||||||
|
"whatIsIncluded": "含まれるもの",
|
||||||
|
"peopleFrequentlyAsk": "よくある質問",
|
||||||
|
"aboutTitle": "概要",
|
||||||
|
"exactLocation": "正確な場所",
|
||||||
|
"viewOnMap": "地図で場所を確認する",
|
||||||
|
"selectImageSource": "画像ソースを選択",
|
||||||
|
"cameraLabel": "カメラ",
|
||||||
|
"galleryLabel": "ギャラリー",
|
||||||
|
"failedToPickImage": "画像の取得に失敗しました: {error}",
|
||||||
|
"userIdNotFound": "ユーザーIDが見つかりません",
|
||||||
|
"changeProfilePicture": "プロフィール写真を変更",
|
||||||
|
"enterYourFirstName": "名前を入力してください",
|
||||||
|
"firstNameRequired": "名前は必須です",
|
||||||
|
"enterYourLastName": "苗字を入力してください",
|
||||||
|
"lastNameRequired": "苗字は必須です",
|
||||||
|
"enterYourEmail": "メールアドレスを入力してください",
|
||||||
|
"enterYourPhoneNumber": "電話番号を入力してください",
|
||||||
|
"addressLabelWithStar": "住所 *",
|
||||||
|
"addressHint": "住所を手動で入力するか、タップして検索してください",
|
||||||
|
"address2Optional": "住所2(任意)",
|
||||||
|
"address2Hint": "詳細な住所を入力してください",
|
||||||
|
"zipCodeLabelWithStar": "郵便番号 *",
|
||||||
|
"zipCodeHint": "お住まいの地域の郵便番号を入力してください",
|
||||||
|
"enterYourCity": "市区町村名を入力してください",
|
||||||
|
"stateLabelWithStar": "都道府県 *",
|
||||||
|
"enterYourState": "都道府県を入力してください",
|
||||||
|
"enterYourCountry": "国を入力してください",
|
||||||
|
"editProfileTitle": "プロフィールを編集",
|
||||||
|
"personalDetails": "個人詳細",
|
||||||
|
"locationDetails": "場所の詳細",
|
||||||
|
"cancelTitle": "キャンセル",
|
||||||
|
"saveTitle": "保存",
|
||||||
|
"contactUsTitle": "お問い合わせ",
|
||||||
|
"contactUsMessage": "以下のプラットフォームからお問い合わせいただけます。担当チームより折り返しご連絡いたします",
|
||||||
|
"customerSupport": "カスタマーサポート",
|
||||||
|
"contactNumberLabel": "電話番号",
|
||||||
|
"tapToCall": "タップして電話をかける",
|
||||||
|
"emailLabel": "メールアドレス",
|
||||||
|
"tapToEmail": "タップしてメールを送信",
|
||||||
|
"locationLabel": "所在地",
|
||||||
|
"enterYourEmailAddress": "メールアドレスを入力してください",
|
||||||
|
"emailRequired": "メールアドレスは必須です",
|
||||||
|
"enterValidEmail": "有効なメールアドレスを入力してください",
|
||||||
|
"phoneNumberRequired": "電話番号は必須です",
|
||||||
|
"descriptionLabelWithStar": "詳細 *",
|
||||||
|
"writeMessageHint": "ここにメッセージを入力してください",
|
||||||
|
"submitTicket": "チケットを送信",
|
||||||
|
"termsAndConditionsTitle": "利用規約",
|
||||||
|
"privacyPolicyTitle": "プライバシーポリシー",
|
||||||
|
"faqTitle": "よくある質問",
|
||||||
|
"noTermsContent": "利用規約の内容はありません。",
|
||||||
|
"noPrivacyContent": "プライバシーポリシーの内容はありません。",
|
||||||
|
"retryLabel": "リトライ",
|
||||||
|
"errorLabel": "エラー",
|
||||||
|
"offersWithCard": "{cardName}カードの特典",
|
||||||
|
"@offersWithCard": {
|
||||||
|
"placeholders": {
|
||||||
|
"cardName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"searchOffersHint": "特典を検索",
|
||||||
|
"noOffersFound": "特典が見つかりません",
|
||||||
|
"aboutPartner": "{partnerName}について",
|
||||||
|
"@aboutPartner": {
|
||||||
|
"placeholders": {
|
||||||
|
"partnerName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howToMakeBooking": "予約方法は?",
|
||||||
|
"bookingStep1": "クーポンの有効期限を確認して、まだ有効であることを確かめてください。",
|
||||||
|
"bookingStep2": "クーポンが利用できる店舗またはウェブサイトにアクセスしてください。",
|
||||||
|
"bookingStep3": "オンラインでショッピングをする場合は、商品をカートに追加してチェックアウトに進みます。",
|
||||||
|
"bookingStep4": "チェックアウト時に「クーポンコード」または「プロモーションコード」というラベルのフィールドを探してください。",
|
||||||
|
"bookingStep5": "特殊文字を含め、クーポンコードを表示されている通りに入力してください。",
|
||||||
|
"couponCodeCopied": "クーポンコードをコピーしました!",
|
||||||
|
"createYourAccount": "アカウントを作成",
|
||||||
|
"personalInformation": "個人情報",
|
||||||
|
"locationDetailsWithStar": "場所の詳細 *",
|
||||||
|
"enterYourAddress": "住所を入力してください",
|
||||||
|
"autoFillZipNote": "市区町村、都道府県、国は郵便番号から自動入力されます",
|
||||||
|
"creatingLabelEllipsis": "作成中...",
|
||||||
|
"createAccount": "アカウントを作成",
|
||||||
|
"getStarted": "始める",
|
||||||
|
"enterEmailToBegin": "メールアドレスを入力して、CityCardsの旅を始めましょう",
|
||||||
|
"emailExampleHint": "john.doe@gmail.com",
|
||||||
|
"pleaseEnterEmail": "メールアドレスを入力してください",
|
||||||
|
"sendingLabel": "送信中...",
|
||||||
|
"otpVerifiedSuccess": "OTPの認証に成功しました!",
|
||||||
|
"completeProfileNote": "プロフィールを完成させてください",
|
||||||
|
"otpResentSuccess": "OTPを再送信しました!",
|
||||||
|
"verifyYourPhone": "電話番号の確認",
|
||||||
|
"enterVerificationCodeNote": "メールアドレスに送信された確認コードを入力してください",
|
||||||
|
"resendingLabel": "再送信中...",
|
||||||
|
"resendOtpLabel": "OTPを再送信",
|
||||||
|
"resendOtpWithTimer": "OTPを再送信 ({seconds}s)",
|
||||||
|
"pleaseEnterCompleteOtp": "OTPをすべて入力してください",
|
||||||
|
"verifyingLabel": "認証中...",
|
||||||
|
"ok": "OK",
|
||||||
|
"uploadAPhoto": "写真をアップロード",
|
||||||
|
"orLabel": "または",
|
||||||
|
"takeAPhoto": "写真を撮る",
|
||||||
|
"uploadAgain": "もう一度アップロード",
|
||||||
|
"nextTitle": "次へ",
|
||||||
|
"addImage": "画像を追加",
|
||||||
|
"addAFilter": "フィルターを追加",
|
||||||
|
"chooseFavoriteFilter": "お気に入りのフィルターを選んで、ポストカードをより良くしましょう。",
|
||||||
|
"filterOriginal": "オリジナル",
|
||||||
|
"filterBW": "モノクロ",
|
||||||
|
"filterSepia": "セピア",
|
||||||
|
"filterVintage": "ビンテージ",
|
||||||
|
"filterCoolTone": "クールトーン",
|
||||||
|
"filterContrast": "コントラスト",
|
||||||
|
"filterSoftGlow": "ソフトグロー",
|
||||||
|
"writeYourMessage": "メッセージを書く",
|
||||||
|
"failedToFetchEditDetails": "編集内容の取得に失敗しました",
|
||||||
|
"saveChanges": "変更を保存",
|
||||||
|
"writeAMessage": "メッセージを書く",
|
||||||
|
"addYourMessageHere": "ここにメッセージを追加",
|
||||||
|
"fontDefault": "デフォルト",
|
||||||
|
"previewYourPostcard": "ポストカードのプレビュー",
|
||||||
|
"flipLabel": "裏返す",
|
||||||
|
"buyPostcardForMyself": "自分のためにポストカードを購入",
|
||||||
|
"editDetailsLabel": "詳細を編集",
|
||||||
|
"giftPostcardForSomeoneElse": "他の人にポストカードを贈る",
|
||||||
|
"addTitleLabel": "タイトルを追加",
|
||||||
|
"enterTitleHint": "タイトルを入力",
|
||||||
|
"pleaseEnterTitle": "タイトルを入力してください",
|
||||||
|
"yourDetailsTitle": "お客様の詳細",
|
||||||
|
"senderDetailsDescription": "このポストカードの送り主としての詳細情報を入力してください",
|
||||||
|
"fullNameLabel": "氏名",
|
||||||
|
"enterFullNameHint": "氏名を入力してください",
|
||||||
|
"cityLabel": "市区町村",
|
||||||
|
"enterCityHint": "市区町村名を入力してください",
|
||||||
|
"countryLabel": "国",
|
||||||
|
"enterCountryHint": "国名を入力してください",
|
||||||
|
"recipientDetailsTitle": "受取人の詳細",
|
||||||
|
"recipientDetailsDescription": "ポストカードを受け取る人の住所を入力してください",
|
||||||
|
"selfDetailsDescription": "このポストカードの連絡先詳細を入力してください。",
|
||||||
|
"recipientNameLabel": "受取人名",
|
||||||
|
"enterRecipientNameHint": "受取人の名前を入力してください",
|
||||||
|
"addressLabel": "住所",
|
||||||
|
"enterRecipientAddressHint": "受取人の住所を入力してください",
|
||||||
|
"zipCodeLabel": "郵便番号",
|
||||||
|
"enterZipCodeHint": "お住まいの郵便番号を入力してください",
|
||||||
|
"pleaseEnterZipCode": "郵便番号を入力してください",
|
||||||
|
"stateLabel": "都道府県",
|
||||||
|
"enterStateHint": "都道府県を入力してください",
|
||||||
|
"pleaseEnterField": "{fieldName}を入力してください",
|
||||||
|
"invalidEmailError": "有効なメールアドレスを入力してください",
|
||||||
|
"onlyNumbersAllowed": "数字のみ入力可能です",
|
||||||
|
"mobileNumberLengthError": "携帯電話番号は{length}桁である必要があります",
|
||||||
|
"onlyLettersAllowed": "文字のみ入力可能です",
|
||||||
|
"spacesNotAllowed": "スペースは使用できません",
|
||||||
|
"specialCharsNotAllowed": "特殊文字は使用できません",
|
||||||
|
"saveAsDraftLabel": "下書きとして保存",
|
||||||
|
"deliveryAddressLabel": "お届け先住所",
|
||||||
|
"paymentSummaryLabel": "お支払い内容",
|
||||||
|
"grandTotalLabel": "総計",
|
||||||
|
"paymentFailedError": "決済に失敗しました: {error}",
|
||||||
|
"paymentInitializationFailedError": "エラー:決済の初期化に失敗しました",
|
||||||
|
"draftSavedSuccess": "下書きを保存しました。",
|
||||||
|
"payAmountLabel": "支払い ${amount}",
|
||||||
|
"orderPlacedSuccess": "注文が完了しました!",
|
||||||
|
"orderPlacedDescription": "ご注文を承りました。注文番号は {orderId} です。",
|
||||||
|
"deliveryTimelineNote": "2〜3営業日以内にお届けします。",
|
||||||
|
"goToMyOrdersLabel": "注文履歴へ",
|
||||||
|
"messagePreviewLabel": "メッセージのプレビュー",
|
||||||
|
"postcardLabel": "ポストカード",
|
||||||
|
"homeTitle": "CityCards.\nもっと見て、\nもっと節約。",
|
||||||
|
"homeSubtitle": "40以上のアトラクションへの即時QRアクセス、\n限定特典、最大30%の節約",
|
||||||
|
"getYourCityCards": "CityCardsを手に入れる",
|
||||||
|
"exploreCitiesTitleExplore": "探索 ",
|
||||||
|
"exploreCitiesTitleCities": "都市",
|
||||||
|
"upcomingCitiesTitleUpcoming": "今後の ",
|
||||||
|
"upcomingCitiesTitleCities": "都市",
|
||||||
|
"exploreDescription": "夢の目的地を探索し、さまざまなアトラクションを体験してください。",
|
||||||
|
"noCitiesAvailable": "利用可能な都市はありません",
|
||||||
|
"notAvailableLabel": "N/A",
|
||||||
|
"editPostcardTitle": "ポストカードを編集",
|
||||||
|
"uploadImageTitle": "画像をアップロード",
|
||||||
|
"editPostcardUploadDescription": "忘れられない瞬間を捉えた画像をアップロードして、独自のポストカードを編集してください。",
|
||||||
|
"editFiltersLabel": "フィルターを編集",
|
||||||
|
"editTitleLabel": "タイトルを編集 ",
|
||||||
|
"editTitleDescription": "ポストカードに別のタイトルを付けてください",
|
||||||
|
"titleMaxLengthError": "タイトルは最大10文字です",
|
||||||
|
"editMessageLabel": "メッセージを編集 ",
|
||||||
|
"editMessageDescription": "忘れられない瞬間を大切にするために、独自のポストカードを編集してください。",
|
||||||
|
"fullNameLabelWithStar": "氏名 *",
|
||||||
|
"recipientLabelWithStar": "受取人 *",
|
||||||
|
"pleaseEnterMessage": "メッセージを入力してください",
|
||||||
|
"messageMaxLengthError": "メッセージは最大400文字です",
|
||||||
|
"fontPatrickHand": "Patrick Hand",
|
||||||
|
"fontIndieFlower": "Indie Flower",
|
||||||
|
"fontGloriaHallelujah": "Gloria Hallelujah"
|
||||||
|
}
|
||||||
3662
lib/l10n/app_localizations.dart
Normal file
1879
lib/l10n/app_localizations_en.dart
Normal file
1901
lib/l10n/app_localizations_es.dart
Normal file
1909
lib/l10n/app_localizations_fr.dart
Normal file
1828
lib/l10n/app_localizations_ja.dart
Normal file
1892
lib/l10n/app_localizations_nl.dart
Normal file
671
lib/l10n/app_nl.arb
Normal file
@@ -0,0 +1,671 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "nl",
|
||||||
|
"profileTitle": "Mijn Profiel",
|
||||||
|
"supportAndLegal": "Ondersteuning & Juridisch",
|
||||||
|
"contactUs": "Neem contact op",
|
||||||
|
"termsAndConditions": "Algemene voorwaarden",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"privacyPolicy": "Privacybeleid",
|
||||||
|
"logout": "Uitloggen",
|
||||||
|
"signIn": "Inloggen",
|
||||||
|
"heyStranger": "Hé, vreemdeling! 👋",
|
||||||
|
"guestSubtitle1": "We zijn blij dat je onze app gebruikt.",
|
||||||
|
"guestSubtitle2": "Waarom maak je het niet officieel?",
|
||||||
|
"accountSettings": "Accountinstellingen",
|
||||||
|
"editProfile": "Profiel bewerken",
|
||||||
|
"changeLanguage": "Taal wijzigen",
|
||||||
|
"retry": "Opnieuw proberen",
|
||||||
|
"failedToLoadProfile": "Profiel laden mislukt",
|
||||||
|
"changeLanguageTitle": "Taal wijzigen",
|
||||||
|
"searchLanguages": "Talen zoeken",
|
||||||
|
"save": "Opslaan",
|
||||||
|
"oopsSomethingWentWrong": "Oeps! Er is iets misgegaan",
|
||||||
|
"tryAgain": "Probeer opnieuw",
|
||||||
|
"defaultCityName": "Stadsnaam",
|
||||||
|
"defaultCityDescription": "Stadsbeschrijving",
|
||||||
|
"noHighlightsAvailable": "Geen hoogtepunten beschikbaar",
|
||||||
|
"popular": "Populaire ",
|
||||||
|
"attractions": "Attracties",
|
||||||
|
"viewAll": "Bekijk alles",
|
||||||
|
"createMyMagicItinerary": "Maak mijn magische reisschema",
|
||||||
|
"claimOffersWithCityCards": "Claim aanbiedingen met uw City Cards",
|
||||||
|
"offerSubtitleDummy": "Lorem ipsum dolor sit amet...",
|
||||||
|
"noAttractionsAvailable": "Geen attracties beschikbaar",
|
||||||
|
"explore": "Ontdek ",
|
||||||
|
"moreCities": "Meer steden.",
|
||||||
|
"stay": "Blijf ",
|
||||||
|
"connected": "Verbonden",
|
||||||
|
"everywhere": "Overal",
|
||||||
|
"claimEsimOffers": "Claim e-Sim aanbiedingen met\nCityCard",
|
||||||
|
"chooseYourCard": "Kies je kaart",
|
||||||
|
"diveIntoExtensiveSelection": "Duik in een uitgebreide selectie...",
|
||||||
|
"from": "Vanaf ",
|
||||||
|
"getACard": "Koop een kaart",
|
||||||
|
"marriott": "MARRIOTT ",
|
||||||
|
"moments": "MOMENTEN",
|
||||||
|
"citycard": "CITYCARD ",
|
||||||
|
"prices": "PRIJZEN",
|
||||||
|
"premium": "Premium ",
|
||||||
|
"stays": "Verblijven",
|
||||||
|
"citycardPrices": "CityCard Prijzen",
|
||||||
|
"claimHotelDiscountOffers": "Claim Hotel Korting aanbiedingen\nmet CityCard",
|
||||||
|
"back": "Terug",
|
||||||
|
"selectACity": "Selecteer een stad",
|
||||||
|
"searchCities": "Zoek steden",
|
||||||
|
"errorLoadingCities": "Fout bij laden van steden",
|
||||||
|
"noCitiesFound": "Geen steden gevonden",
|
||||||
|
"individualTickets": "Individuele tickets:",
|
||||||
|
"cityCardLabel": "City Card:",
|
||||||
|
"getYourCard": "Haal je kaart",
|
||||||
|
"fromLabel": "Vanaf",
|
||||||
|
"perAdult": " /Volwassene",
|
||||||
|
"planYour": "Plan uw ",
|
||||||
|
"dreamJourney": "Droomreis",
|
||||||
|
"inJust3Seconds": "\nin slechts 3 seconden",
|
||||||
|
"navExplore": "Ontdekken",
|
||||||
|
"navMagicItinerary": "Magisch Reisschema",
|
||||||
|
"navMyCards": "Mijn Kaarten",
|
||||||
|
"navPostcard": "Ansichtkaart",
|
||||||
|
"planYourNextAdventure": "Plan je volgende avontuur",
|
||||||
|
"createMyItinerary": "Maak Mijn Reisschema",
|
||||||
|
"notLoggedInYet": "Je bent nog niet ingelogd!",
|
||||||
|
"loginOrPurchasePass": "Log in of koop een pas om het magische reisschema te ontgrendelen!",
|
||||||
|
"logInLabel": "Inloggen",
|
||||||
|
"noUnlimitedPass": "Je hebt geen Onbeperkte Pas! ὡ4",
|
||||||
|
"getUnlimitedPass": "Haal je Onbeperkte Pas en maak een aangepast reisschema!",
|
||||||
|
"buyUnlimitedCityCard": "Koop Onbeperkte CityCard",
|
||||||
|
"noItineraryYet": "Je hebt nog geen Reisschema! ὡF",
|
||||||
|
"createPersonalizedItinerary": "Maak je eigen gepersonaliseerde magische reisschema dat past bij je reisbehoeften",
|
||||||
|
"travelPlan": " Reisplan",
|
||||||
|
"activeLabel": "Actief",
|
||||||
|
"inactiveLabel": "Inactief",
|
||||||
|
"daysLabel": " dagen",
|
||||||
|
"attractionsCountLabel": " attracties",
|
||||||
|
"createdOnLabel": "Gemaakt ",
|
||||||
|
"viewItineraryLabel": "Bekijk Reisschema",
|
||||||
|
"allFilter": "Alles",
|
||||||
|
"latestFilter": "Nieuwste",
|
||||||
|
"oldestFilter": "Oudste",
|
||||||
|
"flexiCardFilter": "Flexibele Kaart",
|
||||||
|
"unlimitedCardFilter": "Onbeperkte Kaart",
|
||||||
|
"sortByLabel": "Sorteer op",
|
||||||
|
"pleaseLogInToViewPasses": "Log in om je passen te bekijken",
|
||||||
|
"logInAccessPasses": "Log in voor toegang tot je passen, exclusieve aanbiedingen en kortingen.",
|
||||||
|
"noPassYet": "Je hebt nog geen pas! 😕",
|
||||||
|
"getAPassUnlockOffers": "Koop een pas en ontgrendel exclusieve aanbiedingen, kortingen en meer.",
|
||||||
|
"buyAPass": "Koop een Pas",
|
||||||
|
"attractionSingular": "Attractie",
|
||||||
|
"attractionsPlural": "Attracties",
|
||||||
|
"daySingular": "Dag",
|
||||||
|
"daysPlural": "Dagen",
|
||||||
|
"cardSuffixLabel": " Kaart",
|
||||||
|
"adultsPrefix": "Volwassenen-",
|
||||||
|
"kidsPrefix": " • Kinderen-",
|
||||||
|
"validTillPrefix": "Geldig tot: ",
|
||||||
|
"noPostcardOrdersYet": "Het lijkt erop dat je nog geen\nansichtkaarten hebt besteld!",
|
||||||
|
"whipUpFunPostcardOrders": "Wat dacht je ervan we een leuke ansichtkaart maken om naar je geliefden te sturen? Laten we beginnen!",
|
||||||
|
"searchOrdersHint": "Zoek bestellingen...",
|
||||||
|
"foundPrefix": "Gevonden ",
|
||||||
|
"ofMiddle": " van ",
|
||||||
|
"ordersSuffix": " bestellingen",
|
||||||
|
"noOrdersFound": "Geen bestellingen gevonden",
|
||||||
|
"tryAdjustingSearchQuery": "Probeer je zoekopdracht aan te passen",
|
||||||
|
"errorLoadingOrders": "Fout bij laden bestellingen",
|
||||||
|
"retryButtonLabel": "Opnieuw proberen",
|
||||||
|
"statusLabel": "Status:",
|
||||||
|
"previewButtonLabel": "Voorbeeld",
|
||||||
|
"noPostcardDraftsYet": "Het lijkt erop dat je nog geen\nansichtkaarten hebt gemaakt!",
|
||||||
|
"whipUpFunPostcardDrafts": "Waarom maak je niet een ansichtkaart en stuur je die naar een speciaal iemand ver weg?",
|
||||||
|
"searchDraftsHint": "Zoek concepten...",
|
||||||
|
"draftsSuffix": " concepten",
|
||||||
|
"noSearchAvailable": "Geen zoekopdracht beschikbaar",
|
||||||
|
"tryDifferentKeywords": "Probeer te zoeken met andere trefwoorden",
|
||||||
|
"errorLoadingDrafts": "Fout bij laden concepten",
|
||||||
|
"deleteButtonLabel": "Verwijderen",
|
||||||
|
"editButtonLabel": "Bewerken",
|
||||||
|
"sendButtonLabel": "Verzenden",
|
||||||
|
"myDraftsTab": "Mijn concepten",
|
||||||
|
"myOrdersTab": "Mijn bestellingen",
|
||||||
|
"createPostcardButton": "Maak ansichtkaart",
|
||||||
|
"makeMostOfTrip": "Haal het meeste uit je reis",
|
||||||
|
"designUniquePostcards": "Ontwerp je eigen unieke ansichtkaarten om\nje onvergetelijke momenten te koesteren.",
|
||||||
|
"letsCreateButton": "Laten we maken ",
|
||||||
|
"notLoggedInPostCards": "Je bent nog niet ingelogd!",
|
||||||
|
"loginToDesignPostcards": "Om je eigen unieke ansichtkaarten te ontwerpen, log\nje in en koop je een onbeperkte pas",
|
||||||
|
"errorLoadingDataTitle": "Fout bij laden gegevens",
|
||||||
|
"buyACardTitle": "Koop een kaart",
|
||||||
|
"memberPrivilegesTitle": "Ledenprivileges",
|
||||||
|
"noOffersAvailable": "Geen aanbiedingen beschikbaar",
|
||||||
|
"availableAttractionsTitle": "Beschikbare attracties",
|
||||||
|
"featureAccessToAttractions": "Toegang tot attracties",
|
||||||
|
"featureEntryToAttractions": "Toegang tot bezienswaardigheden",
|
||||||
|
"featureAccessToExperiences": "Toegang tot ervaringen",
|
||||||
|
"featureEntryToSites": "Toegang tot locaties",
|
||||||
|
"featureAccessToVenues": "Toegang tot locaties",
|
||||||
|
"featureEntryToEvents": "Toegang tot evenementen",
|
||||||
|
"featureAccessToItineraryCreation": "Toegang tot het maken van reisroutes",
|
||||||
|
"featureAccessToPostcardCreation": "Toegang tot het maken van ansichtkaarten",
|
||||||
|
"featuresTitle": "Functies",
|
||||||
|
"unlimitedTitle": "Onbeperkt",
|
||||||
|
"fromPrefix": "Vanaf ",
|
||||||
|
"perAdultSuffix": " /Volwassene",
|
||||||
|
"andPrefix": "en ",
|
||||||
|
"perChildSuffix": " /kind",
|
||||||
|
"diveIntoSelection": "Duik in een uitgebreide selectie spannende bestemmingen!",
|
||||||
|
"noOfAdultsLabel": "Aantal volwassenen",
|
||||||
|
"noOfChildrenLabel": "Aantal kinderen",
|
||||||
|
"noOfDaysLabel": "Aantal dagen",
|
||||||
|
"noOfAttractionsLabel": "Aantal attracties",
|
||||||
|
"youPayLabel": "Je betaalt",
|
||||||
|
"pleaseWaitLabel": "Even geduld...",
|
||||||
|
"proceedToPayLabel": "Ga naar betalen",
|
||||||
|
"atLeastOneAdultRequired": "Minimaal 1 volwassene vereist",
|
||||||
|
"cannotGoBelowZero": "Kan niet onder de 0 zijn",
|
||||||
|
"noCheckoutDataAvailable": "Geen afrekengegevens beschikbaar",
|
||||||
|
"goBackButtonLabel": "Ga Terug",
|
||||||
|
"completePaymentTitle": "Voltooi Betaling",
|
||||||
|
"processingPaymentMessage": "Verwerken van uw pasbetaling...",
|
||||||
|
"paymentSuccessfulMessage": "Betaling Succesvol!\nUw pas is klaar.",
|
||||||
|
"paymentFailedMessage": "Betaling Mislukt",
|
||||||
|
"paymentCancelledMessage": "Betaling geannuleerd",
|
||||||
|
"paymentConfirmedMessage": "Betaling succesvol bevestigd!",
|
||||||
|
"checkoutTitle": "Afrekenen",
|
||||||
|
"adultLabelSingular": "volwassene",
|
||||||
|
"adultLabelPlural": "volwassenen",
|
||||||
|
"kidLabelSingular": "Kind",
|
||||||
|
"kidLabelPlural": "Kinderen",
|
||||||
|
"loadingCoupons": "Coupons laden...",
|
||||||
|
"errorLoadingCoupons": "Fout bij laden van coupons",
|
||||||
|
"couponAppliedPrefix": "Coupon Toegepast: ",
|
||||||
|
"discountOnPrefix": " korting op ",
|
||||||
|
"noCouponsAvailable": "Geen coupons beschikbaar",
|
||||||
|
"viewAllCoupons": "Bekijk alle coupons",
|
||||||
|
"pleaseLoginToApplyCoupon": "Log in om coupon toe te passen",
|
||||||
|
"applyingLabel": "Toepassen...",
|
||||||
|
"removeLabel": "Verwijderen",
|
||||||
|
"applyLabel": "Toepassen",
|
||||||
|
"subtotalLabel": "Subtotaal",
|
||||||
|
"discountLabel": "Korting",
|
||||||
|
"totalLabel": "Totaal",
|
||||||
|
"includingTaxesPrefix": "Inclusief ",
|
||||||
|
"inTaxesSuffix": " aan belastingen",
|
||||||
|
"allCouponsTitle": "Alle Coupons",
|
||||||
|
"applyCouponLabel": "Coupon Toepassen",
|
||||||
|
"purchaseDetailsTitle": "Aankoopdetails",
|
||||||
|
"buyPassForMyselfTitle": "Koop Pas voor Mezelf",
|
||||||
|
"editDetailsLabel": "Bewerk Details",
|
||||||
|
"giftThePassTitle": "Geef de pas cadeau",
|
||||||
|
"giftThePassDescription": "Geef de pas aan iemand anders cadeau",
|
||||||
|
"proceedLabel": "Doorgaan",
|
||||||
|
"processingLabel": "Verwerken...",
|
||||||
|
"payPrefixLabel": "Betalen ",
|
||||||
|
"loginToCheckoutLabel": "Log in om af te rekenen",
|
||||||
|
"yourCartTitle": "Jouw winkelwagen",
|
||||||
|
"myCardsTab": "Mijn Kaarten",
|
||||||
|
"myPostCardsTab": "Mijn Ansichtkaarten",
|
||||||
|
"yourCartIsEmpty": "Je winkelwagen is leeg",
|
||||||
|
"attractionsLabelSuffix": " Attracties",
|
||||||
|
"daysLabelSuffix": " Dagen",
|
||||||
|
"youDoNotHaveAnyPasses": "Je hebt geen passen",
|
||||||
|
"emptyPassesDescription": "Krijg een pas en ontvang aanbiedingen, kortingen en meer tijdens je reis naar je favoriete stad",
|
||||||
|
"buyAPassLabel": "Koop een Pas",
|
||||||
|
"errorLoadingCart": "Fout bij laden winkelwagen",
|
||||||
|
"purchaseOnePostcardAtTime": "Je kunt één ansichtkaart tegelijk kopen",
|
||||||
|
"proceedToCheckoutLabel": "Ga naar afrekenen",
|
||||||
|
"loginToAccessPostcardsCart": "Log in om toegang te krijgen tot je ansichtkaartenwinkelwagen",
|
||||||
|
"youDoNotHaveAnyPostcards": "Je hebt geen ansichtkaarten",
|
||||||
|
"emptyPostcardsDescription": "Je bezit nog geen ansichtkaarten en hebt er geen naar iemand verzonden",
|
||||||
|
"designMyPostcardLabel": "Ontwerp mijn ansichtkaart",
|
||||||
|
"somethingWentWrong": "Er is iets misgegaan",
|
||||||
|
"retryLabel": "Opnieuw proberen",
|
||||||
|
"editDraftLabel": "Bewerk concept",
|
||||||
|
"backTitle": "Terug",
|
||||||
|
"magicItineraryTitle": "Magische Reisroute ✨",
|
||||||
|
"stepPrefix": "Stap ",
|
||||||
|
"ofSuffix": " van ",
|
||||||
|
"createYourLabel": "Creëer jouw",
|
||||||
|
"magicItinerarySubtitle": " magische reisroute",
|
||||||
|
"itineraryCreationStartDescription": "Hallo! Beantwoord een paar leuke vragen en we stellen een reiservaring samen die perfect bij je past! ✈️✨",
|
||||||
|
"letsExploreTogether": "Laten we samen verkennen!",
|
||||||
|
"takesOnlyTwoMinutes": "Duurt slechts 2 minuten ⏱️",
|
||||||
|
"gatheringPreferencesLabel": "Jouw voorkeuren verzamelen...",
|
||||||
|
"findingBestSpotsLabel": "De beste plekken vinden...",
|
||||||
|
"buildingScheduleLabel": "Jouw schema opbouwen...",
|
||||||
|
"almostThereLabel": "Bijna daar...",
|
||||||
|
"yourLabel": "Jouw ",
|
||||||
|
"magicItineraryLabel": "Magische Reisroute",
|
||||||
|
"isLabel": " is ",
|
||||||
|
"readyLabel": "Klaar ",
|
||||||
|
"weHaveGotEverythingWeNeed": "We hebben alles wat we nodig hebben om je perfecte reis te plannen",
|
||||||
|
"startOverLabel": "Begin opnieuw",
|
||||||
|
"getMyTripPlan": "Ontvang Mijn Reisplan",
|
||||||
|
"creatingLabel": "Creëren\n",
|
||||||
|
"yourItineraryLabel": "Jouw Reisroute",
|
||||||
|
"travelingWithKidsLabel": "Reizen met\n kinderen",
|
||||||
|
"noKidsWithMeLabel": "Geen kinderen",
|
||||||
|
"areYouTravellingWithKidsLabel": "Reis je met kinderen?",
|
||||||
|
"noRestrictionsLabel": "Geen beperkingen",
|
||||||
|
"vegetarianLabel": "Vegetarisch",
|
||||||
|
"veganLabel": "Veganistisch",
|
||||||
|
"pescatarianLabel": "Pescotariër",
|
||||||
|
"halalLabel": "Halal",
|
||||||
|
"kosherLabel": "Koosjer",
|
||||||
|
"doYouFollowDietaryPreferenceLabel": "Volg je een specifiek dieet?",
|
||||||
|
"whenAreYouPlanningToVisitLabel": "Hallo! Wanneer ben je van plan om te bezoeken?",
|
||||||
|
"selectADateLabel": "Selecteer een datum",
|
||||||
|
"continueTitle": "Doorgaan",
|
||||||
|
"relaxedAndChillLabel": "Ontspannen",
|
||||||
|
"balancedMixLabel": "Gebalanceerde mix",
|
||||||
|
"activeAndEnergeticLabel": "Actief en energiek",
|
||||||
|
"fullAdventureLabel": "Volledig avontuur!",
|
||||||
|
"whatKindOfEnergyLabel": "Wat voor energie zoek je tijdens deze reis?",
|
||||||
|
"notInterestedLabel": "Niet geïnteresseerd",
|
||||||
|
"maybeOneOrTwoLabel": "Misschien een of twee",
|
||||||
|
"yesSoundsGoodLabel": "Ja, klinkt goed!",
|
||||||
|
"absolutelyLoveThemLabel": "Ik hou er helemaal van!",
|
||||||
|
"doYouEnjoyMuseumsLabel": "Geniet je van een bezoek aan musea en kunstgalerijen?",
|
||||||
|
"@esimOfferTitle": {},
|
||||||
|
"esimOfferTitle": "Maak direct verbinding met uw gratis eSIM",
|
||||||
|
"@esimOfferSubtitle": {},
|
||||||
|
"esimOfferSubtitle": "Elke geweldige reis begint met een soepele verbinding.",
|
||||||
|
"@viewPlans": {},
|
||||||
|
"viewPlans": "Bekijk plannen",
|
||||||
|
"@withYour": {},
|
||||||
|
"withYour": "Met uw ",
|
||||||
|
"@esimKeyword": {},
|
||||||
|
"esimKeyword": "eSIM",
|
||||||
|
"@youCanKeyword": {},
|
||||||
|
"youCanKeyword": ", kunt u:",
|
||||||
|
"@navigateCity": {},
|
||||||
|
"navigateCity": "Navigeer met gemak door de stad",
|
||||||
|
"@navigateCityDesc": {},
|
||||||
|
"navigateCityDesc": "Krijg overal toegang tot realtime kaarten en routebeschrijvingen",
|
||||||
|
"@bookRides": {},
|
||||||
|
"bookRides": "Boek ritten, bekijk kaarten en vind attracties in realtime",
|
||||||
|
"@bookRidesDesc": {},
|
||||||
|
"bookRidesDesc": "Blijf verbonden met alle essentiële reisdiensten",
|
||||||
|
"@sharePhotos": {},
|
||||||
|
"sharePhotos": "Deel foto's en herinneringen direct",
|
||||||
|
"@sharePhotosDesc": {},
|
||||||
|
"sharePhotosDesc": "Upload and deel uw reismomenten zonder vertraging",
|
||||||
|
"@stayConnectedFeatures": {},
|
||||||
|
"stayConnectedFeatures": "Blijf verbonden met vrienden, familie en reisplannen",
|
||||||
|
"@stayConnectedFeaturesDesc": {},
|
||||||
|
"stayConnectedFeaturesDesc": "Mis nooit belangrijke updates of berichten tijdens het reizen",
|
||||||
|
"@simpleKeyword": {},
|
||||||
|
"simpleKeyword": "Eenvoudig ",
|
||||||
|
"@threeStepProcess": {},
|
||||||
|
"threeStepProcess": "3-stappenproces",
|
||||||
|
"@getConnectedInSeconds": {},
|
||||||
|
"getConnectedInSeconds": "Binnen enkele seconden verbonden",
|
||||||
|
"@receiveQrCode": {},
|
||||||
|
"receiveQrCode": "QR-code ontvangen",
|
||||||
|
"@receiveQrCodeDesc": {},
|
||||||
|
"receiveQrCodeDesc": "Ontvang uw unieke eSIM QR-code bij uw CityCard",
|
||||||
|
"@scanCode": {},
|
||||||
|
"scanCode": "Code scannen",
|
||||||
|
"@scanCodeDesc": {},
|
||||||
|
"scanCodeDesc": "Open uw telefooncamera en scan de QR-code",
|
||||||
|
"@connectedStep": {},
|
||||||
|
"connectedStep": "Verbonden",
|
||||||
|
"@connectedStepDesc": {},
|
||||||
|
"connectedStepDesc": "Je bent direct online - begin met ontdekken!",
|
||||||
|
"@itsOneMoreWay": {},
|
||||||
|
"itsOneMoreWay": "Het is nog een manier waarop",
|
||||||
|
"@cityCardsKeywordSpace": {},
|
||||||
|
"cityCardsKeywordSpace": " CityCards",
|
||||||
|
"@makesYourJourney": {},
|
||||||
|
"makesYourJourney": "uw reis",
|
||||||
|
"@smarterKeywordSpace": {},
|
||||||
|
"smarterKeywordSpace": " slimmer",
|
||||||
|
"@andMoreSpace": {},
|
||||||
|
"andMoreSpace": "en",
|
||||||
|
"@effortlessSpace": {},
|
||||||
|
"effortlessSpace": " moeitelozer maakt",
|
||||||
|
"@startYourJourneyToday": {},
|
||||||
|
"startYourJourneyToday": "Begin vandaag nog aan uw reis",
|
||||||
|
"@enjoy20OffMarriott": {},
|
||||||
|
"enjoy20OffMarriott": "Geniet van 20% korting op iconische Marriott hotels - Exclusief met CityCards",
|
||||||
|
"@makeEveryStayUnforgettable": {},
|
||||||
|
"makeEveryStayUnforgettable": "Maak elk verblijf even onvergetelijk als de stad die u verkent.",
|
||||||
|
"@bookNow": {},
|
||||||
|
"bookNow": "Boek nu",
|
||||||
|
"@yourCityCardsUnlocksMore": {},
|
||||||
|
"yourCityCardsUnlocksMore": "Uw CityCards ontgrendelt meer dan alleen attracties — het opent ook deuren naar uitzonderlijke verblijven.",
|
||||||
|
"@thanksToOurExclusivePartnership": {},
|
||||||
|
"thanksToOurExclusivePartnership": "Dankzij onze exclusieve samenwerking met ",
|
||||||
|
"@marriottHotelsKeyword": {},
|
||||||
|
"marriottHotelsKeyword": "Marriott Hotels",
|
||||||
|
"@cityCardsHoldersEnjoy": {},
|
||||||
|
"cityCardsHoldersEnjoy": ", genieten CityCards-houders van ",
|
||||||
|
"@twentyPercentOffRates": {},
|
||||||
|
"twentyPercentOffRates": "20% korting op de best beschikbare tarieven",
|
||||||
|
"@acrossCuratedProperties": {},
|
||||||
|
"acrossCuratedProperties": " in een zorgvuldig geselecteerd aantal hotels in de stad.",
|
||||||
|
"@chooseFromAKeyword": {},
|
||||||
|
"chooseFromAKeyword": "Kies uit een ",
|
||||||
|
"@wideVarietyBreak": {},
|
||||||
|
"wideVarietyBreak": "Grote \nVariëteit",
|
||||||
|
"@chooseFromAWideVarietyDesc": {},
|
||||||
|
"chooseFromAWideVarietyDesc": "Kies uit een grote variëteit aan Marriott hotels — van elegante stedelijke schuilplaatsen en premium locaties in het stadscentrum tot luxueuze vijfsterrenervaringen — allemaal ontworpen om uw reis ",
|
||||||
|
"@effortlessComfortable": {},
|
||||||
|
"effortlessComfortable": "moeiteloos, comfortabel",
|
||||||
|
"@andMiddle": {},
|
||||||
|
"andMiddle": " en ",
|
||||||
|
"@memorableKeyword": {},
|
||||||
|
"memorableKeyword": "onvergetelijk te maken",
|
||||||
|
"@simplyUseYourCityCards": {},
|
||||||
|
"simplyUseYourCityCards": "Gebruik gewoon uw ",
|
||||||
|
"@bookingLinkTo": {},
|
||||||
|
"bookingLinkTo": " CityCards-boekingslink om:",
|
||||||
|
"@access20OffRates": {},
|
||||||
|
"access20OffRates": "Toegang te krijgen tot 20% korting op de best beschikbare tarieven",
|
||||||
|
"@saveOnYourStay": {},
|
||||||
|
"saveOnYourStay": "Bespaar op uw verblijf bij premium Marriott hotels",
|
||||||
|
"@enjoyPriorityCheckIn": {},
|
||||||
|
"enjoyPriorityCheckIn": "Geniet van prioriteit bij het inchecken en late check-out",
|
||||||
|
"@subjectToAvailability": {},
|
||||||
|
"subjectToAvailability": "Onder voorbehoud van beschikbaarheid voor uw gemak",
|
||||||
|
"@receiveExclusiveSeasonalOffers": {},
|
||||||
|
"receiveExclusiveSeasonalOffers": "Ontvang exclusieve seizoensaanbiedingen",
|
||||||
|
"@designedSpeciallyForCityCards": {},
|
||||||
|
"designedSpeciallyForCityCards": "Speciaal ontworpen voor CityCards-reizigers",
|
||||||
|
"@itsJustOneMoreWay": {},
|
||||||
|
"itsJustOneMoreWay": "Het is nog een andere manier waarop",
|
||||||
|
"@makesExploring": {},
|
||||||
|
"makesExploring": " verkennen",
|
||||||
|
"@simplerKeyword": {},
|
||||||
|
"simplerKeyword": " eenvoudiger",
|
||||||
|
"@moreRewarding": {},
|
||||||
|
"moreRewarding": " lonender maakt",
|
||||||
|
"@getYourCityCardsToday": {},
|
||||||
|
"getYourCityCardsToday": "Ontvang vandaag nog uw CityCards",
|
||||||
|
"shareSubject": "Bekijk dit",
|
||||||
|
"scanAtAttractionSite": "Scan dit op de locatie van de attractie",
|
||||||
|
"codeCopied": "Code gekopieerd naar klembord",
|
||||||
|
"checkedIn": "Ingecheckt",
|
||||||
|
"checkIn": "Inchecken",
|
||||||
|
"problemsRedeemingPass": "Problemen met het inwisselen van de pas? ",
|
||||||
|
"clickHere": "Klik hier",
|
||||||
|
"about": "Over",
|
||||||
|
"howToMakeBooking": "Hoe maak je een boeking?",
|
||||||
|
"contactNumber": "Contactnummer",
|
||||||
|
"tapToCall": "Tik om te bellen",
|
||||||
|
"email": "E-mail",
|
||||||
|
"tapToEmail": "Tik om te e-mailen",
|
||||||
|
"datesNotAvailable": "Data niet beschikbaar",
|
||||||
|
"viaCityCards": "Via CityCards",
|
||||||
|
"createBookingViaApp": "Maak een boeking via de app",
|
||||||
|
"whatIsIncluded": "Wat is inbegrepen",
|
||||||
|
"exactLocation": "Exacte locatie",
|
||||||
|
"viewLocationOnMap": "Bekijk de locatie op de kaart",
|
||||||
|
"peopleFrequentlyAsk": "Veelgestelde vragen",
|
||||||
|
"errorMessage": "Fout: {message}",
|
||||||
|
"suggestedAttractions": "Voorgestelde attracties",
|
||||||
|
"viewAllAttractions": "Bekijk alle attracties",
|
||||||
|
"recommendedOffers": "Aanbevolen aanbiedingen",
|
||||||
|
"viewAllOffers": "Bekijk alle aanbiedingen",
|
||||||
|
"learnAboutPolicies": "Meer informatie over het beleid",
|
||||||
|
"pricePerPerson": "${price}/persoon",
|
||||||
|
"priceNotAvailable": "Prijs niet beschikbaar",
|
||||||
|
"bookingRequired": "Reservering verplicht",
|
||||||
|
"adultsLabel": "Volwassenen",
|
||||||
|
"kidsLabel": "Kinderen",
|
||||||
|
"checkedInSuccessful": "Inchecken geslaagd",
|
||||||
|
"readyToCheckIn": "Klaar om in te checken?",
|
||||||
|
"entranceActivationNote": "Alleen activeren als u bij de ingang bent van ",
|
||||||
|
"minuteTimer": "{minutes} minuten timer",
|
||||||
|
"timerActivationWarning": "Eenmaal geactiveerd, is de pas {minutes} minuten geldig. Deze actie kan niet ongedaan worden gemaakt",
|
||||||
|
"activatePassNow": "Pas nu activeren",
|
||||||
|
"notAtEntranceYet": "Ik ben nog niet bij de ingang",
|
||||||
|
"howToRedeemTitle": "Hoe wissel ik mijn attractiepas in?",
|
||||||
|
"howToRedeemDescription": "Om uw attractiepas in te wisselen, toont u de QR-code bij de ingang. Onze medewerkers zullen deze scannen, zodat u toegang krijgt tot de wonderen binnenin. Geniet van uw avontuur bij ",
|
||||||
|
"contactSupport": "Contact opnemen met ondersteuning",
|
||||||
|
"passAttractionsTitle": "Pas Attracties",
|
||||||
|
"searchAttractionsHint": "Attracties zoeken...",
|
||||||
|
"noAttractionsFound": "Geen attracties gevonden",
|
||||||
|
"noAttractionsMatchSearch": "Geen attracties gevonden die overeenkomen met uw zoekopdracht",
|
||||||
|
"selectiveCardFilter": "Selectieve Kaart",
|
||||||
|
"offersWithCardTitle": "Aanbiedingen met {cardName}",
|
||||||
|
"searchOffersHint": "Aanbiedingen zoeken",
|
||||||
|
"noOffersFound": "Geen aanbiedingen gevonden",
|
||||||
|
"codeCopiedMessage": "Code gekopieerd: {code}",
|
||||||
|
"noDataAvailable": "Geen gegevens beschikbaar",
|
||||||
|
"userPossessive": "{firstName}'s",
|
||||||
|
"tripDetails": "REISDETAILS:",
|
||||||
|
"daysCount": "{count} Dagen",
|
||||||
|
"stopsCount": "{count} stops",
|
||||||
|
"adultsCount": "{count} volwassenen",
|
||||||
|
"kidsCount": "{count} kinderen",
|
||||||
|
"shareLabel": "Delen",
|
||||||
|
"preparingPdfToShare": "PDF voorbereiden om te delen...",
|
||||||
|
"myItinerarySubject": "Mijn reisschema - {title}",
|
||||||
|
"failedToShare": "Delen mislukt: {error}",
|
||||||
|
"downloadLabel": "Downloaden",
|
||||||
|
"downloadingLabel": "Downloaden...",
|
||||||
|
"downloadingPdf": "PDF downloaden...",
|
||||||
|
"dailyView": "Dagoverzicht",
|
||||||
|
"summary": "Samenvatting",
|
||||||
|
"dayNumberLabel": "Dag {number}",
|
||||||
|
"pleaseFillAllFields": "Vul alle velden in",
|
||||||
|
"enterValidEmail": "Voer een geldig e-mailadres in",
|
||||||
|
"enterValidPhoneForIsd": "Voer een geldig telefoonnummer in voor {isdCode}",
|
||||||
|
"failedToSubmitDetails": "Details verzenden mislukt",
|
||||||
|
"addDetailsTitle": "Details toevoegen",
|
||||||
|
"aboutRecipient": "Vertel ons over de ontvanger",
|
||||||
|
"firstNameLabelWithStar": "Voornaam *",
|
||||||
|
"firstNameHint": "Voer de voornaam van de ontvanger in",
|
||||||
|
"lastNameLabelWithStar": "Achternaam *",
|
||||||
|
"lastNameHint": "Voer de achternaam van de ontvanger in",
|
||||||
|
"emailLabelWithStar": "E-mail *",
|
||||||
|
"emailHint": "Voer het e-mailadres van de ontvanger in",
|
||||||
|
"phoneNumberLabelWithStar": "Telefoonnummer *",
|
||||||
|
"phoneNumberHint": "Voer telefoonnummer in",
|
||||||
|
"searchCountryHint": "Zoek land...",
|
||||||
|
"cityLabelWithStar": "Stad *",
|
||||||
|
"cityHint": "Voer de naam van de stad in",
|
||||||
|
"countryLabelWithStar": "Land *",
|
||||||
|
"countryHint": "Voer landnaam in",
|
||||||
|
"submittingLabel": "Verzenden...",
|
||||||
|
"yourAttractionTitle": "Uw attractie",
|
||||||
|
"perPersonSuffix": "/persoon",
|
||||||
|
"checkThisOut": "Bekijk dit eens",
|
||||||
|
"whatIsIncluded": "Wat is inbegrepen",
|
||||||
|
"peopleFrequentlyAsk": "Veelgestelde vragen",
|
||||||
|
"aboutTitle": "Over",
|
||||||
|
"exactLocation": "Exacte locatie",
|
||||||
|
"viewOnMap": "Bekijk de locatie op de kaart",
|
||||||
|
"selectImageSource": "Selecteer afbeeldingsbron",
|
||||||
|
"cameraLabel": "Camera",
|
||||||
|
"galleryLabel": "Galerij",
|
||||||
|
"failedToPickImage": "Afbeelding kiezen mislukt: {error}",
|
||||||
|
"userIdNotFound": "Gebruikers-ID niet gevonden",
|
||||||
|
"changeProfilePicture": "Profielfoto wijzigen",
|
||||||
|
"enterYourFirstName": "Voer uw voornaam in",
|
||||||
|
"firstNameRequired": "Voornaam is verplicht",
|
||||||
|
"enterYourLastName": "Voer uw achternaam in",
|
||||||
|
"lastNameRequired": "Achternaam is verplicht",
|
||||||
|
"enterYourEmail": "Voer uw e-mail in",
|
||||||
|
"enterYourPhoneNumber": "Voer uw telefoonnummer in",
|
||||||
|
"addressLabelWithStar": "Adres *",
|
||||||
|
"addressHint": "Voer adres handmatig in of tik om te zoeken",
|
||||||
|
"address2Optional": "Adres 2 (Optioneel)",
|
||||||
|
"address2Hint": "Voer aanvullende adresgegevens in",
|
||||||
|
"zipCodeLabelWithStar": "Postcode *",
|
||||||
|
"zipCodeHint": "Voer de postcode in waar u woont",
|
||||||
|
"enterYourCity": "Voer de naam van uw stad in",
|
||||||
|
"stateLabelWithStar": "Provincie *",
|
||||||
|
"enterYourState": "Voer uw provincie in",
|
||||||
|
"enterYourCountry": "Voer uw land in",
|
||||||
|
"editProfileTitle": "Profiel bewerken",
|
||||||
|
"personalDetails": "Persoonlijke gegevens",
|
||||||
|
"locationDetails": "Locatiegegevens",
|
||||||
|
"cancelTitle": "Annuleren",
|
||||||
|
"saveTitle": "Opslaan",
|
||||||
|
"contactUsTitle": "Contact",
|
||||||
|
"contactUsMessage": "U kunt contact met ons opnemen via de onderstaande platforms. Ons team neemt spoedig contact met u op",
|
||||||
|
"customerSupport": "Klantenservice",
|
||||||
|
"contactNumberLabel": "Telefoonnummer",
|
||||||
|
"tapToCall": "Tik om te bellen",
|
||||||
|
"emailLabel": "E-mail",
|
||||||
|
"tapToEmail": "Tik om te mailen",
|
||||||
|
"locationLabel": "Locatie",
|
||||||
|
"enterYourEmailAddress": "Voer uw e-mailadres in",
|
||||||
|
"emailRequired": "E-mail is verplicht",
|
||||||
|
"enterValidEmail": "Voer een geldig e-mailadres in",
|
||||||
|
"phoneNumberRequired": "Telefoonnummer is verplicht",
|
||||||
|
"descriptionLabelWithStar": "Beschrijving *",
|
||||||
|
"writeMessageHint": "Schrijf hier uw bericht",
|
||||||
|
"submitTicket": "Ticket indienen",
|
||||||
|
"termsAndConditionsTitle": "Algemene voorwaarden",
|
||||||
|
"privacyPolicyTitle": "Privacybeleid",
|
||||||
|
"faqTitle": "Veelgestelde vragen",
|
||||||
|
"noTermsContent": "Geen inhoud voor algemene voorwaarden beschikbaar.",
|
||||||
|
"noPrivacyContent": "Geen inhoud voor privacybeleid beschikbaar.",
|
||||||
|
"retryLabel": "Opnieuw proberen",
|
||||||
|
"errorLabel": "Fout",
|
||||||
|
"offersWithCard": "Aanbiedingen met {cardName} Card",
|
||||||
|
"@offersWithCard": {
|
||||||
|
"placeholders": {
|
||||||
|
"cardName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"searchOffersHint": "Zoek aanbiedingen",
|
||||||
|
"noOffersFound": "Geen aanbiedingen gevonden",
|
||||||
|
"aboutPartner": "Over {partnerName}",
|
||||||
|
"@aboutPartner": {
|
||||||
|
"placeholders": {
|
||||||
|
"partnerName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howToMakeBooking": "Hoe maak je een boeking?",
|
||||||
|
"bookingStep1": "Controleer de vervaldatum van uw kortingsbon om er zeker van te zijn dat deze nog geldig is.",
|
||||||
|
"bookingStep2": "Bezoek de winkel of website waar de kortingsbon kan worden verzilverd.",
|
||||||
|
"bookingStep3": "Voeg bij online winkelen artikelen toe aan uw winkelwagentje en ga naar de kassa.",
|
||||||
|
"bookingStep4": "Zoek tijdens het afrekenen naar een veld met de tekst 'Kortingscode' of 'Promotiecode'.",
|
||||||
|
"bookingStep5": "Voer uw kortingscode exact in zoals deze verschijnt, inclusief eventuele speciale tekens.",
|
||||||
|
"couponCodeCopied": "Kortingscode gekopieerd!",
|
||||||
|
"createYourAccount": "Maak uw account aan",
|
||||||
|
"personalInformation": "Persoonlijke informatie",
|
||||||
|
"locationDetailsWithStar": "Locatiegegevens *",
|
||||||
|
"enterYourAddress": "Voer uw adres in",
|
||||||
|
"autoFillZipNote": "Stad, provincie en land worden automatisch ingevuld op basis van de postcode",
|
||||||
|
"creatingLabelEllipsis": "Bezig met maken...",
|
||||||
|
"createAccount": "Account aanmaken",
|
||||||
|
"getStarted": "Aan de slag",
|
||||||
|
"enterEmailToBegin": "Voer uw e-mailadres in om uw CityCards-reis te beginnen",
|
||||||
|
"emailExampleHint": "john.doe@gmail.com",
|
||||||
|
"pleaseEnterEmail": "Voer uw e-mailadres in",
|
||||||
|
"sendingLabel": "Verzenden...",
|
||||||
|
"otpVerifiedSuccess": "OTP succesvol geverifieerd!",
|
||||||
|
"completeProfileNote": "Vul uw profiel aan",
|
||||||
|
"otpResentSuccess": "OTP succesvol opnieuw verzonden!",
|
||||||
|
"verifyYourPhone": "Verifieer uw telefoon",
|
||||||
|
"enterVerificationCodeNote": "Voer de verificatiecode in die naar uw e-mailadres is verzonden",
|
||||||
|
"resendingLabel": "Opnieuw verzenden...",
|
||||||
|
"resendOtpLabel": "OTP opnieuw verzenden",
|
||||||
|
"resendOtpWithTimer": "OTP opnieuw verzenden ({seconds}s)",
|
||||||
|
"pleaseEnterCompleteOtp": "Voer de volledige OTP in",
|
||||||
|
"verifyingLabel": "Verifiëren...",
|
||||||
|
"ok": "OK",
|
||||||
|
"uploadAPhoto": "Upload een foto",
|
||||||
|
"orLabel": "OF",
|
||||||
|
"takeAPhoto": "Neem een foto",
|
||||||
|
"uploadAgain": "Opnieuw uploaden",
|
||||||
|
"nextTitle": "Volgende",
|
||||||
|
"addImage": "Afbeelding toevoegen",
|
||||||
|
"addAFilter": "Filter toevoegen",
|
||||||
|
"chooseFavoriteFilter": "Kies je favoriete filter en verbeter je ansichtkaart.",
|
||||||
|
"filterOriginal": "Origineel",
|
||||||
|
"filterBW": "Zwart-wit",
|
||||||
|
"filterSepia": "Sepia",
|
||||||
|
"filterVintage": "Vintage",
|
||||||
|
"filterCoolTone": "Koele tint",
|
||||||
|
"filterContrast": "Contrast",
|
||||||
|
"filterSoftGlow": "Zachte gloed",
|
||||||
|
"writeYourMessage": "Schrijf je bericht",
|
||||||
|
"failedToFetchEditDetails": "Fout bij ophalen van bewerkingsgegevens",
|
||||||
|
"saveChanges": "Wijzigingen opslaan",
|
||||||
|
"writeAMessage": "Schrijf een bericht",
|
||||||
|
"addYourMessageHere": "Voeg hier je bericht toe",
|
||||||
|
"fontDefault": "Standaard",
|
||||||
|
"previewYourPostcard": "Bekijk je ansichtkaart",
|
||||||
|
"flipLabel": "Omdraaien",
|
||||||
|
"buyPostcardForMyself": "Ansichtkaart voor mezelf kopen",
|
||||||
|
"editDetailsLabel": "Gegevens bewerken",
|
||||||
|
"giftPostcardForSomeoneElse": "Ansichtkaart cadeau doen aan iemand anders",
|
||||||
|
"addTitleLabel": "Titel toevoegen",
|
||||||
|
"enterTitleHint": "Voer titel in",
|
||||||
|
"pleaseEnterTitle": "Voer een titel in",
|
||||||
|
"yourDetailsTitle": "Jouw gegevens",
|
||||||
|
"senderDetailsDescription": "Voer je gegevens in als afzender van deze ansichtkaart",
|
||||||
|
"fullNameLabel": "Volledige naam",
|
||||||
|
"enterFullNameHint": "Voer de volledige naam in",
|
||||||
|
"cityLabel": "Stad",
|
||||||
|
"enterCityHint": "Voer de naam van je stad in",
|
||||||
|
"countryLabel": "Land",
|
||||||
|
"enterCountryHint": "Voer je land in",
|
||||||
|
"recipientDetailsTitle": "Gegevens ontvanger",
|
||||||
|
"recipientDetailsDescription": "Voer het adres in van de persoon die deze ansichtkaart zal ontvangen",
|
||||||
|
"selfDetailsDescription": "Voer je contactgegevens in voor deze ansichtkaart.",
|
||||||
|
"recipientNameLabel": "Naam ontvanger",
|
||||||
|
"enterRecipientNameHint": "Voer de naam van de ontvanger in",
|
||||||
|
"addressLabel": "Adres",
|
||||||
|
"enterRecipientAddressHint": "Voer het adres van de ontvanger in",
|
||||||
|
"zipCodeLabel": "Postcode",
|
||||||
|
"enterZipCodeHint": "Voer de postcode in waar je woont",
|
||||||
|
"pleaseEnterZipCode": "Voer de postcode in",
|
||||||
|
"stateLabel": "Provincie",
|
||||||
|
"enterStateHint": "Voer je provincie in",
|
||||||
|
"pleaseEnterField": "Voer {fieldName} in",
|
||||||
|
"invalidEmailError": "Voer een geldig e-mailadres in",
|
||||||
|
"onlyNumbersAllowed": "Alleen cijfers zijn toegestaan",
|
||||||
|
"mobileNumberLengthError": "Mobiel nummer moet {length} cijfers bevatten",
|
||||||
|
"onlyLettersAllowed": "Alleen letters zijn toegestaan",
|
||||||
|
"spacesNotAllowed": "Spaties zijn niet toegestaan",
|
||||||
|
"specialCharsNotAllowed": "Speciale tekens zijn niet toegestaan",
|
||||||
|
"saveAsDraftLabel": "Opslaan als concept",
|
||||||
|
"deliveryAddressLabel": "Bezorgadres",
|
||||||
|
"paymentSummaryLabel": "Overzicht betaling",
|
||||||
|
"grandTotalLabel": "Eindtotaal",
|
||||||
|
"paymentFailedError": "Betaling mislukt: {error}",
|
||||||
|
"paymentInitializationFailedError": "Fout: Initialisatie van betaling mislukt",
|
||||||
|
"draftSavedSuccess": "Concept succesvol opgeslagen!",
|
||||||
|
"payAmountLabel": "Betaal ${amount}",
|
||||||
|
"orderPlacedSuccess": "Bestelling succesvol geplaatst!",
|
||||||
|
"orderPlacedDescription": "Je bestelling is geplaatst. Je bestellingsnummer is {orderId}",
|
||||||
|
"deliveryTimelineNote": "Het wordt binnen 2-3 werkdagen bezorgd.",
|
||||||
|
"goToMyOrdersLabel": "Ga naar Mijn Bestellingen",
|
||||||
|
"messagePreviewLabel": "Voorbeeld Bericht",
|
||||||
|
"postcardLabel": "Ansichtkaart",
|
||||||
|
"homeTitle": "CityCards.\nZie meer,\nBesteed minder.",
|
||||||
|
"homeSubtitle": "Directe QR-toegang tot 40+ attracties,\nexclusieve voordelen en besparingen tot 30%",
|
||||||
|
"getYourCityCards": "Haal je CityCards",
|
||||||
|
"exploreCitiesTitleExplore": "Ontdek ",
|
||||||
|
"exploreCitiesTitleCities": "Steden",
|
||||||
|
"upcomingCitiesTitleUpcoming": "Komende ",
|
||||||
|
"upcomingCitiesTitleCities": "Steden",
|
||||||
|
"exploreDescription": "Verken je droombestemming en ervaar diverse attracties.",
|
||||||
|
"noCitiesAvailable": "Geen steden beschikbaar",
|
||||||
|
"notAvailableLabel": "N/A",
|
||||||
|
"editPostcardTitle": "Ansichtkaart bewerken",
|
||||||
|
"uploadImageTitle": "Afbeelding uploaden",
|
||||||
|
"editPostcardUploadDescription": "Bewerk je eigen unieke ansichtkaarten door afbeeldingen te uploaden die je onvergetelijke momenten vastleggen.",
|
||||||
|
"editFiltersLabel": "Filters bewerken",
|
||||||
|
"editTitleLabel": "Titel bewerken ",
|
||||||
|
"editTitleDescription": "Geef een andere titel aan je ansichtkaart",
|
||||||
|
"titleMaxLengthError": "Titel mag maximaal 10 letters bevatten",
|
||||||
|
"editMessageLabel": "Bericht bewerken ",
|
||||||
|
"editMessageDescription": "Bewerk je eigen unieke ansichtkaarten om je onvergetelijke momenten te koesteren.",
|
||||||
|
"fullNameLabelWithStar": "Volledige naam *",
|
||||||
|
"recipientLabelWithStar": "Ontvanger *",
|
||||||
|
"pleaseEnterMessage": "Voer een bericht in",
|
||||||
|
"messageMaxLengthError": "Bericht mag maximaal 400 tekens bevatten",
|
||||||
|
"fontPatrickHand": "Patrick Hand",
|
||||||
|
"fontIndieFlower": "Indie Flower",
|
||||||
|
"fontGloriaHallelujah": "Gloria Hallelujah"
|
||||||
|
}
|
||||||
@@ -88,9 +88,9 @@ class LocalPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reset onboarding (for logout / testing)
|
/// Reset onboarding (for logout / testing)
|
||||||
static Future<void> resetOnboarding() async {
|
// static Future<void> resetOnboarding() async {
|
||||||
await updateOnboardingPage(0);
|
// await updateOnboardingPage(0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Set login state
|
/// Set login state
|
||||||
static Future<void> setLogin(bool value) async {
|
static Future<void> setLogin(bool value) async {
|
||||||
@@ -182,6 +182,22 @@ class LocalPreference {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear only access token (keep refresh token)
|
||||||
|
static Future<void> clearAccessToken() async {
|
||||||
|
final db = await LocalDatabase().database;
|
||||||
|
|
||||||
|
await db.update(
|
||||||
|
'user_tokens',
|
||||||
|
{'access_token': ''}, // ← empty string, not null
|
||||||
|
where: 'id = ?',
|
||||||
|
whereArgs: [1],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('🧹 [LOCAL_PREF] Access token cleared');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get refresh token
|
/// Get refresh token
|
||||||
static Future<String?> getRefreshToken() async {
|
static Future<String?> getRefreshToken() async {
|
||||||
final db = await LocalDatabase().database;
|
final db = await LocalDatabase().database;
|
||||||
@@ -522,8 +538,7 @@ class LocalPreference {
|
|||||||
await clearTokens();
|
await clearTokens();
|
||||||
await clearUserDetails();
|
await clearUserDetails();
|
||||||
await clearPassCart();// optional
|
await clearPassCart();// optional
|
||||||
await clearProfileImage();// optional
|
await clearProfileImage();
|
||||||
await clearPassCart();// optional
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> clearAllData() async {
|
static Future<void> clearAllData() async {
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:citycards_customer/login/repository/login_repository.dart';
|
import 'package:citycards_customer/login/repository/login_repository.dart';
|
||||||
import '../../../create_account/models/create_account_model.dart';
|
import '../../../create_account/models/create_account_model.dart';
|
||||||
import '../../../localPreference/local_preference.dart';
|
import '../../../localPreference/local_preference.dart';
|
||||||
import 'verify_event.dart';
|
import 'verify_event.dart';
|
||||||
import 'verify_state.dart';
|
import 'verify_state.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
class VerifyOtpBloc extends Bloc<VerifyOtpEvent, VerifyOtpState> {
|
class VerifyOtpBloc extends Bloc<VerifyOtpEvent, VerifyOtpState> {
|
||||||
final LoginRepository _loginRepository;
|
final LoginRepository _loginRepository;
|
||||||
|
|
||||||
VerifyOtpBloc({required LoginRepository loginRepository})
|
VerifyOtpBloc({required LoginRepository loginRepository})
|
||||||
: _loginRepository = loginRepository,
|
: _loginRepository = loginRepository,
|
||||||
super(VerifyOtpInitial()) {
|
super(ResendOtpCooldown(secondsRemaining: 60)) { // start with cooldown
|
||||||
on<VerifyEmailOtpEvent>(_onVerifyEmailOtp);
|
on<VerifyEmailOtpEvent>(_onVerifyEmailOtp);
|
||||||
on<ResendOtpEvent>(_onResendOtp);
|
on<ResendOtpEvent>(_onResendOtp);
|
||||||
|
on<StartResendTimerEvent>(_onStartResendTimer);
|
||||||
|
|
||||||
|
add(StartResendTimerEvent()); // auto-start on bloc creation
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onStartResendTimer(
|
||||||
|
StartResendTimerEvent event,
|
||||||
|
Emitter<VerifyOtpState> emit,
|
||||||
|
) async {
|
||||||
|
for (int i = 60; i >= 0; i--) {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
if (isClosed) return;
|
||||||
|
emit(ResendOtpCooldown(secondsRemaining: i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onVerifyEmailOtp(
|
Future<void> _onVerifyEmailOtp(
|
||||||
@@ -25,7 +40,6 @@ class VerifyOtpBloc extends Bloc<VerifyOtpEvent, VerifyOtpState> {
|
|||||||
emailAddress: event.emailAddress,
|
emailAddress: event.emailAddress,
|
||||||
otp: event.otp,
|
otp: event.otp,
|
||||||
);
|
);
|
||||||
|
|
||||||
final userModel = UserRegisteredModel.fromJson(response);
|
final userModel = UserRegisteredModel.fromJson(response);
|
||||||
await LocalPreference.setTokens(
|
await LocalPreference.setTokens(
|
||||||
accessToken: userModel.accessToken,
|
accessToken: userModel.accessToken,
|
||||||
@@ -58,6 +72,7 @@ class VerifyOtpBloc extends Bloc<VerifyOtpEvent, VerifyOtpState> {
|
|||||||
emailAddress: event.emailAddress,
|
emailAddress: event.emailAddress,
|
||||||
);
|
);
|
||||||
emit(ResendOtpSuccess(response: response));
|
emit(ResendOtpSuccess(response: response));
|
||||||
|
add(StartResendTimerEvent()); // restart countdown after resend
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(VerifyOtpError(errorMessage: e.toString()));
|
emit(VerifyOtpError(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ class ResendOtpEvent extends VerifyOtpEvent {
|
|||||||
final String emailAddress;
|
final String emailAddress;
|
||||||
|
|
||||||
ResendOtpEvent({required this.emailAddress});
|
ResendOtpEvent({required this.emailAddress});
|
||||||
}
|
}
|
||||||
|
class StartResendTimerEvent extends VerifyOtpEvent {}
|
||||||
@@ -24,4 +24,9 @@ class VerifyOtpError extends VerifyOtpState {
|
|||||||
final String errorMessage;
|
final String errorMessage;
|
||||||
|
|
||||||
VerifyOtpError({required this.errorMessage});
|
VerifyOtpError({required this.errorMessage});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResendOtpCooldown extends VerifyOtpState {
|
||||||
|
final int secondsRemaining;
|
||||||
|
ResendOtpCooldown({required this.secondsRemaining});
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../../common_packages/custom_snackbar.dart';
|
import '../../common_packages/custom_snackbar.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../bloc/login/login_bloc.dart';
|
import '../bloc/login/login_bloc.dart';
|
||||||
import '../bloc/login/login_event.dart';
|
import '../bloc/login/login_event.dart';
|
||||||
import '../bloc/login/login_state.dart';
|
import '../bloc/login/login_state.dart';
|
||||||
@@ -53,6 +54,7 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return BlocListener<LoginBloc, LoginState>(
|
return BlocListener<LoginBloc, LoginState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is LoginError) {
|
if (state is LoginError) {
|
||||||
@@ -83,13 +85,13 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Get Started",
|
text: l10n.getStarted,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
SizedBox(height: 42.h),
|
SizedBox(height: 42.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Enter your email to begin your CityCards journey",
|
text: l10n.enterEmailToBegin,
|
||||||
size: 14.sp,
|
size: 14.sp,
|
||||||
color: const Color(0xFF000000).withOpacity(.6),
|
color: const Color(0xFF000000).withOpacity(.6),
|
||||||
),
|
),
|
||||||
@@ -124,7 +126,7 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
Icons.email_outlined,
|
Icons.email_outlined,
|
||||||
color: Color(0xFFF95F62),
|
color: Color(0xFFF95F62),
|
||||||
),
|
),
|
||||||
hintText: "john.doe@gmail.com",
|
hintText: l10n.emailExampleHint,
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
color: const Color(0xFF000000).withOpacity(0.6),
|
color: const Color(0xFF000000).withOpacity(0.6),
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
@@ -148,7 +150,7 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
if (email.isEmpty) {
|
if (email.isEmpty) {
|
||||||
CustomSnackbar.showError(
|
CustomSnackbar.showError(
|
||||||
context,
|
context,
|
||||||
message: "Please enter your email address",
|
message: l10n.pleaseEnterEmail,
|
||||||
useOverlay: true,
|
useOverlay: true,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -157,7 +159,7 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
if (!isValidEmail(email)) {
|
if (!isValidEmail(email)) {
|
||||||
CustomSnackbar.showError(
|
CustomSnackbar.showError(
|
||||||
context,
|
context,
|
||||||
message: "Please enter a valid email address",
|
message: l10n.enterValidEmail,
|
||||||
useOverlay: true,
|
useOverlay: true,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -188,7 +190,7 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: isLoading ? "Sending..." : "Continue",
|
label: isLoading ? l10n.sendingLabel : l10n.continueTitle,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import 'package:citycards_customer/profile/bloc/profile/profile_bloc.dart';
|
|||||||
import 'package:citycards_customer/profile/bloc/profile/profile_event.dart';
|
import 'package:citycards_customer/profile/bloc/profile/profile_event.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
|
import 'package:pinput/pinput.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import '../../cart/blocs/myPassCart/my_pass_cart_event.dart';
|
import '../../cart/blocs/myPassCart/my_pass_cart_event.dart';
|
||||||
import '../../cart/blocs/myPostcardsCart/my_postcards_cart_bloc.dart';
|
import '../../cart/blocs/myPostcardsCart/my_postcards_cart_bloc.dart';
|
||||||
import '../../common_packages/custom_snackbar.dart';
|
import '../../common_packages/custom_snackbar.dart';
|
||||||
import '../../core/route_constants.dart';
|
import '../../core/route_constants.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../../localPreference/local_preference.dart';
|
import '../../localPreference/local_preference.dart';
|
||||||
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
||||||
import '../bloc/verify/verify_bloc.dart';
|
import '../bloc/verify/verify_bloc.dart';
|
||||||
@@ -38,6 +39,7 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return BlocListener<VerifyOtpBloc, VerifyOtpState>(
|
return BlocListener<VerifyOtpBloc, VerifyOtpState>(
|
||||||
listener: (context, state) async {
|
listener: (context, state) async {
|
||||||
if (state is VerifyOtpSuccess) {
|
if (state is VerifyOtpSuccess) {
|
||||||
@@ -61,8 +63,8 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
// User exists - navigate to home/dashboard
|
// User exists - navigate to home/dashboard
|
||||||
// Navigator.of(context).pushReplacementNamed(RouteConstants.home);
|
// Navigator.of(context).pushReplacementNamed(RouteConstants.home);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('OTP verified successfully!'),
|
content: Text(l10n.otpVerifiedSuccess),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -70,28 +72,29 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
// User doesn't exist - navigate to create account
|
// User doesn't exist - navigate to create account
|
||||||
Navigator.of(context).pushNamed(RouteConstants.createAcct,arguments: widget.emailAddress);
|
Navigator.of(context).pushNamed(RouteConstants.createAcct,arguments: widget.emailAddress);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Please complete your profile'),
|
content: Text(l10n.completeProfileNote),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (state is ResendOtpSuccess) {
|
} else if (state is ResendOtpSuccess) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('OTP resent successfully!'),
|
content: Text(l10n.otpResentSuccess),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
CustomSnackbar.showSuccess(
|
CustomSnackbar.showSuccess(
|
||||||
context,
|
context,
|
||||||
message: 'OTP resent successfully!',
|
message: l10n.otpResentSuccess,
|
||||||
useOverlay: true, // Use overlay to show above bottom sheet
|
useOverlay: true, // Use overlay to show above bottom sheet
|
||||||
);
|
);
|
||||||
} else if (state is VerifyOtpError) {
|
} else if (state is VerifyOtpError) {
|
||||||
CustomSnackbar.showError(
|
CustomSnackbar.showError(
|
||||||
context,
|
context,
|
||||||
message: state.errorMessage,
|
message: "Please enter valid OTP",
|
||||||
|
// message: state.errorMessage,
|
||||||
useOverlay: true, // Use overlay to show above bottom sheet
|
useOverlay: true, // Use overlay to show above bottom sheet
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -112,7 +115,7 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
|
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
CustomText(
|
CustomText(
|
||||||
text: "Verify your phone",
|
text: l10n.verifyYourPhone,
|
||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -121,7 +124,7 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "Enter the verification code sent to your email id",
|
text: l10n.enterVerificationCodeNote,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: Colors.black.withOpacity(0.6),
|
color: Colors.black.withOpacity(0.6),
|
||||||
@@ -139,25 +142,82 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 15.h),
|
SizedBox(height: 15.h),
|
||||||
OtpTextField(
|
Pinput(
|
||||||
numberOfFields: 6,
|
length: 6,
|
||||||
borderWidth: 0.4.w,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
fieldWidth: 48.w,
|
defaultPinTheme: PinTheme(
|
||||||
fieldHeight: 60.h,
|
width: 48.w,
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
height: 60.h,
|
||||||
filled: true,
|
textStyle: TextStyle(
|
||||||
fillColor: const Color(0xFFFFF5F5),
|
fontSize: 18.sp,
|
||||||
borderColor: const Color(0xFFBB474A),
|
fontWeight: FontWeight.w500,
|
||||||
cursorColor: const Color(0xFFF95F62),
|
color: Colors.black,
|
||||||
showFieldAsBox: true,
|
),
|
||||||
textStyle: TextStyle(
|
decoration: BoxDecoration(
|
||||||
fontSize: 18.sp,
|
color: const Color(0xFFFFF5F5),
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFBB474A).withOpacity(0.4),
|
||||||
|
width: 1.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onCodeChanged: (code) {
|
focusedPinTheme: PinTheme(
|
||||||
|
width: 48.w,
|
||||||
|
height: 60.h,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
width: 1.5.w,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xFFF95F62).withOpacity(0.1),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
submittedPinTheme: PinTheme(
|
||||||
|
width: 48.w,
|
||||||
|
height: 60.h,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFF5F5),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFBB474A),
|
||||||
|
width: 1.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cursor: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(bottom: 9.h),
|
||||||
|
width: 20.w,
|
||||||
|
height: 1.h,
|
||||||
|
color: const Color(0xFFF95F62),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onChanged: (code) {
|
||||||
_otpCode = code;
|
_otpCode = code;
|
||||||
},
|
},
|
||||||
onSubmit: (code) {
|
onCompleted: (code) {
|
||||||
_otpCode = code;
|
_otpCode = code;
|
||||||
debugPrint("OTP entered: $code");
|
debugPrint("OTP entered: $code");
|
||||||
},
|
},
|
||||||
@@ -166,20 +226,25 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
BlocBuilder<VerifyOtpBloc, VerifyOtpState>(
|
BlocBuilder<VerifyOtpBloc, VerifyOtpState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final isResending = state is ResendOtpLoading;
|
final isResending = state is ResendOtpLoading;
|
||||||
|
final isCooldown = state is ResendOtpCooldown && state.secondsRemaining > 0;
|
||||||
|
final canResend = !isResending && !isCooldown;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: isResending
|
onTap: canResend
|
||||||
? null
|
? () {
|
||||||
: () {
|
|
||||||
context.read<VerifyOtpBloc>().add(
|
context.read<VerifyOtpBloc>().add(
|
||||||
ResendOtpEvent(emailAddress: widget.emailAddress),
|
ResendOtpEvent(emailAddress: widget.emailAddress),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
: null,
|
||||||
child: Text(
|
child: Text(
|
||||||
isResending ? "Resending..." : "Resend OTP",
|
isResending
|
||||||
|
? l10n.resendingLabel
|
||||||
|
: isCooldown
|
||||||
|
? l10n.resendOtpWithTimer((state as ResendOtpCooldown).secondsRemaining)
|
||||||
|
: l10n.resendOtpLabel,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isResending
|
color: canResend ? const Color(0xFFF95F62) : Colors.grey,
|
||||||
? Colors.grey
|
|
||||||
: const Color(0xFFF95F62),
|
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
@@ -197,8 +262,8 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
|
|
||||||
if (_otpCode.isEmpty || _otpCode.length < 6) {
|
if (_otpCode.isEmpty || _otpCode.length < 6) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(
|
||||||
content: Text('Please enter complete OTP'),
|
content: Text(l10n.pleaseEnterCompleteOtp),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -212,39 +277,11 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label: isLoading ? "Verifying..." : "Continue",
|
label: isLoading ? l10n.verifyingLabel : l10n.continueTitle,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// SizedBox(height: 20.h),
|
|
||||||
// InkWell(
|
|
||||||
// onTap: () {
|
|
||||||
// Navigator.of(context).pushNamed(RouteConstants.createAcct);
|
|
||||||
// },
|
|
||||||
// child: Text.rich(
|
|
||||||
// TextSpan(
|
|
||||||
// children: [
|
|
||||||
// TextSpan(
|
|
||||||
// text: "Already have an account?",
|
|
||||||
// style: TextStyle(
|
|
||||||
// color: Colors.black.withOpacity(0.6),
|
|
||||||
// fontSize: 12.sp,
|
|
||||||
// fontWeight: FontWeight.w400,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// TextSpan(
|
|
||||||
// text: " Sign in",
|
|
||||||
// style: TextStyle(
|
|
||||||
// color: const Color(0xFFF95F62),
|
|
||||||
// fontSize: 12.sp,
|
|
||||||
// fontWeight: FontWeight.w600,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
SizedBox(height: 15.h),
|
SizedBox(height: 15.h),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||