4th commit
@@ -2,7 +2,7 @@
|
||||
<application
|
||||
android:label="citycards_partner_flutter"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
android/app/src/main/res/drawable-night-v21/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
BIN
android/app/src/main/res/drawable-night/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
BIN
android/app/src/main/res/drawable-v21/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
BIN
android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 627 KiB |
BIN
android/app/src/main/res/drawable/background.png
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
19
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
19
android/app/src/main/res/values-v31/styles.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
BIN
assets/app/App_logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
assets/app/profile.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/onboarding/bg.png
Normal file
|
After Width: | Height: | Size: 629 KiB |
|
Before Width: | Height: | Size: 543 KiB |
|
Before Width: | Height: | Size: 6.1 MiB After Width: | Height: | Size: 615 KiB |
BIN
assets/recurring/Entertainment.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/recurring/culture.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/recurring/food.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/recurring/lets-icons_date-fill.png
Normal file
|
After Width: | Height: | Size: 778 B |
BIN
assets/recurring/musuem.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/recurring/outdoor.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/recurring/tourist.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/scan/flash.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/scan/flash_on.png
Normal file
|
After Width: | Height: | Size: 989 B |
BIN
assets/scan/history.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/scan/logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/scan/menu.png
Normal file
|
After Width: | Height: | Size: 784 B |
BIN
assets/scan/mobile.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
assets/scan/page.png
Normal file
|
After Width: | Height: | Size: 794 B |
BIN
assets/scan/profile.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/scan/qr.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
assets/scan/setting.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/scan/support.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/scan/ticket-fill.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/scan/ticket.png
Normal file
|
After Width: | Height: | Size: 932 B |
BIN
assets/scan/ticket_qr.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/splash/bg.png
Normal file
|
After Width: | Height: | Size: 5.6 MiB |
BIN
assets/splash/logo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/splash_screen/Screen.png
Normal file
|
After Width: | Height: | Size: 781 KiB |
BIN
assets/support/icon/upload.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
assets/ticket/dubai.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
assets/ticket/lets-icons_date-fill.png
Normal file
|
After Width: | Height: | Size: 688 B |
BIN
assets/time/image.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/time/lets-icons_date-fill.png
Normal file
|
After Width: | Height: | Size: 778 B |
BIN
assets/time/mingcute_time-fill.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -428,7 +428,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
@@ -485,7 +485,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
|
||||
@@ -1,122 +1 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 551 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 821 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.6 KiB |
22
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "background.png",
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "darkbackground.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 383 KiB |
@@ -16,13 +16,19 @@
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
||||
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
||||
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
@@ -32,6 +38,7 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
<image name="LaunchImage" width="500" height="1052"/>
|
||||
<image name="LaunchBackground" width="1" height="1"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Citycards Partner Flutter</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>citycards_partner_flutter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Citycards Partner Flutter</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>citycards_partner_flutter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
306
lib/booking/views/add_time_slot_sheet.dart
Normal file
@@ -0,0 +1,306 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
// ------------------ Bloc ------------------
|
||||
abstract class TimeSlotEvent {}
|
||||
class IncrementCapacity extends TimeSlotEvent {}
|
||||
class DecrementCapacity extends TimeSlotEvent {}
|
||||
class SelectStartTime extends TimeSlotEvent {
|
||||
final TimeOfDay time;
|
||||
SelectStartTime(this.time);
|
||||
}
|
||||
class SelectEndTime extends TimeSlotEvent {
|
||||
final TimeOfDay time;
|
||||
SelectEndTime(this.time);
|
||||
}
|
||||
|
||||
class TimeSlotState {
|
||||
final int capacity;
|
||||
final TimeOfDay startTime;
|
||||
final TimeOfDay endTime;
|
||||
|
||||
TimeSlotState({
|
||||
required this.capacity,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
});
|
||||
|
||||
TimeSlotState copyWith({
|
||||
int? capacity,
|
||||
TimeOfDay? startTime,
|
||||
TimeOfDay? endTime,
|
||||
}) {
|
||||
return TimeSlotState(
|
||||
capacity: capacity ?? this.capacity,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeSlotBloc extends Bloc<TimeSlotEvent, TimeSlotState> {
|
||||
TimeSlotBloc()
|
||||
: super(TimeSlotState(
|
||||
capacity: 50,
|
||||
startTime: const TimeOfDay(hour: 9, minute: 0),
|
||||
endTime: const TimeOfDay(hour: 12, minute: 0),
|
||||
)) {
|
||||
on<IncrementCapacity>((event, emit) {
|
||||
emit(state.copyWith(capacity: state.capacity + 1));
|
||||
});
|
||||
on<DecrementCapacity>((event, emit) {
|
||||
if (state.capacity > 1) {
|
||||
emit(state.copyWith(capacity: state.capacity - 1));
|
||||
}
|
||||
});
|
||||
on<SelectStartTime>((event, emit) => emit(state.copyWith(startTime: event.time)));
|
||||
on<SelectEndTime>((event, emit) => emit(state.copyWith(endTime: event.time)));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ Dialog ------------------
|
||||
class AddTimeSlotDialog extends StatelessWidget {
|
||||
const AddTimeSlotDialog({super.key});
|
||||
|
||||
Future<TimeOfDay?> _pickTime(BuildContext context, TimeOfDay initialTime) async {
|
||||
return await showTimePicker(
|
||||
context: context,
|
||||
initialTime: initialTime,
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData(
|
||||
colorScheme: const ColorScheme.light(primary: Color(0xffF95F62)),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => TimeSlotBloc(),
|
||||
child: Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
width: 400,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: BlocBuilder<TimeSlotBloc, TimeSlotState>(
|
||||
builder: (context, state) {
|
||||
final bloc = context.read<TimeSlotBloc>();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
"Add Time Slots",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 24,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Slot Section
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF6F6F6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Slot 4",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16)),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: _timeBox(context, "Start Time", state.startTime, true, bloc)),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: _timeBox(context, "End Time", state.endTime, false, bloc)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Capacity Section
|
||||
Text("Capacity per Time Slot",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24,
|
||||
color: Colors.black)),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF6F6F6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_roundButton("-", () => bloc.add(DecrementCapacity())),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
Text("${state.capacity}",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18)),
|
||||
Text("people",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.grey, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
_roundButton("+", () => bloc.add(IncrementCapacity())),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Buttons
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: Text("Create Slot",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
color: Colors.white)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(
|
||||
color: Color(0xffF95F62), width: 1.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: Text("Cancel",
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xffF95F62),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _timeBox(BuildContext context, String label, TimeOfDay time,
|
||||
bool isStart, TimeSlotBloc bloc) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final picked = await _pickTime(context, time);
|
||||
if (picked != null) {
|
||||
if (isStart) {
|
||||
bloc.add(SelectStartTime(picked));
|
||||
} else {
|
||||
bloc.add(SelectEndTime(picked));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13, color: Colors.black87)),
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade400),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Text(
|
||||
time.format(context),
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w500, fontSize: 15),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _roundButton(String symbol, VoidCallback onTap) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x22F95F62),
|
||||
border: Border.all(color: const Color(0xffF95F62)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(symbol,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xffF95F62),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:citycards_partner_flutter/core/app_router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@@ -36,12 +37,17 @@ class BookingBottomSheet extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(formattedDate,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600, fontSize: 16)),
|
||||
Text(
|
||||
"${booking.attractions.length} attractions available",
|
||||
style: GoogleFonts.poppins(color: Colors.black54)),
|
||||
formattedDate,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${booking.attractions.length} attractions available",
|
||||
style: GoogleFonts.poppins(color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: RawScrollbar(
|
||||
@@ -55,84 +61,109 @@ class BookingBottomSheet extends StatelessWidget {
|
||||
// thumbColor: const MaterialStatePropertyAll(Color(0xffF95F62)),
|
||||
controller: scrollController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 20), child: ListView.builder(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: booking.attractions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final attraction = booking.attractions[index];
|
||||
final isExpanded = state.expandedAttractions.contains(index);
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(int.parse("0x22${attraction.colorHex.substring(1)}")),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(int.parse(
|
||||
"0xff${attraction.colorHex.substring(1)}")),
|
||||
shape: BoxShape.circle,
|
||||
final isExpanded = state.expandedAttractions
|
||||
.contains(index);
|
||||
return InkWell(
|
||||
onTap: (){
|
||||
Navigator.pushNamed(context, AppRouter.selectedTimeSlotPage);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(
|
||||
int.parse(
|
||||
"0x22${attraction.colorHex.substring(1)}",
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(
|
||||
int.parse(
|
||||
"0xff${attraction.colorHex.substring(1)}",
|
||||
),
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
attraction.name,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: List.generate(
|
||||
isExpanded
|
||||
? attraction.slots.length
|
||||
: (attraction.slots.length > 2
|
||||
? 2
|
||||
: attraction.slots.length),
|
||||
(i) => _slotCard(attraction.slots[i]),
|
||||
),
|
||||
),
|
||||
if (attraction.slots.length > 2)
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
bloc.add(ToggleSlotExpand(index)),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(
|
||||
8,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(attraction.name,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13)),
|
||||
],
|
||||
),
|
||||
const Icon(Icons.arrow_forward_ios, size: 14)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: List.generate(
|
||||
isExpanded
|
||||
? attraction.slots.length
|
||||
: (attraction.slots.length > 2
|
||||
? 2
|
||||
: attraction.slots.length),
|
||||
(i) => _slotCard(attraction.slots[i]),
|
||||
),
|
||||
),
|
||||
if (attraction.slots.length > 2)
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
bloc.add(ToggleSlotExpand(index)),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
isExpanded
|
||||
? "Show less"
|
||||
: "+${attraction.slots.length - 2} more",
|
||||
style: GoogleFonts.poppins(
|
||||
child: Text(
|
||||
isExpanded
|
||||
? "Show less"
|
||||
: "+${attraction.slots.length - 2} more",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black54,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -160,19 +191,21 @@ class BookingBottomSheet extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(slot.time,
|
||||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black87)),
|
||||
Text(
|
||||
slot.time,
|
||||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text("${slot.booked}/${slot.total}",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 11, color: Colors.black87)),
|
||||
child: Text(
|
||||
"${slot.booked}/${slot.total}",
|
||||
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black87),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:citycards_partner_flutter/core/app_router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@@ -34,21 +35,36 @@ class _BookingView extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
final bloc = context.read<BookingBloc>();
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
_buildMonthHeader(bloc, state),
|
||||
const SizedBox(height: 10),
|
||||
_buildCalendar(context, bloc, state),
|
||||
const SizedBox(height: 12),
|
||||
_buildAttractionLegend(),
|
||||
const SizedBox(height: 20),
|
||||
_buildRecurringButton(),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 40.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
_buildHeader(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildMonthHeader(bloc, state),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// 🗓️ Scrollable calendar section
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 350,
|
||||
child: _buildCalendar(context, bloc, state)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildAttractionLegend(),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_buildRecurringButton(context),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -56,41 +72,53 @@ class _BookingView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
// ---------------- HEADER ----------------
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0),
|
||||
child: CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white, size: 22),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0),
|
||||
child: CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
child:
|
||||
const Icon(Icons.arrow_back, color: Colors.white, size: 22),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"Booking",
|
||||
style: GoogleFonts.poppins(fontSize: 32, fontWeight: FontWeight.w600),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 25),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"Easily schedule and manage your bookings anytime,\nanywhere. Fast, simple, and secure.",
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
color: Colors.black,
|
||||
height: 1.4,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Text(
|
||||
"Easily schedule and manage your bookings anytime, anywhere. Fast, simple, and secure.",
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
color: Colors.black,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- MONTH HEADER ----------------
|
||||
Widget _buildMonthHeader(BookingBloc bloc, BookingState state) {
|
||||
final formatter = DateFormat("MMM yyyy");
|
||||
return Padding(
|
||||
@@ -100,8 +128,8 @@ class _BookingView extends StatelessWidget {
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final prevMonth = DateTime(
|
||||
state.focusedMonth.year, state.focusedMonth.month - 1, 1);
|
||||
final prevMonth =
|
||||
DateTime(state.focusedMonth.year, state.focusedMonth.month - 1, 1);
|
||||
bloc.add(LoadBookings(prevMonth));
|
||||
},
|
||||
child: _navButton(Icons.keyboard_arrow_left_rounded),
|
||||
@@ -115,8 +143,8 @@ class _BookingView extends StatelessWidget {
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final nextMonth = DateTime(
|
||||
state.focusedMonth.year, state.focusedMonth.month + 1, 1);
|
||||
final nextMonth =
|
||||
DateTime(state.focusedMonth.year, state.focusedMonth.month + 1, 1);
|
||||
bloc.add(LoadBookings(nextMonth));
|
||||
},
|
||||
child: _navButton(Icons.keyboard_arrow_right_rounded),
|
||||
@@ -138,135 +166,134 @@ class _BookingView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCalendar(BuildContext context, BookingBloc bloc, BookingState state) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: TableCalendar(
|
||||
firstDay: DateTime(2020),
|
||||
lastDay: DateTime(2030),
|
||||
focusedDay: state.focusedMonth,
|
||||
calendarFormat: CalendarFormat.month,
|
||||
headerVisible: false,
|
||||
availableGestures: AvailableGestures.none,
|
||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
weekdayStyle: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Colors.black54,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
weekendStyle: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Colors.black54,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
calendarStyle: CalendarStyle(
|
||||
outsideDaysVisible: false,
|
||||
markersMaxCount: 0,
|
||||
defaultTextStyle: GoogleFonts.poppins(color: Colors.black87,fontSize: 22),
|
||||
weekendTextStyle: GoogleFonts.poppins(color: Colors.black87),
|
||||
),
|
||||
eventLoader: (day) {
|
||||
return state.bookings
|
||||
.where((b) =>
|
||||
b.date.year == day.year &&
|
||||
b.date.month == day.month &&
|
||||
b.date.day == day.day)
|
||||
.toList();
|
||||
},
|
||||
calendarBuilders: CalendarBuilders(
|
||||
defaultBuilder: (context, date, _) {
|
||||
final bookings = state.bookings
|
||||
.where((b) =>
|
||||
b.date.year == date.year &&
|
||||
b.date.month == date.month &&
|
||||
b.date.day == date.day)
|
||||
.toList();
|
||||
// ---------------- CALENDAR ----------------
|
||||
Widget _buildCalendar(
|
||||
BuildContext context, BookingBloc bloc, BookingState state) {
|
||||
return TableCalendar(
|
||||
firstDay: DateTime(2020),
|
||||
lastDay: DateTime(2030),
|
||||
focusedDay: state.focusedMonth,
|
||||
calendarFormat: CalendarFormat.month,
|
||||
headerVisible: false,
|
||||
availableGestures: AvailableGestures.none,
|
||||
startingDayOfWeek: StartingDayOfWeek.monday,
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
weekdayStyle: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Colors.black54,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
weekendStyle: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Colors.black54,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
calendarStyle: CalendarStyle(
|
||||
outsideDaysVisible: false,
|
||||
markersMaxCount: 0,
|
||||
defaultTextStyle:
|
||||
GoogleFonts.poppins(color: Colors.black87, fontSize: 22),
|
||||
weekendTextStyle: GoogleFonts.poppins(color: Colors.black87),
|
||||
),
|
||||
eventLoader: (day) {
|
||||
return state.bookings
|
||||
.where((b) =>
|
||||
b.date.year == day.year &&
|
||||
b.date.month == day.month &&
|
||||
b.date.day == day.day)
|
||||
.toList();
|
||||
},
|
||||
calendarBuilders: CalendarBuilders(
|
||||
defaultBuilder: (context, date, _) {
|
||||
final bookings = state.bookings
|
||||
.where((b) =>
|
||||
b.date.year == date.year &&
|
||||
b.date.month == date.month &&
|
||||
b.date.day == date.day)
|
||||
.toList();
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (bookings.isNotEmpty) {
|
||||
bloc.add(SelectDate(date));
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BookingBottomSheet(
|
||||
date: date,
|
||||
booking: bookings.first,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (bookings.isNotEmpty && bookings.first.attractions.length > 3)
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 3, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF95F62),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
"+${bookings.first.attractions.length - 3}",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
"${date.day}",
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (bookings.isNotEmpty) {
|
||||
bloc.add(SelectDate(date));
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BookingBottomSheet(
|
||||
date: date,
|
||||
booking: bookings.first,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
(bookings.isNotEmpty && bookings.first.attractions.length > 3)
|
||||
? Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 3, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF95F62),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
"+${bookings.first.attractions.length - 3}",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (bookings.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...List.generate(
|
||||
bookings.first.attractions.length > 3
|
||||
? 3
|
||||
: bookings.first.attractions.length,
|
||||
(i) => Container(
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: 2),
|
||||
width: 5,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(int.parse(
|
||||
"0xff${bookings.first.attractions[i].colorHex.substring(1)}")),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Container(height: 15),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
"${date.day}",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (bookings.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...List.generate(
|
||||
bookings.first.attractions.length > 3
|
||||
? 3
|
||||
: bookings.first.attractions.length,
|
||||
(i) => Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||
width: 5,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(int.parse(
|
||||
"0xff${bookings.first.attractions[i].colorHex.substring(1)}")),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- LEGEND ----------------
|
||||
Widget _buildAttractionLegend() {
|
||||
final legends = [
|
||||
{"name": "The Enchanted Garden", "color": "#4CAF50", "percent": "90%"},
|
||||
@@ -343,13 +370,16 @@ class _BookingView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecurringButton() {
|
||||
// ---------------- BUTTON ----------------
|
||||
Widget _buildRecurringButton(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, AppRouter.recurringBlockBasicInfo);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'add_time_slot_sheet.dart';
|
||||
|
||||
|
||||
|
||||
@@ -11,27 +12,33 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
InkWell(
|
||||
onTap: (){
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
Text(
|
||||
"Selected Time Slot",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 22,
|
||||
fontSize: 32,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 3,)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
@@ -59,20 +66,29 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_sectionHeader("Time Slots Available", true),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xffF95F62)),
|
||||
),
|
||||
child: Text(
|
||||
"+ Add timing",
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xffF95F62),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
_sectionHeader("Time Slots Available", false),
|
||||
InkWell(
|
||||
onTap: (){
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withOpacity(0.4),
|
||||
builder: (_) => const AddTimeSlotDialog(),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xffF95F62)),
|
||||
),
|
||||
child: Text(
|
||||
"+ Add timing",
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xffF95F62),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -86,54 +102,6 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Capacity Used
|
||||
_sectionHeader("Capacity Used", true),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF6F6F6),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.group,
|
||||
size: 18, color: Colors.green),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"45/50",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
color: Colors.black87),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
"Available",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.green,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: LinearProgressIndicator(
|
||||
value: 45 / 50,
|
||||
backgroundColor: const Color(0xffF9E7E1),
|
||||
valueColor: const AlwaysStoppedAnimation(
|
||||
Color(0xffF95F62)),
|
||||
minHeight: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Date
|
||||
_sectionHeader("Date", true),
|
||||
@@ -159,9 +127,7 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Save button
|
||||
Spacer(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@@ -216,18 +182,20 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
|
||||
Widget _sectionHeader(String title, bool editable) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 16,
|
||||
fontSize: 24,
|
||||
color: Colors.black),
|
||||
),
|
||||
|
||||
if (editable)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 6),
|
||||
child: Icon(Icons.edit, color: Color(0xffF95F62), size: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Image.asset("assets/time/image.png",scale: 4,),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -243,14 +211,33 @@ class SelectedTimeSlotPage extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.access_time, color: Color(0xffF95F62), size: 18),
|
||||
Image.asset("assets/time/mingcute_time-fill.png",scale: 4,),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
time,
|
||||
style: GoogleFonts.poppins(fontSize: 14, color: Colors.black87),
|
||||
),
|
||||
const Spacer(),
|
||||
const Icon(Icons.edit_outlined, color: Colors.black54, size: 18),
|
||||
Container(
|
||||
height: 34,
|
||||
width: 54,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffEBEBEB),
|
||||
borderRadius: BorderRadius.circular(6)
|
||||
),
|
||||
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Text("15",style: TextStyle(color: Color(0xff00B1EE),fontSize: 12,fontWeight: FontWeight.w600),),
|
||||
Text("/50",style: TextStyle(color: Color(0xff000000),fontSize: 12,fontWeight: FontWeight.w600)), const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4,),
|
||||
Image.asset("assets/time/image.png",scale: 4,color: Color(0xff626262),)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,22 +7,32 @@ import '../login/views/otp_verification_page.dart';
|
||||
import '../login/views/reset_password_page.dart';
|
||||
import '../onboarding/views/onboarding_page.dart';
|
||||
import '../profile/views/profile_page.dart';
|
||||
import '../recurring/views/recurring_block.dart';
|
||||
import '../redemption/view/ticket_redemption_screen.dart';
|
||||
import '../scan/view/qr_scan_screen.dart';
|
||||
import '../scan_history/views/scan_history_detail_page.dart';
|
||||
import '../scan_history/views/scan_history_page.dart';
|
||||
import '../splash/splash_view.dart';
|
||||
import '../support/view/help_support_page.dart';
|
||||
|
||||
class AppRouter {
|
||||
static const String onboarding = '/onboarding';
|
||||
static const String login = '/login';
|
||||
static const String home = '/home';
|
||||
static const String ticketRedemptionScreen = '/ticketRedemptionScreen';
|
||||
static const String qrScanScreen = '/qrScanScreen';
|
||||
static const String scanHistory = '/scanHistory';
|
||||
static const String forgotPassword = '/forgot_password';
|
||||
static const String splashScreen = '/splashScreen';
|
||||
static const String otpVerification = '/otp_verification';
|
||||
static const String resetPassword = '/reset_password';
|
||||
static const String profileScreen = '/profile_screen';
|
||||
static const String bookingPage = '/booking_page';
|
||||
static const String selectedTimeSlotPage = '/selected_time_slot_page';
|
||||
static const String helpSupportPage = '/help_support_page';
|
||||
static const String scanHistoryDetailPage = '/ScanHistoryDetailPage';
|
||||
|
||||
static const String recurringBlockBasicInfo = '/RecurringBlockBasicInfo';
|
||||
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
@@ -40,12 +50,22 @@ class AppRouter {
|
||||
return MaterialPageRoute(builder: (_) => const ResetPasswordPage());
|
||||
case profileScreen:
|
||||
return MaterialPageRoute(builder: (_) => const ProfileScreen());
|
||||
case selectedTimeSlotPage:
|
||||
case qrScanScreen:
|
||||
return MaterialPageRoute(builder: (_) => const QrScanScreen());
|
||||
case splashScreen:
|
||||
return MaterialPageRoute(builder: (_) => const SplashScreen());
|
||||
case scanHistoryDetailPage:
|
||||
return MaterialPageRoute(builder: (_) => const ScanHistoryDetailPage(passId: 'P214125125',));
|
||||
case selectedTimeSlotPage:
|
||||
return MaterialPageRoute(builder: (_) => const SelectedTimeSlotPage());
|
||||
case bookingPage:
|
||||
case bookingPage:
|
||||
return MaterialPageRoute(builder: (_) => const BookingPage());
|
||||
case helpSupportPage:
|
||||
case helpSupportPage:
|
||||
return MaterialPageRoute(builder: (_) => const HelpSupportPage());
|
||||
case ticketRedemptionScreen:
|
||||
return MaterialPageRoute(builder: (_) => const TicketRedemptionScreen());
|
||||
case recurringBlockBasicInfo:
|
||||
return MaterialPageRoute(builder: (_) => const RecurringBlockPage());
|
||||
default:
|
||||
return MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
|
||||
101
lib/core/ticket_card.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class MaskedTicketImage extends StatefulWidget {
|
||||
final String imagePath;
|
||||
final String maskPath;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const MaskedTicketImage({
|
||||
super.key,
|
||||
required this.imagePath,
|
||||
required this.maskPath,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MaskedTicketImage> createState() => _MaskedTicketImageState();
|
||||
}
|
||||
|
||||
class _MaskedTicketImageState extends State<MaskedTicketImage> {
|
||||
ui.Image? image;
|
||||
ui.Image? mask;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadImages();
|
||||
}
|
||||
|
||||
Future<void> _loadImages() async {
|
||||
final img = await _loadAsset(widget.imagePath);
|
||||
final maskImg = await _loadAsset(widget.maskPath);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
image = img;
|
||||
mask = maskImg;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<ui.Image> _loadAsset(String asset) async {
|
||||
final data = await DefaultAssetBundle.of(context).load(asset);
|
||||
final codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
||||
final frame = await codec.getNextFrame();
|
||||
return frame.image;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (image == null || mask == null) {
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: const Center(child: CircularProgressIndicator(strokeWidth: 1.5)),
|
||||
);
|
||||
}
|
||||
|
||||
return CustomPaint(
|
||||
size: Size(widget.width, widget.height),
|
||||
painter: _MaskedPainter(image!, mask!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MaskedPainter extends CustomPainter {
|
||||
final ui.Image image;
|
||||
final ui.Image mask;
|
||||
|
||||
_MaskedPainter(this.image, this.mask);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint();
|
||||
|
||||
// Fill background to avoid black transparency
|
||||
canvas.drawColor(Colors.transparent, BlendMode.srcOver);
|
||||
|
||||
// Draw base image
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
paint,
|
||||
);
|
||||
|
||||
// Apply mask
|
||||
paint.blendMode = BlendMode.dstIn;
|
||||
canvas.drawImageRect(
|
||||
mask,
|
||||
Rect.fromLTWH(0, 0, mask.width.toDouble(), mask.height.toDouble()),
|
||||
Rect.fromLTWH(0, 0, size.width, size.height),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -156,37 +156,75 @@ class _ForgotPasswordView extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 148),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.isValidEmail && !state.isLoading
|
||||
? () => bloc.add(SendResetLink())
|
||||
: null,
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return const Color(0xFF9C3F42);
|
||||
}
|
||||
return const Color(0xFFFF4C4C);
|
||||
},
|
||||
),
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
// SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: ElevatedButton(
|
||||
// onPressed: state.isValidEmail && !state.isLoading
|
||||
// ? () => bloc.add(SendResetLink())
|
||||
// : null,
|
||||
// style: ButtonStyle(
|
||||
// backgroundColor:
|
||||
// MaterialStateProperty.resolveWith<Color>(
|
||||
// (states) {
|
||||
// if (states.contains(MaterialState.disabled)) {
|
||||
// return const Color(0xFF9C3F42);
|
||||
// }
|
||||
// return const Color(0xFFFF4C4C);
|
||||
// },
|
||||
// ),
|
||||
// padding: MaterialStateProperty.all(
|
||||
// const EdgeInsets.symmetric(vertical: 14),
|
||||
// ),
|
||||
// shape: MaterialStateProperty.all(
|
||||
// RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(
|
||||
// "Send Reset Link",
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: state.isValidEmail?Colors.white:Color(0xff9D9F9F),
|
||||
// fontSize: 16,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => bloc.add(SendResetLink()),
|
||||
style: ButtonStyle(
|
||||
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return const Color(0xFFFF4C4C);
|
||||
}
|
||||
return const Color(0xFFFF4C4C);
|
||||
},
|
||||
),
|
||||
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Send Reset Link",
|
||||
style: GoogleFonts.poppins(
|
||||
color: state.isValidEmail?Colors.white:Color(0xff9D9F9F),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
child: Text(
|
||||
"Send Reset Link",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -20,7 +20,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Background image
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
'assets/login/bg.png',
|
||||
@@ -200,15 +199,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 68),
|
||||
|
||||
// Log in button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, '/home');
|
||||
Navigator.pushNamed(context, AppRouter.forgotPassword);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFF4C4C),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../core/app_router.dart';
|
||||
import '../blocs/otp_bloc.dart';
|
||||
|
||||
class OtpVerificationPage extends StatelessWidget {
|
||||
@@ -58,7 +59,7 @@ class OtpVerificationPage extends StatelessWidget {
|
||||
child: BlocConsumer<OtpBloc, OtpState>(
|
||||
listener: (context, state) {
|
||||
if (state.isVerified) {
|
||||
Navigator.pushNamed(context, '/reset_password');
|
||||
Navigator.pushNamed(context, AppRouter.resetPassword);
|
||||
} else if (state.message.isNotEmpty &&
|
||||
!state.isLoading) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -193,8 +193,7 @@ class _ResetPasswordView extends StatelessWidget {
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context, AppRouter.otpVerification);
|
||||
// Navigate or show success
|
||||
context, AppRouter.qrScanScreen);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFF4C4C),
|
||||
|
||||