4th commit

This commit is contained in:
2025-10-29 18:55:48 +05:30
parent 6086ae249f
commit 4946d6b8c0
128 changed files with 4665 additions and 845 deletions

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View 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>

View File

@@ -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

View 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>

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
assets/app/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/onboarding/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/recurring/food.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

BIN
assets/recurring/musuem.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
assets/scan/flash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/scan/flash_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

BIN
assets/scan/history.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
assets/scan/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
assets/scan/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

BIN
assets/scan/mobile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
assets/scan/page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

BIN
assets/scan/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/scan/qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

BIN
assets/scan/setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/scan/support.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/scan/ticket-fill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/scan/ticket.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

BIN
assets/scan/ticket_qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/splash/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

BIN
assets/splash/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

BIN
assets/ticket/dubai.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

BIN
assets/time/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -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++";

View File

@@ -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"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 383 KiB

View File

@@ -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>

View File

@@ -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>

View 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)),
),
),
),
);
}
}

View File

@@ -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),
),
),
],
),

View File

@@ -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(

View File

@@ -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),)
],
),
);

View File

@@ -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
View 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;
}

View File

@@ -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,
),
),
),
),

View File

@@ -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),

View File

@@ -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(

View File

@@ -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),

Some files were not shown because too many files have changed in this diff Show More