diff --git a/logo/icon_pix.png b/logo/icon_pix.png new file mode 100644 index 0000000..d3d4824 Binary files /dev/null and b/logo/icon_pix.png differ diff --git a/logo/pedifoods.png b/logo/pedifoods.png new file mode 100644 index 0000000..45bec47 Binary files /dev/null and b/logo/pedifoods.png differ diff --git a/logo/pedifoods_dark.png b/logo/pedifoods_dark.png new file mode 100644 index 0000000..c94bd7e Binary files /dev/null and b/logo/pedifoods_dark.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png index ff862e7..6dae27f 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 0eeb46e..6dae27f 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 3dac36a..b253f0b 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 3cf8fb1..b253f0b 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index c9d77af..8d68de2 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index beb10dc..8d68de2 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index b0bd6f9..19f92aa 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 7625d77..19f92aa 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/pedi-foods/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index d80e6f6..4b9563a 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index a0175d9..4b9563a 100644 Binary files a/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/pedi-foods/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png deleted file mode 100644 index f1a1740..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png deleted file mode 100644 index f1a1740..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png deleted file mode 100644 index 68c9d75..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png deleted file mode 100644 index 880a146..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png deleted file mode 100644 index 5974ff1..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png deleted file mode 100644 index 375714a..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png deleted file mode 100644 index 375714a..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png deleted file mode 100644 index c85663d..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png deleted file mode 100644 index 5974ff1..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png deleted file mode 100644 index a2c885a..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png deleted file mode 100644 index a2c885a..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png deleted file mode 100644 index bd8fbed..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png deleted file mode 100644 index f1a1740..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png deleted file mode 100644 index af0ccf8..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png deleted file mode 100644 index bd8fbed..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png deleted file mode 100644 index 301602e..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png deleted file mode 100644 index d727e92..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png deleted file mode 100644 index 57e105d..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png deleted file mode 100644 index 4f3fa69..0000000 Binary files a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png and /dev/null differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json index 611a2bb..6035446 100644 --- a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,116 @@ { - "images" : [ + "images": [ { - "filename" : "AppIcon-20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" + "idiom": "iphone", + "size": "20x20", + "scale": "2x", + "filename": "icon_iphone_20pt@2x.png" }, { - "filename" : "AppIcon-20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" + "idiom": "iphone", + "size": "20x20", + "scale": "3x", + "filename": "icon_iphone_20pt@3x.png" }, { - "filename" : "AppIcon-29.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" + "idiom": "iphone", + "size": "29x29", + "scale": "2x", + "filename": "icon_iphone_29pt@2x.png" }, { - "filename" : "AppIcon-29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" + "idiom": "iphone", + "size": "29x29", + "scale": "3x", + "filename": "icon_iphone_29pt@3x.png" }, { - "filename" : "AppIcon-29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" + "idiom": "iphone", + "size": "40x40", + "scale": "2x", + "filename": "icon_iphone_40pt@2x.png" }, { - "filename" : "AppIcon-40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" + "idiom": "iphone", + "size": "40x40", + "scale": "3x", + "filename": "icon_iphone_40pt@3x.png" }, { - "filename" : "AppIcon-40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" + "idiom": "iphone", + "size": "60x60", + "scale": "2x", + "filename": "icon_iphone_60pt@2x.png" }, { - "filename" : "AppIcon@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" + "idiom": "iphone", + "size": "60x60", + "scale": "3x", + "filename": "icon_iphone_60pt@3x.png" }, { - "filename" : "AppIcon@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" + "idiom": "ipad", + "size": "20x20", + "scale": "1x", + "filename": "icon_ipad_20pt@1x.png" }, { - "filename" : "AppIcon-20~ipad.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" + "idiom": "ipad", + "size": "20x20", + "scale": "2x", + "filename": "icon_ipad_20pt@2x.png" }, { - "filename" : "AppIcon-20@2x~ipad.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" + "idiom": "ipad", + "size": "29x29", + "scale": "1x", + "filename": "icon_ipad_29pt@1x.png" }, { - "filename" : "AppIcon-29~ipad.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" + "idiom": "ipad", + "size": "29x29", + "scale": "2x", + "filename": "icon_ipad_29pt@2x.png" }, { - "filename" : "AppIcon-29@2x~ipad.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" + "idiom": "ipad", + "size": "40x40", + "scale": "1x", + "filename": "icon_ipad_40pt@1x.png" }, { - "filename" : "AppIcon-40~ipad.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" + "idiom": "ipad", + "size": "40x40", + "scale": "2x", + "filename": "icon_ipad_40pt@2x.png" }, { - "filename" : "AppIcon-40@2x~ipad.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" + "idiom": "ipad", + "size": "76x76", + "scale": "1x", + "filename": "icon_ipad_76pt@1x.png" }, { - "filename" : "AppIcon~ipad.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" + "idiom": "ipad", + "size": "76x76", + "scale": "2x", + "filename": "icon_ipad_76pt@2x.png" }, { - "filename" : "AppIcon@2x~ipad.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" + "idiom": "ipad", + "size": "83.5x83.5", + "scale": "2x", + "filename": "icon_ipad_83.5pt@2x.png" }, { - "filename" : "AppIcon-83.5@2x~ipad.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "AppIcon~ios-marketing.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" + "idiom": "ios-marketing", + "size": "1024x1024", + "scale": "1x", + "filename": "icon_ios-marketing_1024pt@1x.png" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "version": 1, + "author": "xcode" } -} +} \ No newline at end of file diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ios-marketing_1024pt@1x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ios-marketing_1024pt@1x.png new file mode 100644 index 0000000..2729adc Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ios-marketing_1024pt@1x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@1x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@1x.png new file mode 100644 index 0000000..679e384 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@1x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@2x.png new file mode 100644 index 0000000..b92757d Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_20pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@1x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@1x.png new file mode 100644 index 0000000..7ffb69f Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@1x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@2x.png new file mode 100644 index 0000000..bcf419f Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_29pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@1x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@1x.png new file mode 100644 index 0000000..b92757d Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@1x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@2x.png new file mode 100644 index 0000000..75c60b1 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_40pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@1x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@1x.png new file mode 100644 index 0000000..e0e1cb7 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@1x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@2x.png new file mode 100644 index 0000000..f81c408 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_76pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_83.5pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_83.5pt@2x.png new file mode 100644 index 0000000..1d956de Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_ipad_83.5pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@2x.png new file mode 100644 index 0000000..b92757d Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@3x.png new file mode 100644 index 0000000..40d5a85 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_20pt@3x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@2x.png new file mode 100644 index 0000000..bcf419f Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@3x.png new file mode 100644 index 0000000..ecf3804 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_29pt@3x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@2x.png new file mode 100644 index 0000000..75c60b1 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@3x.png new file mode 100644 index 0000000..2cf0dcc Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_40pt@3x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@2x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@2x.png new file mode 100644 index 0000000..2cf0dcc Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@2x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@3x.png b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@3x.png new file mode 100644 index 0000000..720b6d1 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/AppIcon.appiconset/icon_iphone_60pt@3x.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/Contents.json b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/Contents.json new file mode 100644 index 0000000..6df8882 --- /dev/null +++ b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "login_light.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + }, + { + "idiom": "universal", + "filename": "login_dark.png", + "scale": "1x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "2x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "3x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_dark.png b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_dark.png new file mode 100644 index 0000000..bf637d4 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_dark.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_light.png b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_light.png new file mode 100644 index 0000000..e7c5b09 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/login_bg.imageset/login_light.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/Contents.json b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/Contents.json new file mode 100644 index 0000000..0ed1bd7 --- /dev/null +++ b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "pedifoods.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + }, + { + "idiom": "universal", + "filename": "pedifoods_dark.png", + "scale": "1x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "2x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "3x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods.png b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods.png new file mode 100644 index 0000000..45bec47 Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods_dark.png b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods_dark.png new file mode 100644 index 0000000..c94bd7e Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/pedifoods.imageset/pedifoods_dark.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/Contents.json b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/Contents.json new file mode 100644 index 0000000..c6998a7 --- /dev/null +++ b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "pin_image_app.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + }, + { + "idiom": "universal", + "filename": "pin_image_app_dark.png", + "scale": "1x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "2x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + }, + { + "idiom": "universal", + "scale": "3x", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app.png b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app.png new file mode 100644 index 0000000..7ed510e Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app.png differ diff --git a/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app_dark.png b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app_dark.png new file mode 100644 index 0000000..450d7bc Binary files /dev/null and b/pedi-foods/Darwin/Assets.xcassets/pin_image_app.imageset/pin_image_app_dark.png differ diff --git a/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj b/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj index 312c83d..2431fa2 100644 --- a/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj +++ b/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj @@ -183,7 +183,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = "/bin/sh -e"; - shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" = \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" = \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelif [ \"${SKIP_ACTION}\" = \"none\" ]; then\n echo \"note: skipping skip due to SKIP_ACTION none\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Release:${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n"; + shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" = \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" = \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelif [ \"${PLATFORM_NAME}\" = \"iphonesimulator\" ]; then\n echo \"note: skipping skip gradle for iOS Simulator\"\n exit 0\nelif [ \"${SKIP_ACTION}\" = \"none\" ]; then\n echo \"note: skipping skip due to SKIP_ACTION none\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Release:${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -203,7 +203,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = K4E5BZMM4V; ENABLE_PREVIEWS = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; }; @@ -213,7 +215,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = K4E5BZMM4V; ENABLE_PREVIEWS = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; }; @@ -244,7 +248,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf-with-dsym; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; diff --git a/pedi-foods/Sources/PediFoods/Components/Buttons.swift b/pedi-foods/Sources/PediFoods/Components/Buttons.swift new file mode 100644 index 0000000..6b88292 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Components/Buttons.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct PrimaryButton: View { + let title: String + var fullWidth: Bool = true + let action: @MainActor @Sendable () -> Void + + var body: some View { + Button(action: { action() }) { + Text(title) + .font(AppTypography.button) + .tracking(AppTypography.buttonLetterSpacing) + .foregroundStyle(AppColors.textInverse) + .frame(maxWidth: fullWidth ? .infinity : nil) + .frame(height: 56) + .background(AppColors.primary) + .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) + } + .buttonStyle(.plain) + } +} + +struct SecondaryButton: View { + let title: String + var fullWidth: Bool = true + let action: @MainActor @Sendable () -> Void + + var body: some View { + Button(action: { action() }) { + Text(title) + .font(AppTypography.button) + .tracking(AppTypography.buttonLetterSpacing) + .foregroundStyle(AppColors.primary) + .frame(maxWidth: fullWidth ? .infinity : nil) + .frame(height: 56) + .background(AppColors.backgroundLight) + .overlay( + RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous) + .stroke(AppColors.primary.opacity(0.3), lineWidth: 1) + ) + } + .buttonStyle(.plain) + } +} diff --git a/pedi-foods/Sources/PediFoods/Components/Inputs.swift b/pedi-foods/Sources/PediFoods/Components/Inputs.swift new file mode 100644 index 0000000..50cf530 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Components/Inputs.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct SearchField: View { + var placeholder: String + @Binding var text: String + + var body: some View { + HStack(spacing: 12) { + Image(systemName: "magnifyingglass") + .foregroundStyle(AppColors.secondary) + TextField(placeholder, text: $text) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + .padding(.horizontal, 16) + .frame(height: 52) + .background(AppColors.backgroundLight) + .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous) + .stroke(AppColors.secondary.opacity(0.15), lineWidth: 1) + ) + } +} + +struct PillButton: View { + let title: String + let isActive: Bool + + var body: some View { + Text(title) + .font(AppTypography.caption) + .tracking(AppTypography.captionLetterSpacing) + .foregroundStyle(isActive ? AppColors.textInverse : AppColors.secondary) + .padding(.horizontal, 14) + .padding(.vertical, 8) + .background(isActive ? AppColors.primary : AppColors.backgroundLight) + .clipShape(Capsule()) + .overlay( + Capsule() + .stroke(AppColors.secondary.opacity(0.15), lineWidth: isActive ? 0 : 1) + ) + } +} diff --git a/pedi-foods/Sources/PediFoods/Components/StoreCard.swift b/pedi-foods/Sources/PediFoods/Components/StoreCard.swift new file mode 100644 index 0000000..228e542 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Components/StoreCard.swift @@ -0,0 +1,131 @@ +import SwiftUI + +struct FeaturedStoreCard: View { + let store: FeaturedStoreCardModel + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous) + .fill(AppColors.brandSoft) + .frame(height: 120) + .overlay( + Circle() + .fill(AppColors.surface) + .frame(width: 64, height: 64) + .overlay( + Image(systemName: store.iconName) + .font(.title2) + .foregroundStyle(AppColors.primary) + ) + ) + + if let promo = store.promoText { + Text(promo) + .font(AppTypography.caption) + .foregroundStyle(AppColors.textInverse) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.red) + .clipShape(Capsule()) + .padding(10) + } + + HStack { + Spacer() + Button(action: {}) { + Image(systemName: store.isFavorite ? "heart.fill" : "heart") + .foregroundStyle(store.isFavorite ? Color.red : AppColors.textMuted) + .padding(8) + .background(AppColors.surface) + .clipShape(Circle()) + .shadow(color: AppShadow.soft.color, radius: AppShadow.soft.radius, y: AppShadow.soft.y) + } + .padding(10) + } + } + + Text(store.name) + .font(AppTypography.heading3) + .foregroundStyle(AppColors.textPrimary) + .lineLimit(1) + + HStack(spacing: 6) { + Image(systemName: "star.fill") + .font(.caption) + .foregroundStyle(Color(hex: "#F5B335")) + Text(String(format: "%.1f", store.rating)) + .font(.caption) + .foregroundStyle(AppColors.textPrimary) + Text("(\(store.reviews))") + .font(.caption) + .foregroundStyle(AppColors.textMuted) + Text("·") + .font(.caption) + .foregroundStyle(AppColors.textMuted) + Text(store.distance) + .font(.caption) + .foregroundStyle(AppColors.textMuted) + } + + Text(store.category) + .font(.caption) + .foregroundStyle(AppColors.textMuted) + } + .padding(14) + .background(AppColors.surface) + .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous)) + } +} + +struct FeaturedStoreCardModel: Identifiable { + let id = UUID() + let name: String + let rating: Double + let reviews: String + let distance: String + let category: String + let promoText: String? + let isFavorite: Bool + let iconName: String +} + +struct SpecialOfferCard: View { + let model: SpecialOfferCardModel + + var body: some View { + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous) + .fill( + LinearGradient( + colors: model.colors, + startPoint: .leading, + endPoint: .trailing + ) + ) + + Circle() + .fill(Color.white.opacity(0.18)) + .frame(width: 120, height: 120) + .offset(x: 140, y: 10) + + VStack(alignment: .leading, spacing: 8) { + Text(model.title) + .font(AppTypography.heading2) + .foregroundStyle(AppColors.textInverse) + Text(model.subtitle) + .font(AppTypography.body) + .foregroundStyle(AppColors.textInverse.opacity(0.85)) + } + .padding(20) + } + .shadow(color: AppShadow.soft.color, radius: AppShadow.soft.radius, y: AppShadow.soft.y) + } +} + +struct SpecialOfferCardModel: Identifiable { + let id = UUID() + let title: String + let subtitle: String + let colors: [Color] +} diff --git a/pedi-foods/Sources/PediFoods/ContentView.swift b/pedi-foods/Sources/PediFoods/ContentView.swift index f7afb09..f62f516 100644 --- a/pedi-foods/Sources/PediFoods/ContentView.swift +++ b/pedi-foods/Sources/PediFoods/ContentView.swift @@ -1,177 +1,16 @@ import SwiftUI -enum ContentTab: String, Hashable { - case welcome, home, settings -} - struct ContentView: View { - @AppStorage("tab") var tab = ContentTab.welcome - @AppStorage("name") var welcomeName = "Skipper" - @AppStorage("appearance") var appearance = "" - @State var viewModel = ViewModel() + @State var root: RootFlow = DefaultTokenStore().jwt == nil ? .auth : .main + @State var selectedTab: MainTab = .home + private let tokenStore: TokenStore = DefaultTokenStore() var body: some View { - TabView(selection: $tab) { - NavigationStack { - WelcomeView(welcomeName: $welcomeName) - } - .tabItem { Label("Welcome", systemImage: "heart.fill") } - .tag(ContentTab.welcome) - - NavigationStack { - ItemListView() - .navigationTitle(Text("\(viewModel.items.count) Items")) - } - .tabItem { Label("Home", systemImage: "house.fill") } - .tag(ContentTab.home) - - NavigationStack { - SettingsView(appearance: $appearance, welcomeName: $welcomeName) - .navigationTitle("Settings") - } - .tabItem { Label("Settings", systemImage: "gearshape.fill") } - .tag(ContentTab.settings) - } - .environment(viewModel) - .preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil) - } -} - -struct WelcomeView : View { - @State var heartBeating = false - @Binding var welcomeName: String - - var body: some View { - VStack(spacing: 0) { - Text("Hello [\(welcomeName)](https://skip.tools)!") - .padding() - Image(systemName: "heart.fill") - .foregroundStyle(.red) - .scaleEffect(heartBeating ? 1.5 : 1.0) - .animation(.easeInOut(duration: 1).repeatForever(), value: heartBeating) - .task { heartBeating = true } - } - .font(.largeTitle) - } -} - -struct ItemListView : View { - @Environment(ViewModel.self) var viewModel: ViewModel - - var body: some View { - List { - ForEach(viewModel.items) { item in - NavigationLink(value: item) { - Label { - Text(item.itemTitle) - } icon: { - if item.favorite { - Image(systemName: "star.fill") - .foregroundStyle(.yellow) - } - } - } - } - .onDelete { offsets in - viewModel.items.remove(atOffsets: offsets) - } - .onMove { fromOffsets, toOffset in - viewModel.items.move(fromOffsets: fromOffsets, toOffset: toOffset) - } - } - .navigationDestination(for: Item.self) { item in - ItemView(item: item) - .navigationTitle(item.itemTitle) - } - .toolbar { - ToolbarItemGroup { - Button { - withAnimation { - viewModel.items.insert(Item(), at: 0) - } - } label: { - Label("Add", systemImage: "plus") - } - } + switch root { + case .auth: + AuthFlowView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore) + case .main: + MainTabView(selectedTab: $selectedTab, root: $root, tokenStore: tokenStore) } } } - -struct ItemView : View { - @State var item: Item - @Environment(ViewModel.self) var viewModel: ViewModel - @Environment(\.dismiss) var dismiss - - var body: some View { - Form { - TextField("Title", text: $item.title) - .textFieldStyle(.roundedBorder) - Toggle("Favorite", isOn: $item.favorite) - DatePicker("Date", selection: $item.date) - Text("Notes").font(.title3) - TextEditor(text: $item.notes) - .border(Color.secondary, width: 1.0) - } - .navigationBarBackButtonHidden() - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { - dismiss() - } - } - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - viewModel.save(item: item) - dismiss() - } - .disabled(!viewModel.isUpdated(item)) - } - } - } -} - -struct SettingsView : View { - @Binding var appearance: String - @Binding var welcomeName: String - - var body: some View { - Form { - TextField("Name", text: $welcomeName) - Picker("Appearance", selection: $appearance) { - Text("System").tag("") - Text("Light").tag("light") - Text("Dark").tag("dark") - } - if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, - let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { - Text("Version \(version) (\(buildNumber))") - } - HStack { - PlatformHeartView() - Text("Powered by [Skip](https://skip.tools)") - } - } - } -} - -/// A view that shows a blue heart on iOS and a green heart on Android. -struct PlatformHeartView : View { - var body: some View { - #if os(Android) - ComposeView { - HeartComposer() - } - #else - Text(verbatim: "💙") - #endif - } -} - -#if SKIP -/// Use a ContentComposer to integrate Compose content. This code will be transpiled to Kotlin. -struct HeartComposer : ContentComposer { - @Composable func Compose(context: ComposeContext) { - androidx.compose.material3.Text("💚", modifier: context.modifier) - } -} -#endif diff --git a/pedi-foods/Sources/PediFoods/Coordinators/AppCoordinator.swift b/pedi-foods/Sources/PediFoods/Coordinators/AppCoordinator.swift new file mode 100644 index 0000000..50ee526 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Coordinators/AppCoordinator.swift @@ -0,0 +1,10 @@ +enum RootFlow: Hashable { + case auth + case main +} + +enum MainTab: Hashable { + case home + case cart + case profile +} diff --git a/pedi-foods/Sources/PediFoods/DesignSystem/DesignSystem.swift b/pedi-foods/Sources/PediFoods/DesignSystem/DesignSystem.swift new file mode 100644 index 0000000..62ccbd3 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/DesignSystem/DesignSystem.swift @@ -0,0 +1,101 @@ +import SwiftUI + +// MARK: - Colors + +enum AppColors { + static let primary = Color(hex: "#1E6B43") + static let secondary = Color(hex: "#2F7C4B") + static let tertiary = Color(hex: "#8CFF2E") + + static let brandDark = Color(hex: "#1B5C3A") + static let brandSoft = Color(hex: "#E8F2EC") + + static let backgroundLight = Color(hex: "#F3F5F7") + static let backgroundDark = Color(hex: "#18230F") + static let surface = Color(hex: "#FFFFFF") + static let textPrimary = Color(hex: "#1C1F23") + static let textInverse = Color(hex: "#FFFFFF") + static let textMuted = Color(hex: "#7B8794") +} + +enum AppDarkColors { + static let background = Color(hex: "#18230F") + static let surface = Color(hex: "#FFFFFF", alpha: 0.05) + static let textPrimary = Color(hex: "#FFFFFF") + static let textSecondary = Color(hex: "#A3A3A3") + static let primary = AppColors.primary + static let secondary = AppColors.secondary + static let tertiary = AppColors.tertiary +} + +// MARK: - Typography + +enum AppTypography { + static let fontFamily = "Plus Jakarta Sans" + + static let heading1 = Font.custom(fontFamily, size: 28).weight(.heavy) + static let heading2 = Font.custom(fontFamily, size: 20).weight(.heavy) + static let heading3 = Font.custom(fontFamily, size: 16).weight(.bold) + static let body = Font.custom(fontFamily, size: 16).weight(.regular) + static let button = Font.custom(fontFamily, size: 14).weight(.heavy) + static let caption = Font.custom(fontFamily, size: 10).weight(.bold) + static let overline = Font.custom(fontFamily, size: 11).weight(.bold) + + static let bodyLineHeight: CGFloat = 1.6 + static let buttonLetterSpacing: CGFloat = 0.08 + static let captionLetterSpacing: CGFloat = 0.12 +} + +// MARK: - Layout + +enum AppLayout { + static let radiusMD: CGFloat = 12 + static let radiusLG: CGFloat = 16 + static let radiusXL: CGFloat = 24 + static let radiusFull: CGFloat = 9999 + + static let spacing: [CGFloat] = [4, 8, 12, 16, 24, 32] +} + +// MARK: - Shadow + +struct ShadowSpec { + let color: Color + let radius: CGFloat + let y: CGFloat +} + +enum AppShadow { + static let soft = ShadowSpec(color: Color.black.opacity(0.08), radius: 20, y: 6) + static let glow = ShadowSpec(color: AppColors.tertiary.opacity(0.2), radius: 24, y: 8) +} + +// MARK: - Helpers + +extension Color { + init(hex: String, alpha: Double = 1.0) { + let cleaned = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: cleaned).scanHexInt64(&int) + + let r, g, b: UInt64 + switch cleaned.count { + case 6: // RRGGBB + r = (int >> 16) & 0xFF + g = (int >> 8) & 0xFF + b = int & 0xFF + case 3: // RGB + r = ((int >> 8) & 0xF) * 17 + g = ((int >> 4) & 0xF) * 17 + b = (int & 0xF) * 17 + default: + r = 0; g = 0; b = 0 + } + + self.init(.sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: alpha) + } +} diff --git a/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings b/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings index 779b2ac..83659ca 100644 --- a/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings +++ b/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings @@ -1,8 +1,17 @@ { "sourceLanguage" : "en", "strings" : { + "·" : { + "comment" : "A period character used to separate different pieces of information in a list.", + "isCommentAutoGenerated" : true + }, + "(%@)" : { + "comment" : "A text element showing the number of reviews for a store. The content inside the parentheses is the number of reviews.", + "isCommentAutoGenerated" : true + }, "%lld Items" : { "comment" : "Header title for a list that contains the number of items", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -32,6 +41,7 @@ }, "Add" : { "comment" : "Button in items list that will cause a new item to be added", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -59,8 +69,13 @@ } } }, + "Ajuda" : { + "comment" : "A link to the help section of the app.", + "isCommentAutoGenerated" : true + }, "Appearance" : { "comment" : "Settings select label for the interface style of the controls (light, dark, or default)", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -88,8 +103,13 @@ } } }, + "Cadastro" : { + "comment" : "A label displayed above the registration form.", + "isCommentAutoGenerated" : true + }, "Cancel" : { "comment" : "Button title indicating that the operation should be cancelled", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -117,8 +137,33 @@ } } }, + "Canggu, Kuta Utara, Bali" : { + "comment" : "The address of the user's delivery location.", + "isCommentAutoGenerated" : true + }, + "Carrinho" : { + "comment" : "A label for the cart section of the app.", + "isCommentAutoGenerated" : true + }, + "Categories" : { + "comment" : "A section header that lists available food categories.", + "isCommentAutoGenerated" : true + }, + "Criar conta" : { + "comment" : "A link that navigates to the registration screen.", + "isCommentAutoGenerated" : true + }, + "Daniel Loverde" : { + "comment" : "The name of the user in the profile view header.", + "isCommentAutoGenerated" : true + }, + "daniel@pedifoods.com.br" : { + "comment" : "An email address displayed in the user's profile.", + "isCommentAutoGenerated" : true + }, "Dark" : { "comment" : "Menu item indicating that the appearance should be in dark mode", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -148,6 +193,7 @@ }, "Date" : { "comment" : "Item editor form label for the Date field", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -175,8 +221,33 @@ } } }, + "DELIVERY LOCATION" : { + "comment" : "A label describing the delivery location section.", + "isCommentAutoGenerated" : true + }, + "Descubra lorem ipsum ba bla" : { + "comment" : "A welcome message displayed on the login screen.", + "isCommentAutoGenerated" : true + }, + "DL" : { + "comment" : "An abbreviation for \"Delivery Lady\" used in the user's profile picture circle.", + "isCommentAutoGenerated" : true + }, + "Email" : { + "comment" : "A text field for entering the user's email address.", + "isCommentAutoGenerated" : true + }, + "Enderecos" : { + "comment" : "A link to the user's address list.", + "isCommentAutoGenerated" : true + }, + "Enviado para %@" : { + "comment" : "A label below the \"OTP\" title that indicates the email to which the OTP was sent. The argument is the email address to which the OTP was sent.", + "isCommentAutoGenerated" : true + }, "Favorite" : { "comment" : "Item editor title label for marking the item as a favorite", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -206,6 +277,7 @@ }, "Hello [%@](https://skip.tools)!" : { "comment" : "Welcome tab contents", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -233,6 +305,10 @@ } } }, + "Historico vazio" : { + "comment" : "A description displayed when a user has no orders in their history.", + "isCommentAutoGenerated" : true + }, "Home" : { "comment" : "Tab bar item title for the Home tab", "localizations" : { @@ -264,6 +340,7 @@ }, "Light" : { "comment" : "Menu item indicating that the appearance should be in light mode", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -293,6 +370,7 @@ }, "Name" : { "comment" : "Placeholder title for the Name field in a form", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -320,8 +398,16 @@ } } }, + "Não tem conta ainda?" : { + + }, + "Nome" : { + "comment" : "A label for the user to input their name.", + "isCommentAutoGenerated" : true + }, "Notes" : { "comment" : "Item editor form label for the Notes field", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -349,8 +435,29 @@ } } }, + "O que vai querer pedir hoje?" : { + "comment" : "A heading displayed above a search bar in the home view.", + "isCommentAutoGenerated" : true + }, + "OTP" : { + "comment" : "The title of a view that asks the user to enter an OTP code.", + "isCommentAutoGenerated" : true + }, + "Pedidos" : { + "comment" : "Navigation links for different sections of the user's profile.", + "isCommentAutoGenerated" : true + }, + "PediFoods" : { + "comment" : "The name of the app.", + "isCommentAutoGenerated" : true + }, + "Perfil" : { + "comment" : "A label for the \"Profile\" tab in the main tab view.", + "isCommentAutoGenerated" : true + }, "Powered by [Skip](https://skip.tools)" : { "comment" : "Link markdown text for the Powered by… label", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -380,6 +487,7 @@ }, "Save" : { "comment" : "Button title indicating that the current contents should be saved", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -409,6 +517,7 @@ }, "Settings" : { "comment" : "Tab bar item title for the Settings tab", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -436,8 +545,13 @@ } } }, + "Seu carrinho esta vazio" : { + "comment" : "A description displayed when a user's cart is empty.", + "isCommentAutoGenerated" : true + }, "System" : { "comment" : "Menu item indicating that the appearance should be in the default system mode", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -465,8 +579,13 @@ } } }, + "Telefone" : { + "comment" : "A label for a text field where a user can enter their phone number.", + "isCommentAutoGenerated" : true + }, "Title" : { "comment" : "Label for the item editor form indicating the title of the item", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -496,6 +615,7 @@ }, "Version %@ (%@)" : { "comment" : "Settings label showing the current version of the app", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -531,6 +651,7 @@ }, "Welcome" : { "comment" : "Tab bar item title for the Welcome tab", + "extractionState" : "stale", "localizations" : { "es" : { "stringUnit" : { @@ -559,5 +680,5 @@ } } }, - "version" : "1.0" -} + "version" : "1.1" +} \ No newline at end of file diff --git a/pedi-foods/Sources/PediFoods/Resources/login_dark.png b/pedi-foods/Sources/PediFoods/Resources/login_dark.png new file mode 100644 index 0000000..bf637d4 Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/login_dark.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/login_light.png b/pedi-foods/Sources/PediFoods/Resources/login_light.png new file mode 100644 index 0000000..e7c5b09 Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/login_light.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/Assets/pedifoods_big.png b/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/Assets/pedifoods_big.png new file mode 100644 index 0000000..bd1c864 Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/Assets/pedifoods_big.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/icon.json b/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/icon.json new file mode 100644 index 0000000..2dfb142 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Resources/pedifoods.icon/icon.json @@ -0,0 +1,94 @@ +{ + "fill-specializations" : [ + { + "value" : { + "automatic-gradient" : "srgb:0.99020,0.98039,1.00000,1.00000" + } + }, + { + "appearance" : "dark", + "value" : { + "solid" : "srgb:0.00000,0.00000,0.00000,1.00000" + } + } + ], + "groups" : [ + { + "layers" : [ + + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + }, + { + "layers" : [ + { + "blend-mode-specializations" : [ + { + "value" : "normal" + }, + { + "appearance" : "dark", + "value" : "normal" + } + ], + "fill-specializations" : [ + { + "value" : "none" + }, + { + "appearance" : "dark", + "value" : { + "solid" : "srgb:0.00000,0.50000,0.04260,1.00000" + } + } + ], + "glass" : false, + "hidden" : false, + "image-name" : "pedifoods_big.png", + "name" : "pedifoods_big", + "position-specializations" : [ + { + "value" : { + "scale" : 1, + "translation-in-points" : [ + 137.390625, + 11.984375 + ] + } + }, + { + "idiom" : "square", + "value" : { + "scale" : 0.35, + "translation-in-points" : [ + 8, + 0 + ] + } + } + ] + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "squares" : [ + "iOS" + ] + } +} \ No newline at end of file diff --git a/pedi-foods/Sources/PediFoods/Resources/pedifoods.png b/pedi-foods/Sources/PediFoods/Resources/pedifoods.png new file mode 100644 index 0000000..45bec47 Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/pedifoods.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/pedifoods_dark.png b/pedi-foods/Sources/PediFoods/Resources/pedifoods_dark.png new file mode 100644 index 0000000..c94bd7e Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/pedifoods_dark.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/pin_image_app.png b/pedi-foods/Sources/PediFoods/Resources/pin_image_app.png new file mode 100644 index 0000000..7ed510e Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/pin_image_app.png differ diff --git a/pedi-foods/Sources/PediFoods/Resources/pin_image_app_dark.png b/pedi-foods/Sources/PediFoods/Resources/pin_image_app_dark.png new file mode 100644 index 0000000..450d7bc Binary files /dev/null and b/pedi-foods/Sources/PediFoods/Resources/pin_image_app_dark.png differ diff --git a/pedi-foods/Sources/PediFoods/Services/ApiClient.swift b/pedi-foods/Sources/PediFoods/Services/ApiClient.swift new file mode 100644 index 0000000..1e29cf7 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Services/ApiClient.swift @@ -0,0 +1,104 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +enum NetworkError: Error, LocalizedError { + case invalidURL + case invalidResponse + case httpError(Int, String?) + case decodeError + case rateLimited + + var errorDescription: String? { + switch self { + case .invalidURL: return "URL invalida" + case .invalidResponse: return "Resposta invalida do servidor" + case .httpError(let code, let message): + return message ?? "Erro HTTP (\(code))" + case .decodeError: return "Erro ao interpretar dados" + case .rateLimited: return "Muitas requisicoes. Tente novamente." + } + } +} + +struct ApiRequest { + let path: String + let method: String + let module: ApiModule + let requiresAuth: Bool + let queryItems: [URLQueryItem] + let body: Data? + + init(path: String, + method: String = "GET", + module: ApiModule = .none, + requiresAuth: Bool = true, + queryItems: [URLQueryItem] = [], + body: Data? = nil) { + self.path = path + self.method = method + self.module = module + self.requiresAuth = requiresAuth + self.queryItems = queryItems + self.body = body + } +} + +final class ApiClient { + private let session: URLSession + private let tokenStore: TokenStore + + init(session: URLSession = .shared, tokenStore: TokenStore = DefaultTokenStore()) { + self.session = session + self.tokenStore = tokenStore + } + + func send(_ request: ApiRequest) async throws -> T { + let url = try buildURL(path: request.path, query: request.queryItems) + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = request.method + urlRequest.httpBody = request.body + urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + + if let token = ApiConfig.token(for: request.module) { + urlRequest.setValue(token, forHTTPHeaderField: "Atomenta-Token") + } + if request.requiresAuth, let jwt = tokenStore.jwt { + urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization") + } + + let (data, response) = try await session.data(for: urlRequest) + guard let http = response as? HTTPURLResponse else { + throw NetworkError.invalidResponse + } + + if http.statusCode == 429 { + throw NetworkError.rateLimited + } + + if !(200...299).contains(http.statusCode) { + let message = String(data: data, encoding: .utf8) + throw NetworkError.httpError(http.statusCode, message) + } + + do { + return try JSONDecoder().decode(T.self, from: data) + } catch { + throw NetworkError.decodeError + } + } + + private func buildURL(path: String, query: [URLQueryItem]) throws -> URL { + guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else { + throw NetworkError.invalidURL + } + components.path = components.path.appending(path) + if !query.isEmpty { + components.queryItems = query + } + guard let url = components.url else { throw NetworkError.invalidURL } + return url + } +} diff --git a/pedi-foods/Sources/PediFoods/Services/ApiConfig.swift b/pedi-foods/Sources/PediFoods/Services/ApiConfig.swift new file mode 100644 index 0000000..dd1e874 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Services/ApiConfig.swift @@ -0,0 +1,30 @@ +import Foundation + +enum ApiModule { + case app + case customer + case store + case resource + case none +} + +enum ApiConfig { + static var baseURL: URL { + let raw = ProcessInfo.processInfo.environment["ATOMENTA_API_URL"] ?? "http://127.0.0.1:3001" + return URL(string: raw) ?? URL(string: "http://127.0.0.1:3001")! + } + + // Tokens provided by backend modules + static let storeToken = "550e8400-e29b-41d4-a716-446655440008" + static let customerToken = "550e8400-e29b-41d4-a716-44665544000a" + static let resourceToken = "550e8400-e29b-41d4-a716-446655440009" + + static func token(for module: ApiModule) -> String? { + switch module { + case .store: return storeToken + case .customer: return customerToken + case .resource: return resourceToken + case .app, .none: return nil + } + } +} diff --git a/pedi-foods/Sources/PediFoods/Services/ApiService.swift b/pedi-foods/Sources/PediFoods/Services/ApiService.swift new file mode 100644 index 0000000..6cc3916 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Services/ApiService.swift @@ -0,0 +1,88 @@ +import Foundation + +struct ApiEnvelope: Decodable { + let error: Bool + let code: String? + let message: String? + let result: T? +} + +final class ApiService { + private let client: ApiClient + private var tokenStore: TokenStore + + init(client: ApiClient = ApiClient(), tokenStore: TokenStore = DefaultTokenStore()) { + self.client = client + self.tokenStore = tokenStore + } + + // MARK: - Auth + + func requestOtp(email: String) async throws -> ApiEnvelope { + let body = try JSONEncoder().encode(["email": email]) + let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body) + return try await client.send(req) + } + + func validateOtp(email: String, otp: String) async throws -> ApiEnvelope { + let body = try JSONEncoder().encode(["email": email, "otp": otp]) + let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body) + let response: ApiEnvelope = try await client.send(req) + if let token = response.result?.token { + tokenStore.jwt = token + } + return response + } + + func profile() async throws -> ApiEnvelope { + let req = ApiRequest(path: "/api/customer/profile", method: "GET", module: .customer, requiresAuth: true) + return try await client.send(req) + } + + // MARK: - Stores + + func listStores(lat: Double, lng: Double, category: String? = nil, search: String? = nil) async throws -> ApiEnvelope<[StoreSummary]> { + var items = [ + URLQueryItem(name: "lat", value: String(lat)), + URLQueryItem(name: "lng", value: String(lng)) + ] + if let category { + items.append(URLQueryItem(name: "category", value: category)) + } + if let search { + items.append(URLQueryItem(name: "search", value: search)) + } + let req = ApiRequest(path: "/api/app/stores", method: "GET", module: .app, requiresAuth: true, queryItems: items) + return try await client.send(req) + } +} + +// MARK: - DTOs + +struct EmptyResult: Decodable {} + +struct LoginResult: Decodable { + let token: String + let customer: CustomerProfile? +} + +struct CustomerProfile: Decodable { + let id: String + let name: String + let email: String + let phoneNumber: String? +} + +struct StoreSummary: Decodable { + let id: String + let name: String + let logo: String? + let cover: String? + let category: String? + let rating: Double? + let deliveryTime: String? + let deliveryFee: Double? + let distance: Double? + let isOpen: Bool? + let statusLabel: String? +} diff --git a/pedi-foods/Sources/PediFoods/Services/TokenStore.swift b/pedi-foods/Sources/PediFoods/Services/TokenStore.swift new file mode 100644 index 0000000..6760242 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Services/TokenStore.swift @@ -0,0 +1,20 @@ +import Foundation + +protocol TokenStore: AnyObject { + var jwt: String? { get set } + func clear() +} + +final class DefaultTokenStore: TokenStore { + private let key = "auth_jwt" + private let defaults = UserDefaults.standard + + var jwt: String? { + get { defaults.string(forKey: key) } + set { defaults.set(newValue, forKey: key) } + } + + func clear() { + defaults.removeObject(forKey: key) + } +} diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/AuthFlowView.swift b/pedi-foods/Sources/PediFoods/Views/Auth/AuthFlowView.swift new file mode 100644 index 0000000..df6f41d --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Views/Auth/AuthFlowView.swift @@ -0,0 +1,149 @@ +import SwiftUI + +struct AuthFlowView: View { + @Binding var root: RootFlow + @Binding var selectedTab: MainTab + let tokenStore: TokenStore + + var body: some View { + NavigationStack { + LoginView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore) + } + } +} + +struct LoginView: View { + @Binding var root: RootFlow + @Binding var selectedTab: MainTab + let tokenStore: TokenStore + @State var email = "" + @Environment(\.colorScheme) var colorScheme + + @ViewBuilder private var logoImage: some View { +#if os(Android) + Image(colorScheme == .dark ? "pedifoods_dark" : "pedifoods", bundle: .module) + .resizable() +#else + SwiftUI.Image("pedifoods") + .resizable() +#endif + } + + @ViewBuilder private var pinHeroImage: some View { +#if os(Android) + Image(colorScheme == .dark ? "pin_image_app_dark" : "pin_image_app", bundle: .module) + .resizable() +#else + SwiftUI.Image("pin_image_app") + .resizable() +#endif + } + + var body: some View { + ZStack(alignment: .top) { + (colorScheme == .dark ? AppColors.backgroundDark : AppColors.backgroundLight) + .ignoresSafeArea() + + pinHeroImage + .scaledToFill() + .frame(height: 520) + .offset(y: -90) + .mask( + LinearGradient( + colors: [.black, .black, .black.opacity(0.0)], + startPoint: .top, + endPoint: .bottom + ) + ) + + VStack(spacing: 20) { + Spacer().frame(height: 280) + logoImage + .scaledToFit() + .frame(height: 200) + + Text("Descubra lorem ipsum ba bla") + .font(AppTypography.heading1) + .foregroundStyle(colorScheme == .dark ? AppColors.textInverse : AppColors.textPrimary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 28) + + Spacer() + + PrimaryButton(title: "ENTRAR") { + // TODO: integrar com ApiService e OTP + tokenStore.jwt = "demo-token" + selectedTab = .home + root = .main + } + .padding(.horizontal, 28) + + HStack(spacing: 6) { + Text("Não tem conta ainda?") + .foregroundStyle(colorScheme == .dark ? AppColors.textInverse.opacity(0.9) : AppColors.textPrimary) + NavigationLink("Criar conta") { + RegistrationView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore) + } + .foregroundStyle(AppColors.primary) + } + .font(AppTypography.body) + + Spacer().frame(height: 24) + } + } + .ignoresSafeArea() + } +} + +struct RegistrationView: View { + @Binding var root: RootFlow + @Binding var selectedTab: MainTab + let tokenStore: TokenStore + @State var name = "" + @State var email = "" + @State var phone = "" + + var body: some View { + ScrollView { + VStack(spacing: 20) { + Text("Cadastro") + .font(AppTypography.heading1) + .foregroundStyle(AppColors.textPrimary) + + VStack(spacing: 12) { + TextField("Nome", text: $name) + .textFieldStyle(.roundedBorder) + TextField("Email", text: $email) + .textFieldStyle(.roundedBorder) + TextField("Telefone", text: $phone) + .textFieldStyle(.roundedBorder) + + PrimaryButton(title: "Enviar OTP") { + // TODO: solicitar OTP + } + } + } + .padding(24) + } + .background(AppColors.backgroundLight) + } +} + +struct OtpView: View { + let email: String + + var body: some View { + VStack(spacing: 16) { + Text("OTP") + .font(AppTypography.heading1) + .foregroundStyle(AppColors.textPrimary) + + Text("Enviado para \(email)") + .font(.caption) + .foregroundStyle(AppColors.secondary) + } + .padding(24) + .background(AppColors.backgroundLight) + } +} diff --git a/pedi-foods/Sources/PediFoods/Views/Main/CartView.swift b/pedi-foods/Sources/PediFoods/Views/Main/CartView.swift new file mode 100644 index 0000000..5982c25 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Views/Main/CartView.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct CartView: View { + var body: some View { + VStack(spacing: 16) { + Text("Carrinho") + .font(AppTypography.heading1) + + Text("Seu carrinho esta vazio") + .foregroundStyle(AppColors.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(AppColors.backgroundLight) + } +} diff --git a/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift b/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift new file mode 100644 index 0000000..445e333 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift @@ -0,0 +1,399 @@ +import SwiftUI +#if os(iOS) +import UIKit +#endif + +struct HomeView: View { + @State var searchText = "" + @State var selectedCategory = "Stores" + @State var scrollOffset: CGFloat = 0 + + private let categories: [CategoryModel] = [ + .init(title: "Stores", systemIcon: "storefront"), + .init(title: "Asian", systemIcon: "fork.knife"), + .init(title: "Breakfast", systemIcon: "sun.max"), + .init(title: "Pizza", systemIcon: "takeoutbag.and.cup.and.straw"), + .init(title: "Dessert", systemIcon: "cup.and.saucer") + ] + + private let featuredStores: [FeaturedStoreCardModel] = [ + .init( + name: "Burger Kingdom", + rating: 4.9, + reviews: "1.2k", + distance: "1.2 km", + category: "Fast Food", + promoText: "10% OFF", + isFavorite: false, + iconName: "takeoutbag.and.cup.and.straw" + ), + .init( + name: "Sushi House", + rating: 4.8, + reviews: "840", + distance: "2.2 km", + category: "Japanese", + promoText: nil, + isFavorite: true, + iconName: "fork.knife" + ) + ] + + private let nearbyStores: [FeaturedStoreCardModel] = [ + .init( + name: "Pizza Prime", + rating: 4.7, + reviews: "510", + distance: "1.8 km", + category: "Pizza", + promoText: nil, + isFavorite: false, + iconName: "takeoutbag.and.cup.and.straw" + ), + .init( + name: "Aoyama", + rating: 4.9, + reviews: "2.4k", + distance: "3.1 km", + category: "Japanese", + promoText: nil, + isFavorite: true, + iconName: "fork.knife" + ) + ] + + private let specials: [SpecialOfferCardModel] = [ + .init(title: "Get 50% OFF", subtitle: "For your first order", colors: [Color(hex: "#1E6B43"), Color(hex: "#58A56C")]), + .init(title: "Enjoy spicy day", subtitle: "Delivery in 20 min", colors: [Color(hex: "#F04B3E"), Color(hex: "#FF8C42")]) + ] + + private let headerExpandedHeight: CGFloat = 260 + private let headerCollapsedHeight: CGFloat = 120 + private let contentTopPadding: CGFloat = 296 + + var body: some View { + let collapseProgress = clamp(value: -scrollOffset / (headerExpandedHeight - headerCollapsedHeight), lower: 0, upper: 1) + let headerHeight = headerExpandedHeight - (headerExpandedHeight - headerCollapsedHeight) * collapseProgress + let headerPadding = headerExpandedHeight - (headerExpandedHeight - headerCollapsedHeight) * collapseProgress + + return ZStack(alignment: .top) { + #if os(iOS) + TrackableScrollView(onOffsetChange: { value in + scrollOffset = min(0, -value) + }) { + contentStack + .padding(.top, headerPadding + 16) + .padding(.bottom, 24) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + #else + ScrollView { + contentStack + .padding(.top, headerPadding + 16) + .padding(.bottom, 24) + } + .coordinateSpace(name: "scroll") + .onPreferenceChange(ScrollOffsetKey.self) { value in + scrollOffset = min(0, value) + } + #endif + + header(collapseProgress: collapseProgress, height: headerHeight) + .frame(maxWidth: .infinity, alignment: .top) + } + .background(AppColors.backgroundLight) + .ignoresSafeArea(edges: .top) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + } + + private var contentStack: some View { + VStack(spacing: 24) { + scrollOffsetMarker + + categoriesSection + + section(title: "Featured") { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(featuredStores) { store in + FeaturedStoreCard(store: store) + .frame(width: 190) + } + } + .padding(.horizontal, 20) + } + } + + section(title: "#SpecialForYou") { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(specials) { item in + SpecialOfferCard(model: item) + .frame(width: 260, height: 120) + } + } + .padding(.horizontal, 20) + } + } + + section(title: "Near you") { + VStack(spacing: 16) { + ForEach(nearbyStores) { store in + FeaturedStoreCard(store: store) + } + } + .padding(.horizontal, 20) + } + } + } + + private func header(collapseProgress: CGFloat, height: CGFloat) -> some View { + let titleOpacity = 1 - clamp(value: collapseProgress * 1.2, lower: 0, upper: 1) + let topRowOpacity = 1 - clamp(value: collapseProgress * 1.4, lower: 0, upper: 1) + + return ZStack(alignment: .top) { + RoundedRectangle(cornerRadius: 32, style: .continuous) + .fill(AppColors.brandDark) + .frame(height: height) + .overlay(headerRings.opacity(1 - collapseProgress), alignment: .topTrailing) + + VStack(alignment: .leading, spacing: 16) { + Spacer().frame(height: 20) + HStack(alignment: .center, spacing: 12) { + Circle() + .fill(AppColors.brandSoft) + .frame(width: 40, height: 40) + .overlay( + Image(systemName: "person.fill") + .foregroundStyle(AppColors.brandDark) + ) + + VStack(alignment: .leading, spacing: 4) { + Text("DELIVERY LOCATION") + .font(AppTypography.overline) + .tracking(AppTypography.captionLetterSpacing) + .foregroundStyle(AppColors.brandSoft) + + HStack(spacing: 6) { + Text("Canggu, Kuta Utara, Bali") + .font(AppTypography.heading3) + .foregroundStyle(AppColors.textInverse) + Image(systemName: "chevron.down") + .font(.caption) + .foregroundStyle(AppColors.brandSoft) + } + } + + Spacer() + + Circle() + .fill(Color.white.opacity(0.18)) + .frame(width: 40, height: 40) + .overlay( + Image(systemName: "bell") + .foregroundStyle(AppColors.textInverse) + ) + } + .opacity(topRowOpacity) + .offset(y: collapseProgress * -12) + + Text("O que vai querer pedir hoje?") + .font(AppTypography.heading1) + .foregroundStyle(AppColors.textInverse) + .fixedSize(horizontal: false, vertical: true) + .opacity(titleOpacity) + .offset(y: collapseProgress * -20) + + SearchBar(placeholder: "Search menu, restaurant or craving", text: $searchText) + .offset(y: collapseProgress * -140) + } + .padding(.horizontal, 20) + .padding(.top, 18) + } + } + + @ViewBuilder + private var scrollOffsetMarker: some View { + #if os(iOS) + EmptyView() + #else + ScrollOffsetReader() + .offset(y: -contentTopPadding) + #endif + } + + private func section(title: String, @ViewBuilder content: () -> Content) -> some View { + VStack(alignment: .leading, spacing: 16) { + Text(title) + .font(AppTypography.heading2) + .foregroundStyle(AppColors.textPrimary) + .padding(.horizontal, 20) + + content() + } + } + + private var categoriesSection: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Categories") + .font(AppTypography.heading2) + .foregroundStyle(AppColors.textPrimary) + .padding(.horizontal, 20) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 12) { + ForEach(categories) { category in + CategoryChip( + title: category.title, + systemIcon: category.systemIcon, + isActive: category.title == selectedCategory + ) + .onTapGesture { + selectedCategory = category.title + } + } + } + .padding(.horizontal, 20) + } + } + } + + private var headerRings: some View { + ZStack { + Circle() + .stroke(Color.white.opacity(0.08), lineWidth: 1) + .frame(width: 180, height: 180) + .offset(x: 40, y: -10) + Circle() + .stroke(Color.white.opacity(0.08), lineWidth: 1) + .frame(width: 130, height: 130) + .offset(x: 70, y: 10) + } + } +} + +struct CategoryModel: Identifiable { + let id = UUID() + let title: String + let systemIcon: String +} + +struct CategoryChip: View { + let title: String + let systemIcon: String + let isActive: Bool + + var body: some View { + HStack(spacing: 8) { + Image(systemName: systemIcon) + .font(.caption) + Text(title) + .font(AppTypography.heading3) + } + .foregroundStyle(isActive ? AppColors.textInverse : AppColors.textPrimary) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(isActive ? AppColors.brandDark : AppColors.surface) + .clipShape(Capsule()) + } +} + +struct SearchBar: View { + let placeholder: String + @Binding var text: String + + var body: some View { + HStack(spacing: 12) { + Image(systemName: "magnifyingglass") + .foregroundStyle(AppColors.textMuted) + TextField(placeholder, text: $text) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + Spacer() + Image(systemName: "slider.horizontal.3") + .foregroundStyle(AppColors.textMuted) + } + .padding(.horizontal, 16) + .frame(height: 52) + .background(AppColors.surface) + .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) + } +} + +struct ScrollOffsetKey: PreferenceKey { + static let defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } +} + +struct ScrollOffsetReader: View { + var body: some View { + GeometryReader { proxy in + Color.clear + .preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .named("scroll")).minY) + } + .frame(height: 1) + } +} + +func clamp(value: CGFloat, lower: CGFloat, upper: CGFloat) -> CGFloat { + min(max(value, lower), upper) +} + +#if os(iOS) +@MainActor +struct TrackableScrollView: UIViewRepresentable { + let onOffsetChange: (CGFloat) -> Void + let content: Content + + init(onOffsetChange: @escaping (CGFloat) -> Void, @ViewBuilder content: () -> Content) { + self.onOffsetChange = onOffsetChange + self.content = content() + } + + func makeCoordinator() -> Coordinator { + Coordinator(onOffsetChange: onOffsetChange) + } + + func makeUIView(context: Context) -> UIScrollView { + let scrollView = UIScrollView() + scrollView.showsVerticalScrollIndicator = false + scrollView.alwaysBounceVertical = true + + let host = UIHostingController(rootView: content) + host.view.translatesAutoresizingMaskIntoConstraints = false + host.view.backgroundColor = .clear + scrollView.addSubview(host.view) + + NSLayoutConstraint.activate([ + host.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), + host.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), + host.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), + host.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), + host.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) + ]) + + context.coordinator.hostingController = host + scrollView.delegate = context.coordinator + return scrollView + } + + func updateUIView(_ uiView: UIScrollView, context: Context) { + context.coordinator.hostingController?.rootView = content + } + + final class Coordinator: NSObject, UIScrollViewDelegate { + var hostingController: UIHostingController? + let onOffsetChange: (CGFloat) -> Void + + init(onOffsetChange: @escaping (CGFloat) -> Void) { + self.onOffsetChange = onOffsetChange + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + onOffsetChange(scrollView.contentOffset.y) + } + } +} +#endif diff --git a/pedi-foods/Sources/PediFoods/Views/Main/MainTabView.swift b/pedi-foods/Sources/PediFoods/Views/Main/MainTabView.swift new file mode 100644 index 0000000..a525676 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Views/Main/MainTabView.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct MainTabView: View { + @Binding var selectedTab: MainTab + @Binding var root: RootFlow + let tokenStore: TokenStore + + var body: some View { + TabView(selection: $selectedTab) { + NavigationStack { + HomeView() + } + .tabItem { Label("Home", systemImage: "house.fill") } + .tag(MainTab.home) + + NavigationStack { + CartView() + } + .tabItem { Label("Carrinho", systemImage: "cart.fill") } + .tag(MainTab.cart) + + NavigationStack { + ProfileView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore) + } + .tabItem { Label("Perfil", systemImage: "person.fill") } + .tag(MainTab.profile) + } + .tint(AppColors.primary) + } +} diff --git a/pedi-foods/Sources/PediFoods/Views/Main/ProfileView.swift b/pedi-foods/Sources/PediFoods/Views/Main/ProfileView.swift new file mode 100644 index 0000000..f51b537 --- /dev/null +++ b/pedi-foods/Sources/PediFoods/Views/Main/ProfileView.swift @@ -0,0 +1,74 @@ +import SwiftUI + +struct ProfileView: View { + @Binding var root: RootFlow + @Binding var selectedTab: MainTab + let tokenStore: TokenStore + + var body: some View { + VStack(spacing: 20) { + header + + VStack(spacing: 12) { + NavigationLink("Pedidos") { + OrdersView() + } + + NavigationLink("Enderecos") { + Text("Enderecos") + } + + NavigationLink("Ajuda") { + Text("Ajuda") + } + } + .font(AppTypography.body) + .foregroundStyle(AppColors.textPrimary) + + Spacer() + + SecondaryButton(title: "Sair") { + tokenStore.clear() + root = .auth + } + .padding(.horizontal, 20) + } + .padding(.top, 24) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(AppColors.backgroundLight) + } + + private var header: some View { + VStack(spacing: 8) { + Circle() + .fill(AppColors.primary) + .frame(width: 72, height: 72) + .overlay( + Text("DL") + .font(.headline) + .foregroundStyle(AppColors.textInverse) + ) + + Text("Daniel Loverde") + .font(AppTypography.heading2) + .foregroundStyle(AppColors.textPrimary) + + Text("daniel@pedifoods.com.br") + .font(.caption) + .foregroundStyle(AppColors.secondary) + } + } +} + +struct OrdersView: View { + var body: some View { + VStack(spacing: 16) { + Text("Pedidos") + .font(AppTypography.heading1) + Text("Historico vazio") + .foregroundStyle(AppColors.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(AppColors.backgroundLight) + } +} diff --git a/pedifoods_system_design.md b/pedifoods_system_design.md index 5b43f95..546c245 100644 --- a/pedifoods_system_design.md +++ b/pedifoods_system_design.md @@ -14,12 +14,11 @@ Este arquivo deve ser usado como **prompt base canônico** para qualquer IA, des ```ts colors: { - primary: "#8CFF2E", // ação principal, CTAs, destaque - secondary: "#388E3C", // ações secundárias, títulos + primary: "#388E3C", // ação principal, CTAs, destaque backgroundLight: "#F7F8F5", backgroundDark: "#18230F", - textPrimary: "#333333", - textInverse: "#FFFFFF" + textPrimary: "#FFFFFF", + textInverse: "#333333" } ``` @@ -31,21 +30,6 @@ Uso: --- -### 1.2 🌙 Dark Theme - -Dark mode nativo, não invertido. - -```ts -dark: { - background: "#18230F", - surface: "rgba(255,255,255,0.05)", - textPrimary: "#FFFFFF", - textSecondary: "#A3A3A3", - primary: "#8CFF2E", - secondary: "#388E3C" -} -``` - --- ### 1.3 🔤 Tipografia @@ -70,7 +54,7 @@ typography: { body: { size: "16px", weight: 400, - lineHeight: "1.6" + lineHeight: "0" }, button: { @@ -195,30 +179,3 @@ Icon { active?: boolean } ``` - ---- - -## 3. IMPLEMENTAÇÃO (TAILWIND-FIRST) - -### 3.1 Tokens no Tailwind - -```ts -theme.extend.colors = { - primary: "#8CFF2E", - secondary: "#388E3C", - backgroundLight: "#F7F8F5", - backgroundDark: "#18230F", - typographyDark: "#333333" -} -``` - ---- - -## 4. PRINCÍPIOS DO SISTEMA - -- Mobile-first -- Tailwind-first -- Dark mode por classe -- Tokens semânticos -- UI energética e saudável -- Feedback visual imediato diff --git a/reference_img/login_dark.png b/reference_img/login_dark.png new file mode 100644 index 0000000..bf637d4 Binary files /dev/null and b/reference_img/login_dark.png differ diff --git a/reference_img/login_light.png b/reference_img/login_light.png new file mode 100644 index 0000000..e7c5b09 Binary files /dev/null and b/reference_img/login_light.png differ diff --git a/reference_img/pin_image_app.png b/reference_img/pin_image_app.png new file mode 100644 index 0000000..7ed510e Binary files /dev/null and b/reference_img/pin_image_app.png differ diff --git a/reference_img/pin_image_app_dark.png b/reference_img/pin_image_app_dark.png new file mode 100644 index 0000000..450d7bc Binary files /dev/null and b/reference_img/pin_image_app_dark.png differ