From 31ffec0f2be2c19185da53800c1f8b79547cf185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=89=E7=A7=8B?= <497055328@qq.com> Date: Mon, 6 Jul 2026 15:12:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=99=BB=E5=BD=95=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E3=80=81=E4=BC=9A=E8=AF=9D=E8=B7=AF=E7=94=B1=E5=92=8C?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E5=8F=82=E8=80=83=20UI=20=E7=9A=84=E4=B8=BB?= =?UTF-8?q?=20Tab=20=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- suixinkan.xcodeproj/project.pbxproj | 2 - suixinkan/App/AppRouter.swift | 57 +++++ .../Contents.json | 12 + .../icon_cb_privacy_selected.png | Bin 0 -> 1825 bytes .../Contents.json | 12 + .../icon_cb_privacy_unselect.png | Bin 0 -> 2059 bytes .../icon_pwd_invisible.imageset/Contents.json | 12 + .../icon_pwd_invisible.png | Bin 0 -> 2653 bytes .../icon_pwd_visible.imageset/Contents.json | 12 + .../icon_pwd_visible.png | Bin 0 -> 2552 bytes .../icon_scan.imageset/Contents.json | 23 ++ .../icon_scan.imageset/icon_scan.png | Bin 0 -> 381 bytes .../icon_scan.imageset/icon_scan@2x.png | Bin 0 -> 675 bytes .../icon_scan.imageset/icon_scan@3x.png | Bin 0 -> 1022 bytes .../img_login_bg.imageset/Contents.json | 12 + .../img_login_bg.imageset/img_login_bg.png | Bin 0 -> 78533 bytes .../tab_data_selected.imageset/Contents.json | 23 ++ .../tab_data_selected.png | Bin 0 -> 908 bytes .../tab_data_selected@2x.png | Bin 0 -> 1802 bytes .../tab_data_selected@3x.png | Bin 0 -> 3015 bytes .../Contents.json | 23 ++ .../tab_data_unselected.png | Bin 0 -> 1169 bytes .../tab_data_unselected@2x.png | Bin 0 -> 2227 bytes .../tab_data_unselected@3x.png | Bin 0 -> 3864 bytes .../tab_home_selected.imageset/Contents.json | 23 ++ .../tab_home_selected.png | Bin 0 -> 867 bytes .../tab_home_selected@2x.png | Bin 0 -> 1673 bytes .../tab_home_selected@3x.png | Bin 0 -> 2910 bytes .../Contents.json | 23 ++ .../tab_home_unselected.png | Bin 0 -> 1129 bytes .../tab_home_unselected@2x.png | Bin 0 -> 2146 bytes .../tab_home_unselected@3x.png | Bin 0 -> 3892 bytes .../tab_order_selected.imageset/Contents.json | 23 ++ .../tab_order_selected.png | Bin 0 -> 733 bytes .../tab_order_selected@2x.png | Bin 0 -> 1246 bytes .../tab_order_selected@3x.png | Bin 0 -> 1834 bytes .../Contents.json | 23 ++ .../tab_order_unselected.png | Bin 0 -> 1026 bytes .../tab_order_unselected@2x.png | Bin 0 -> 1667 bytes .../tab_order_unselected@3x.png | Bin 0 -> 2410 bytes .../Contents.json | 23 ++ .../tab_profile_selected.png | Bin 0 -> 710 bytes .../tab_profile_selected@2x.png | Bin 0 -> 1485 bytes .../tab_profile_selected@3x.png | Bin 0 -> 2264 bytes .../Contents.json | 23 ++ .../tab_profile_unselected.png | Bin 0 -> 944 bytes .../tab_profile_unselected@2x.png | Bin 0 -> 1939 bytes .../tab_profile_unselected@3x.png | Bin 0 -> 2944 bytes suixinkan/DataStore/AppStore.swift | 44 ++++ suixinkan/Info.plist | 2 - suixinkan/SceneDelegate.swift | 67 +++--- suixinkan/Theme/AppColor.swift | 19 ++ suixinkan/Theme/UIColor+Hex.swift | 19 ++ suixinkan/UI/Common/AgreementRowView.swift | 114 +++++++++ suixinkan/UI/Common/AppButton.swift | 59 +++++ suixinkan/UI/Common/LoginTextField.swift | 83 +++++++ suixinkan/UI/Common/PasswordInputField.swift | 104 ++++++++ suixinkan/UI/Home/HomeViewController.swift | 32 +++ suixinkan/UI/Login/LoginViewController.swift | 224 ++++++++++++++++++ suixinkan/UI/Login/LoginViewModel.swift | 61 +++++ suixinkan/UI/MainTab/AppTab.swift | 64 +++++ suixinkan/UI/MainTab/MainScanTabItem.swift | 19 ++ .../UI/MainTab/MainTabBadgeViewModel.swift | 21 ++ .../UI/MainTab/MainTabBarController.swift | 154 ++++++++++++ .../UI/MainTab/TabNavigationController.swift | 73 ++++++ .../UI/Orders/OrdersViewController.swift | 28 +++ .../UI/Profile/ProfileViewController.swift | 28 +++ .../Scan/ScanPlaceholderViewController.swift | 50 ++++ .../Statistics/StatisticsViewController.swift | 28 +++ suixinkan/ViewController.swift | 17 -- 70 files changed, 1588 insertions(+), 48 deletions(-) create mode 100644 suixinkan/App/AppRouter.swift create mode 100644 suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/icon_cb_privacy_selected.png create mode 100644 suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/icon_cb_privacy_unselect.png create mode 100644 suixinkan/Assets.xcassets/icon_pwd_invisible.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/icon_pwd_invisible.imageset/icon_pwd_invisible.png create mode 100644 suixinkan/Assets.xcassets/icon_pwd_visible.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/icon_pwd_visible.imageset/icon_pwd_visible.png create mode 100644 suixinkan/Assets.xcassets/icon_scan.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan.png create mode 100644 suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@2x.png create mode 100644 suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@3x.png create mode 100644 suixinkan/Assets.xcassets/img_login_bg.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/img_login_bg.imageset/img_login_bg.png create mode 100644 suixinkan/Assets.xcassets/tab_data_selected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected.png create mode 100644 suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_data_unselected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_data_unselected.imageset/tab_data_unselected.png create mode 100644 suixinkan/Assets.xcassets/tab_data_unselected.imageset/tab_data_unselected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_data_unselected.imageset/tab_data_unselected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_home_selected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected.png create mode 100644 suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_home_unselected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_home_unselected.imageset/tab_home_unselected.png create mode 100644 suixinkan/Assets.xcassets/tab_home_unselected.imageset/tab_home_unselected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_home_unselected.imageset/tab_home_unselected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_order_selected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_order_selected.imageset/tab_order_selected.png create mode 100644 suixinkan/Assets.xcassets/tab_order_selected.imageset/tab_order_selected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_order_selected.imageset/tab_order_selected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_order_unselected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_order_unselected.imageset/tab_order_unselected.png create mode 100644 suixinkan/Assets.xcassets/tab_order_unselected.imageset/tab_order_unselected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_order_unselected.imageset/tab_order_unselected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_selected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected@3x.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_unselected.imageset/Contents.json create mode 100644 suixinkan/Assets.xcassets/tab_profile_unselected.imageset/tab_profile_unselected.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_unselected.imageset/tab_profile_unselected@2x.png create mode 100644 suixinkan/Assets.xcassets/tab_profile_unselected.imageset/tab_profile_unselected@3x.png create mode 100644 suixinkan/DataStore/AppStore.swift create mode 100644 suixinkan/Theme/AppColor.swift create mode 100644 suixinkan/Theme/UIColor+Hex.swift create mode 100644 suixinkan/UI/Common/AgreementRowView.swift create mode 100644 suixinkan/UI/Common/AppButton.swift create mode 100644 suixinkan/UI/Common/LoginTextField.swift create mode 100644 suixinkan/UI/Common/PasswordInputField.swift create mode 100644 suixinkan/UI/Home/HomeViewController.swift create mode 100644 suixinkan/UI/Login/LoginViewController.swift create mode 100644 suixinkan/UI/Login/LoginViewModel.swift create mode 100644 suixinkan/UI/MainTab/AppTab.swift create mode 100644 suixinkan/UI/MainTab/MainScanTabItem.swift create mode 100644 suixinkan/UI/MainTab/MainTabBadgeViewModel.swift create mode 100644 suixinkan/UI/MainTab/MainTabBarController.swift create mode 100644 suixinkan/UI/MainTab/TabNavigationController.swift create mode 100644 suixinkan/UI/Orders/OrdersViewController.swift create mode 100644 suixinkan/UI/Profile/ProfileViewController.swift create mode 100644 suixinkan/UI/Scan/ScanPlaceholderViewController.swift create mode 100644 suixinkan/UI/Statistics/StatisticsViewController.swift delete mode 100644 suixinkan/ViewController.swift diff --git a/suixinkan.xcodeproj/project.pbxproj b/suixinkan.xcodeproj/project.pbxproj index 6a8aa08..25d5189 100644 --- a/suixinkan.xcodeproj/project.pbxproj +++ b/suixinkan.xcodeproj/project.pbxproj @@ -163,7 +163,6 @@ INFOPLIST_FILE = suixinkan/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16; @@ -200,7 +199,6 @@ INFOPLIST_FILE = suixinkan/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16; diff --git a/suixinkan/App/AppRouter.swift b/suixinkan/App/AppRouter.swift new file mode 100644 index 0000000..c7adff6 --- /dev/null +++ b/suixinkan/App/AppRouter.swift @@ -0,0 +1,57 @@ +// +// AppRouter.swift +// suixinkan +// + +import UIKit + +/// 应用根页面路由。 +enum AppRouter { + + enum Root { + case login + case mainTab + } + + /// 根据登录态创建根控制器:已登录 → 主页 Tab,未登录 → 登录页。 + static func makeRootViewController() -> UIViewController { + makeViewController(for: AppStore.shared.isLoggedIn ? .mainTab : .login) + } + + /// 切换窗口根控制器。 + static func setRoot(_ root: Root, on window: UIWindow?, animated: Bool = true) { + setRoot(makeViewController(for: root), on: window, animated: animated) + } + + /// 根据当前登录态刷新根控制器。 + static func refreshRoot(on window: UIWindow?, animated: Bool = true) { + setRoot(AppStore.shared.isLoggedIn ? .mainTab : .login, on: window, animated: animated) + } + + private static func makeViewController(for root: Root) -> UIViewController { + switch root { + case .login: + return UINavigationController(rootViewController: LoginViewController()) + case .mainTab: + return MainTabBarController() + } + } + + private static func setRoot(_ viewController: UIViewController, on window: UIWindow?, animated: Bool) { + guard let window else { return } + + guard animated, let snapshot = window.snapshotView(afterScreenUpdates: true) else { + window.rootViewController = viewController + return + } + + viewController.view.addSubview(snapshot) + window.rootViewController = viewController + + UIView.animate(withDuration: 0.25, animations: { + snapshot.alpha = 0 + }, completion: { _ in + snapshot.removeFromSuperview() + }) + } +} diff --git a/suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/Contents.json b/suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/Contents.json new file mode 100644 index 0000000..7c9a98d --- /dev/null +++ b/suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_cb_privacy_selected.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/icon_cb_privacy_selected.png b/suixinkan/Assets.xcassets/icon_cb_privacy_selected.imageset/icon_cb_privacy_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..7dfce280b01086615de3f47bcd19a4af775dba4c GIT binary patch literal 1825 zcmV++2j2LJP)(}>t3%Q<2{#MceBi+dvkN4ts02=8YzUc=U>;k8Fl~FuFVGQ6afWG9r z0HVPwbl>mw01PBQmNb*L1aT4Q`lkD6L7D@Io^q`R^5kD-U<+uB0Q68_dVwyDxd1*F z{kKhemVbZ@Ftk`C?Yh&P)Mp@wDE0lL-?V!*gY-|aM27|UwMhlnat4WJu66L7qeVHM*` zv>8+dsXRag2dF)_(Y_3XlOD=;TMi&{&T)cca8VcuNVg~<#R1}K#ra%9`9W6Y7&w-K z#uWpIagIW1_bg~b`_(uWj;TP;#Q-|ROmuZ5eUzgkane(f!Q=yUt+`tDizqv?Iz8c| z+{GpvAnv~?lp?hw1BoxVnXe?-0G;4>8SqD%S~8rSL!m3X4!WcGWH0iY$; z7;f?4ipywud!t~6Uc`O#lbq*P4e3qwuhKag>!UZ&sTs74Frew2zLek#eLVFINDdJQdmHJqg&N&%O-gCh9U6EMOvD6cCNEfHTFe zG(pO{`W<=OgUSpfO({K)KVTq{hUNEaiZ#yVGM}3aICoMvI;g7|QjYpyE`<_ofHaCf zX*h%+@q0a=dRS$u-au~BxC(Ds=QMCwsVP#X?wn);q)?Aov6YT&jL%_q2DUeYsY7o{ zJ}r%@l*K&omCdJNUcv#|CH({~m;QF(5s>U%5~FBIOO@K`9_ni~NZl zTy20RK^kTL6s1j4MogCAv`1WQ4`R7Oe!PNOG7ymFFq?CcqV`ES>f15qbb$WFkEms1 zxqc7^kZ;(V0QJI3mn-_r2E_rQ!h3Zmt6J{7Wz_>s+?Xr1=#&G9qvj?TaE$TMBhohJkS*l_qCV5n z@qol&(AZ4tOQmW6u?u#{`ON$wC)#MdzLJ&I0b+NClek=3UjtaY=d=>O97?QHigS`S z*8_;!xFT@l9dN!8qkUO|Hqo}4eb)zw1~8_GJ__#7n>5}6ubHTG&?eLuKLCYxm`}`S zVkG0G_JA_LjIqH5Ja|nw9G?9GM2&p;BR8!P0@d8qGr$aCfZ>a$_nim-01+gn5GYPP zg$pP!!1VFby9{(0j^VHCAK_^_Aw`-4Xty)&_kLGbAUCY>MW*0T5~m?!siow}9lK9| z72Gq#|15ddy#&3II;e~L=DU3~ly3*1UE_^snUJ5c6bBsspT$Hem}LIh1q8Kn0)3CqOws{RaR5|NnOE-@*U@00v1!K~w_(tAGoyXrPH$ P00000NkvXXu0mjfp@B|+ literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/Contents.json b/suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/Contents.json new file mode 100644 index 0000000..c688206 --- /dev/null +++ b/suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_cb_privacy_unselect.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/icon_cb_privacy_unselect.png b/suixinkan/Assets.xcassets/icon_cb_privacy_unselect.imageset/icon_cb_privacy_unselect.png new file mode 100644 index 0000000000000000000000000000000000000000..c53d8fd088ccc8c0ce39febb6f8f7c699a6549d9 GIT binary patch literal 2059 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 literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/icon_pwd_visible.imageset/Contents.json b/suixinkan/Assets.xcassets/icon_pwd_visible.imageset/Contents.json new file mode 100644 index 0000000..4d9eeae --- /dev/null +++ b/suixinkan/Assets.xcassets/icon_pwd_visible.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_pwd_visible.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/icon_pwd_visible.imageset/icon_pwd_visible.png b/suixinkan/Assets.xcassets/icon_pwd_visible.imageset/icon_pwd_visible.png new file mode 100644 index 0000000000000000000000000000000000000000..df11b0c5e941ae62326c7c91e843bc50e463f9a3 GIT binary patch literal 2552 zcma)8c{J1u8~)A2SZ5eZ*D{vLSjVmgQD&@-eV1z+l8LLP7~&d(nG#V66S8*`BKuV+ zW|Az)a7%&J5)xGzSg<_@`MlnX8fq$Q|(1u?cx#+z z9BA1;Bj&A%lv7k|GP&U%y_|MQYupu-BP=L=r8@b);)MsugZ7On&73wvM_-9t^u06V zI9?fzNGw@Dxv}uIAf?&J>3Z^!GC>XSzaeI6CUdJAva!BS7W(K-(bd)E@wWb{#1)($ zEEwdjCMEqEO3BYx8e+32l`N=VOs&*YC1W?Y)dU=p#E`7&8!1>eJ80zL2sx8$UaB=K|KU_|3YF^Vyv}-pW;y zDbnQTq~;mXnCpzQzM$CpueJ3d3TgA~z5ICZnPrg4Iskba}skRQ9 zCjaorL`f1@dNIRM?@4gN4MJf)R!5Wak>gO3E^F4Pmq_=!MBQr75yWPp`8N6OZ`7MK zQu!2F%M#yGrQ^7HZ%`&k>=*SN?v<)r<+-O3o5A^VJHvbDFel zpKtSA8!SxQ4}KO%k^~9CdXd}koqP?a~VQ@|Fj}?RCM)JNYL{(c-q~%!G`hS>55ovxjykRZ4g9YUjsdUgy_w zXY47zw9%V3Jfxf_XyThXG@gk~u(i@kLB0NtP@q0MZk*^^NU%er8a*sJ8r$OwjXcOM z%k=RJ;*-t`dJ+VMU5HIyc9V=Lf+8lcT4B&&u@OM$`zNwmRsQM_9e=GU4nn{r+5*0; zYbU9h`uw}lr5p-f9mAa#!$m!n4^9$A1%(v1->*KSc{*qpZA{eE#eUA2UrVdxnuXQM<9t;5 zJYEO{uT?0!z;kws>&xtb4cw9!s@zi;VtUje$`t$|0hb4#Y3^aJQ|B|k!LcH{?g-!R zsFW57@;jQj%~KKR%N>_$wEq5|FoWd^9GK~O=wHogJ*shM57Z3~bJbE>St=rrM&%v5DYL!ok_iYJiH;52tNC z=(P*Hl`%9_yKG#3(!lTqOE2u#;6d9gwnX<2r@kK=pqA7WvrtNtns)!RNID5<6(diR zZawk?BEk`r&y`(mk``NuqMA?%W{P04gU{U)NO>zmx@woK#FO(yKts~iR?{DCw4h1+ z%)@2I76T>cV;6 z;97God%gv3W=rOTvNi^D)RsfRGUifAbS1Utzciw;9Uy{PPA(rmy7F8v0Wcrp{MTBprH z?nfc>aO3T%uWz-j`<^H|^>hVe@cSQvWuGlk2%p2mvzh-yZ%0jnHiPtA%`E1sxV_>O ztcub$k6ESDr!Xk{svx7aD5VHTe9KzeMll@xQG{sB=K5s}2oZ_rL>kr-9j6vr^W8cS z$J?5)OA6!y#5O`G1r^j!Pq ze;C}ZA@ZTd?7RAH`cy5^(;J*7?rp`d9^Zwj-N&W-!H9{)Mi1tHs|qcGJs%lU{uH>d z<35wgZ-8uh$}!?Cp_%-nbr3}wW0FT3DEiLVsW}_%20mViv#$P)Ks<8Une!N+=J_?& zli=WUxyGuYsLu*f^siEHLYS9XdbU_%)|Su(C-cXOck;QqFl`GFP_g9DLybhzs$=^W zs4U6S>g|R=9St!{RAoY$*(~F+V-sj|h|3T;*^t(0$Ez*tz!i&v>O8dlEWI^i_hSs$ z;_nKK3|0qfjd$s9IpMfadg(oi-4;_|(jmh?=U-d-Pt(`F&g@!?k9^pnWDNWHUf^w< Kt?MiawEqByEsuHt literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/icon_scan.imageset/Contents.json b/suixinkan/Assets.xcassets/icon_scan.imageset/Contents.json new file mode 100644 index 0000000..c5d9acc --- /dev/null +++ b/suixinkan/Assets.xcassets/icon_scan.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_scan.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_scan@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_scan@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan.png b/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan.png new file mode 100644 index 0000000000000000000000000000000000000000..a03cfd14b15993881cc0971e20fbfba4391b484c GIT binary patch literal 381 zcmV-@0fPRCP)o? z2bT)>$uzNdq6XZ61@Su( zE#ZXhyckQ*7PQwld)m4Kz}DoPu~bKcth0kS{w zDr9LxN3_Jts09-V4e36hX@U@&*2pwW5M$CiaZ3}7SiGY|3lq#31HNYU>Gnl;2!+2I bJZSR)*otm-In#S@00000NkvXXu0mjfId-8U literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@2x.png b/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f76573f9ef99c276e427976e45a3a78760c72f3 GIT binary patch literal 675 zcmV;U0$lxxP) zTXMr75Cr8^Zk!w=KC|;hxk?rik6mU3-QP*U&M+V(S!^2s002mMs5?;1&+Aq7_s_>I zYKz&)TCqzBXiA(8d5NAQh#A^Wj21nI&%)@ozG zO5!JsYVJ2+Me!2`wf7mYviONk4cZMr{5Kt1`0s?_lr{qx{hu^yQoplnZ0Pk@|38o4 zmX^eASMHYU62Qp6rc$GCuYM$2MqdO{zpYZ3qdWnO{&Fg`;wnf?*@DqA`j4g{?^w5D z-q(T!nFNTrHh{UmodWGhfEWxAb8Z0BPd^tLk^nInASQ1B(~lh|T9N=U`JEaGuqCrA zCIMnFK+LfLOh5KyG$jFIFhC3jh;ex)eAnl|S9m-~A^~DBKnwd#J?d4GqOT-COx^$%VmnT>BmrVDK+L%TOhf%#Xh;IY zV1Srw1DJ-|;WuLODIeVfKT(iSeyX6m4}~)a2@q3m0Bb=x6GAYV#FqmL0R>pz_^_&5#D?8vJ*{cQ#=_-#N&xZH6@_f0000Gh<|G_wHSSo*lhp+002ov JPDHLkV1kkxB2oYV literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@3x.png b/suixinkan/Assets.xcassets/icon_scan.imageset/icon_scan@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..72e33400b04b796617cf2493492bfb490bbb6742 GIT binary patch literal 1022 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 a8uwt)+OE|aAxlNzAKS%FiwdnNd;SZA-c$Af literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_data_selected.imageset/Contents.json b/suixinkan/Assets.xcassets/tab_data_selected.imageset/Contents.json new file mode 100644 index 0000000..90c8ea2 --- /dev/null +++ b/suixinkan/Assets.xcassets/tab_data_selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "tab_data_selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tab_data_selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tab_data_selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected.png b/suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5fe1ce77ddf7808ca856ad4ec0bb2ae0472ee4 GIT binary patch literal 908 zcmV;719SX|P)P000>X1^@s6#OZ}&000A1NklyuHUvSzl$sDsk)54+?^@h9Gdr`pQ9& zlAfAK|9t-M+EOZa#!caqR7xrSjoImPf#M!5-wBf9^=#Anw zJ4<0%*FE2BJ7%C3wimJA1bu_0-EpB{*f}Y zz=~2bH)cysx5J)prt6~%+ld7IzD(SfX}0CDYek7+(STIwZ>>m5R4EsCI3*I3ja+z< zkKW|)0nFagxj-97Bl0h4QtGE{#jl>$xBB~Q#y}#L{{ZutenZM26B8d zRh|2YT~l^&cZ2?LTc)*wMm){llXVjb6p?FD7R0000&bCVt?9tyPPH zAE2TS(t-*iYEiII1s@e7RgtFJsI8`Iifx*`ca9G;`*rtjcJC&Q75sfhy~z2T}I4$U>(95lp#WTYqT}eTy2^KEPnz&0AHefhVl_RWE^mwA3<9o)B+Ke zvZBqpoIIOQZXskP#v2v`7EPGh9OC~1Z;1tB#xT5$c^e_zAMlq9aR4K40 zx!IGj^GX6hFer+}lH)-pxtS5Met=WHUI!msTUm-_FF7vs@i2cRYRthBVkq(*5m&Pl zx1uGyQg2)Vw@N9ulW(7d5y%CVkF*<2f+51PfYpR-0?{gbWdL4e)B&)_Ni;+$1pq#9TohMX+QTM-gXaXC7YI0N1ar z$ae07yWS>i%B-dmV=zQqh3adF#MQ~Ve#+dfOAs#c=`NEu1R#bb-LR$~A`8ch?t19} zP=N&=JCkBE0i89Qb|COl^j`sg*{g zztMyi01NpLp1U5dTT_u;xe7MC3>wV+mblIcr7zi>7VHaVQlb`mI^nV*unNXd1Nx9N!j4N*^z_wYoWgf4*mgqzJ=H&;Z|@g*^uUuc1}>u^z?!H*Q-BKCX&JmklIsmkbz zyRVi<&VQQ&L;K9)+v2rOIdPDeU&?%Lm7Ns6JsK(}k} z^zx;PDs$Bc^euAjU3quyThxSzp#G%mE1r>~NpV#k5oMPKh{SBwPqEyFVmuiEzrGuo zoPv#SIU`Gn`REBaS#&qQABVf%g)=)V^6^RUH3NT5!PfWStc7l$W(1D>UCAFQqv!l# z5d!w1xLV3hmGVYyLLBuvEPGJK2w72gk|JfF_B9?+0cE~u&8<++wmq4xua*HdUa1rIXBjWCB zyJz~Gf2oM7|Lb$L2LDEWhd zJGGa9K>~UaE(TU23;=yV2TC_WfHH}295@X8j53Dhd-9AEahRv2aw^uw4+h%9fBz>O sN%;6_P@rrH!_~!$`TkMO|2TE{2P>O?6y0_>E&u=k07*qoM6N<$f(O}4xc~qF literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected@3x.png b/suixinkan/Assets.xcassets/tab_data_selected.imageset/tab_data_selected@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..50a064009cc0e0003271faa7ca8e54ab9732310a GIT binary patch literal 3015 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 JPDHLkV1kQ(6k7lQ literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_data_unselected.imageset/Contents.json b/suixinkan/Assets.xcassets/tab_data_unselected.imageset/Contents.json new file mode 100644 index 0000000..254b4fb --- /dev/null +++ b/suixinkan/Assets.xcassets/tab_data_unselected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "tab_data_unselected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tab_data_unselected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tab_data_unselected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/tab_data_unselected.imageset/tab_data_unselected.png b/suixinkan/Assets.xcassets/tab_data_unselected.imageset/tab_data_unselected.png new file mode 100644 index 0000000000000000000000000000000000000000..cd2a5597fe84b51ecc7306c2bfe996621fa5ac39 GIT binary patch literal 1169 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 literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@2x.png b/suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50f856bf4d8e05a0e773d5608d67f9def6846dee GIT binary patch literal 1673 zcmV;426p+0P)pWe4LIZf>DOX z3JJ!>j~9Y9XpA2b42c>|jGt(Np+Nac1>~a*EkWkqbCwVH-095ynBHM#$~^E-&coej zpS{+9ueI0SYwgP$*jR{l88tKP8#DGkDI(WJ?wVmH3-a`nGm}NH@7iJQEHaaY*{H#u zd^fJbM{yClctrS>((imdo*epSl~_HInJgCEWlx^3_GN|ZwY+PIglwM55$SH}cDKdR z1vgwJq16#tJfqY3N#SN~7phSityrnh!o0)MUUTuYp|M^AUK^3cv$#9oDSQPtXrZf; z3NdEvR12w8s&u6vVAfnbwZyvXPGs@S?aHf#12);B11gjgjngAkXk*^PYG1NH?j4db zAy!7DFJNz;qqbL?)^@h;3J46N(o*U;Asq0Eo81v7S4w6wB8y#VIxi8vWy+Ph$W@W( zqY_FbD)+1Ga$EdsC9Zb*SU!`5h-^rFTH%*Axl%_ZS}ALJ)^SA zT--g*GQ&jr9e3vr;SQy?ZWAhB52RX4q4Gazr@8pF(2AI@@x z(q%g84MPG#sjF7{kdCdeIOiGVGS{tBy0v=r<7K#Z@YN_H@7u6M`bh5|Y#K#m^BGWO zwRrPj!zd!h*I$oS1+8Bt|57@^}Ri#%l`*Eka4$YAp@k#P7-=(8%+9vN&J zCQ^GyCC8?!uO~vNQFwIl)#$}ud#NF^k({hIxs_vT&kep@ytDU|4lfGNDrgd?je(R3 zgclTE!YQ3CCenAL(sAiof!IJqLN-3~ta-zq83!T^~?*dt;HOwLIWuzjb@O(DZ%Y zP5DDyvuM0wSt*qYl^3);1|D2Wd?}GxNrrPh>V*F)T(C^!3a32xBJiE)*qNIgh?nq_ z<{8FJPMMAQX6i0nl)jC2vuHwZooSB>DoPi96Hw zWaVtPn~h$K-D-KKm3AszuGU{4rF zi^Rg4C%o#g(#4JYEGI04XzIbCWs9(7q?F#RRjMZ}ZuAy9wFh;Yg+4-)5r@WRW4t+@ z6Mmx*>WPmkWfLZAoiR^$VUbRHkCIBEcE4|Xad0DChKN&TmX?D$j%hV9V={zLEc;A; zFH>nNbR5U`MC7<@?`0NPgchyPk$G0YvE<{cDXeUF5J_MQ+I;N>G|ZYyjo!guF!J+k}p?l zpT+9csQpL!D-M{8`xbOq29saCMWk(6dao@yF2be4J8+J0o^+l`ZormGukeg?LO3oR z5&ofY*yEmXDCXDlPX#?}%QIgGUcuiyR_GMjJ9&-!!^~P?U;hlZ(f@q&2FBz6u=3D< TKc{vl00000NkvXXu0mjfG!;Qm literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@3x.png b/suixinkan/Assets.xcassets/tab_home_selected.imageset/tab_home_selected@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c4f2c7d3d9db94b6fa448204b5343685bb8f04 GIT binary patch literal 2910 zcmV-k3!(IhP)002t}1^@s6I8J)%000XmNklag2+d#2Gt|XY84|=dAv5&b@Q( znLD1jGk#?3PrCNZ+t zVL^&BHq0}Mh-5Hw+asS-`@zT!Moyfu2YhC3JQ%qhsp$NEN4!b6PVVQ0pA{~^LF|+H zFJr##bLlx$B=3;f&~^Q3!s#U|BDvjw&!lm8#~Nd9(C{0$8gIlU1|$guTHpY7;}M02 zjro>sY0O{@*ltofeX4ny$cki8X1h{KVN<+a_#l2=@_J2_olhwVWRfI9j&UWP6}~I{ zg9p+#L?jzzb|_EBdUoo`8j1`Ir_!cap>(Ir2E0{6B|)XK9!)x7Od(VxWHPxnp-(IQ zjZqKUmd1{|$0@`qBrB33x#3hP)8@EIcsJf-Kr(?)&OlJl4uz1(G%#TTU&apirLTga zXc$(UDk(cPWDP}Ti;z;-7;9y2mAqHeo3v0WY6a`({*^0{5{G+C=KA9dv^}rUHutg-Rb}olg>CRZC zovU}H*2_D zOTi*ZQ!i9fR%mD_?88@NKI>EI$!X(T02Vlbs2Ar{8j8$_-_mra+zqJKrp1@0*-*&j zwSxY$!hJ6E%}O03EmXp?zONjLth#wB-5qNjc8jLJ(sYADOHr%G=|!$kDz!A-DEHS6 zyTzustV2}W0Q7Zv}gzuasx_7@D-Cj=P%PE=zH;> zCs|#hyeU@T1`NymqEaC`KKI#y%8x5GG_?FD?!mviH;vW}=B1kRAgin655?t{yG8h@ zrWFd4Xw0v_(}oaQnwlmZmHaujxi`H~e?4cy<~X^o4nGpVpy4jb?mnmPOYQ=0@1uC1EHU4!Ks-$KSt&uRnW`Npgjj%rE2f4tq}(R9vWJwax6PKNN;!-l(YeWv_lz zK3Qou?y<+;TQ6p_lD#G4)@T||rI+2T^k;Hc)b?&E5Jlyah0GPgR_naqa4OvzJ>8y} z$r_5xwlwMXc$>^E8eXR*&MLQHF_0vM(6C1GULT1!+mx!=Xq{7=AivZ@1t(nsS${HEnzQ^%jqMjvY7NHT@efK`%r*%)i>NX1OjHJz+2 zD$6kGw}qcks64520pX`4*MO_0&v`mo`#O!T!J3XMx0I2j`XuQx$=ks>> z%Qd80qn9>W>UlO~SPRFUjMtb>)=1qbd98+OGkPu{lp5f*nBt)eydf#J%j_jm#l!KK zPS&t6BzeZDuf`+mz)t% zyGk>-4fGVW-=Mjh4zBa*>iQz38h`ahpqHZdwj>eX{Q7JLfDF9eT^z= zx1lhqs4d|PajZ6ai*P%h)AX#uL79vC-cXkgTH!zZTU|iVPKF(dhm81|rgy07cdo!? zScaoGAUr4YsFv@!$WzDVB77kovpF6XZo}*6J+9Ot>wt!*j+^%km`&fy`rloW=jE<6 z(f5|R)KW}KwXP<<*I<0n#kN~*wQ(yn<&sIsQR9wy(a{;RjfWyLZp?Tcq?|9R^dnkc z5_Pm;XFp_3E%MKKr9SKESF$n}8F#HucovCB$4N*<#F(jIgwY?Z;tMO5VVEV`_}YZr44 zMP+y{mbt@iavQO-k14Bcjx0bj7pO~$q8JI}px0-N@6V1v?Noo~^ux=tRo!V^`A-N{YG9G+GR>m}RLnB;F29h!Ny8^WaJaxMR(@Fl}( z!g_P6u$Y@@52u!)$b33IF8PE>dt{n5VXv0hdcLdhqjG2Zo|y z+*h@HTsUArL%YPI|A@q?MK!V#g#o$Je#d;=O8;ai<`oN@J6Y9UuJjobR{Ii$wCtA4 zH6(c_G*<{^{RujsQ-!H}w25hxQ*F?ahD@%Zi0yn1`Q_X}0488qEkghj$d?;4Rth3x(mEM3$aDn9Fn!rmX1O4A~8pf6OOAgD7 zD~+hOOi#)@skGny>1e0*_Q)6VcBDmk8Abi_9GtAG>a?RB_d@5TK?}g*z6676FS%2) zUg|19n93`kA!WL6w3B4JP000>X1^@s6#OZ}&000CqNklAg*|Z75MOnLwa??G!38Hq9l_SaM5a(XPpHgQGTDFNm zhciZ$two}A5kUmA2+A=_2+U16_nyD+eOsKlcAbV%^Id(v@ALjV@B4kg?Fq9i^JYc> zFf(RZ=7Dy8*OFzKteN|b_Sdy=qs=_eU7Dt$*Xy}QwxzF<+@%^G7V>9+Cx1F`pDZq> zVSMh6K(Z{8e!p)dZRY60Fs$%p_-89oj-r4n?nX%vRb1)d_ zEkSyD?iN?AdUrm58+3<#6m`mS)X+#HNP`(+Nz(C6Rh59wytgMkvG)GDAY-@p9r<)i zoOC~mJDnX>F@mZE5^k+QW(Xp5Nzz4G7GG55=#39%UjBV;_--UE%S_TV4Kv3Vo{Kx( z3vtrfQ4EK{RBb%~03hL1)kdRXh?CBaq_gS#fupCMP17_?5<0D=!|J-gPF=b0w52a}u1OR%yo&s3G#^JwxAwgngRWHQMNYV&g zcFutYyDR+$bIyUluOuzYGLe42?*Kq)zmNjs-GLZkq`+VoSC>S9?d?dhY%svqbOAFn zpaHP9H-&cHZZ^u=V3_ezyRaQHHFecm3v5|K065XU%^=>h+uiJ=IB~5GQvitK#C5xy zy@)3!ohPUv0MLs7Fg116WN~r487=gu($~LJntz9Xjv>v99{F?-QP5g0aPDFu4$U@5AWSM z4`6=w!_T+XO})i~R}Sq>zXgEV-u*J)01&sf;cGK%V#X#!E-iX6N#A zangCRDhdVLTA@e{%EvC~@xk;<<76V6KlNas-v{tDQ;k;hp>d}pRaty}a5~*S*6RUa zm0^t;E{M3N+D^V&G-h#<#IKKrE3eGXs8-i3*7Qb=3F zwi6F@|Iw3slJ4d?aUP}trV2AdOG|5sM1UmN4j>{BaR{M7RhD}W?M+XN_1y$=6VUq$ zPrJnJgxaHOb|1-mY@Jt>1~b^-BJwA|B{Ti(;B1y+$T2m{{}UqP(KoO?%%s72d?ef+4YC?Z0!w10#uN~Ivxm z)@tXR2k@lCkZm2g@nHMvE;*5DzVJx>Dj@E%NqMEqn*byL)U${HFhxk&q=dZLcwK_J zW9!D%FLjQwBt+7bD$68pc<6-{Ns@11#J6l>PxrYC5DY}13q&x9)!w=H7_iHpz_u)_ zoR}+_fkGfjDFjQrZ>)B)HTjFyB;Qs_PWR5wp9BC1w0Ay;$cm(#Y<6ehr}vH3F3z$n zrfI6k*~57uW)`$o0RZkD+k2}LRZWti^YUgR095GX3v&iZ3Ro%)Dc5Wq4CDjbhSxmN zsl&X8)M_CEFB2|&>?t|k)=|3UD--~Ah8|XRt zJsVq&cHblQ3Y2_DYx_y(nqEM&MCn-%5QKPZ6B9x>OdsyrI#PM2Ul8chs?~Ax&ZpOk z=*P9u)_WI4WKnaYK|wGYZB+=N!5G$$j@&&5pEiN@?)x8e%ad6r7q8dEF`opB1i?A4A!H zb^Gl%A8Y9hv^jh0{9x>9mm6&_buN#~g#-YIKzo-*ZR{7NVXpvA>J0RVTGa7crI(HV z(?9@9X$a(+&S%|3AfG!WS}h3i^PMeiM5+J)0na{F>dY4cIjgv9Y{L zSNy3?HFTR61 z8pQIc2vUSfD?sFbjOsXWQfHuzNXw4$Bkzwh@<_3RV%ZQ8fJ$kAJYr5v9Gh&L!b#Jb zbnu6F+BMVRw2B5wn4oB%M5NxP2}r)Ci%7DPBe9k=zOEtPb6 zXh;Jh1o|FHi)SL`p&@N-qRGMha7q`!p)AW{QIwEw(*l4>y{-U&qW?9QkY%iuJfHYm zW8#CC2*kveREr>iNFcTpPHdt`vN7?&OFnPDVvHSpC;~d-K)bFziCp;VtCOtbTh8S{ zATra85s2Ctc;}vr&2Yu&@S3ZYl53rJe*y?&6R|c?)+WkMD(t-bDT$Cy7nRhR3iX*3HxK@yGonF)cW$6yos`R`@J_GyKVPw``}%x ze|~&?;z}}H7rndRHS;~rmU6^Y1f_C90&0H5Nx8hLD8%(WDN;lU5sxxrr#;?$EX%%e8~|3=YI^l#r#gVu0P1z1UatfI zSpVpc&sY(YZ%UMVM5(jL$bLisgrro~t~pVE@a{F0mX7TmOy?V|C8Ltxg%DN|P&0U= zkdY$lY-9CkJX3#d^Ui1XSdka9O66$1tz+vx9jl$4gVz}9^-Al@0s>|s0g@_&fB^YD z0O>R<^m@52D8&XgbL1+rB}d4YNAF(!=k}sQ3tcNRrJ3ddcwB#kx*!M_%*ehcW$y4QJxBxT#3m3ILAT5BV5z*I#9S^#n0 zMd$L~mv@De4p^ZfK&6e4@i*J=`1;YQO?x&X08p(Kq!q9y&l|_Au?ieGoe(Mfs_%tC zj|hyx8msbVV_Z4^1BkGtTi2j{Wwd-{XYCek^lpMljKPD@{XVNL7Dy6;pp`Lz7mbDPMTpN3?l1U3$thFhEK0=I$!T%fNUn0Yn2C#1$gy9FXT=?#1o^;xL8p31ytbeh`0G zc4(hIHgO=!D*0mmr~p8*Q!kpZ0gqp1Gx72n#6;pQpyBYf!i YKUc|{U_eHR%m4rY07*qoM6N<$f{{}C^#A|> literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_home_unselected.imageset/tab_home_unselected@3x.png b/suixinkan/Assets.xcassets/tab_home_unselected.imageset/tab_home_unselected@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..95bc97d50cd96059600f6bd65d3fda7bf12f5123 GIT binary patch literal 3892 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 P00000NkvXXu0mjfp0itN literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_order_selected.imageset/tab_order_selected@2x.png b/suixinkan/Assets.xcassets/tab_order_selected.imageset/tab_order_selected@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9a189278b34a27b8848f88d56020d8b1e2d6d8ac GIT binary patch literal 1246 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 Y4`tfWUfeG#%>V!Z07*qoM6N<$f)|C1y8r+H literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_order_unselected.imageset/Contents.json b/suixinkan/Assets.xcassets/tab_order_unselected.imageset/Contents.json new file mode 100644 index 0000000..89b0894 --- /dev/null +++ b/suixinkan/Assets.xcassets/tab_order_unselected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "tab_order_unselected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tab_order_unselected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tab_order_unselected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/tab_order_unselected.imageset/tab_order_unselected.png b/suixinkan/Assets.xcassets/tab_order_unselected.imageset/tab_order_unselected.png new file mode 100644 index 0000000000000000000000000000000000000000..90a3d32da8fd571a0c41199e27bc2cc9458c581a GIT binary patch literal 1026 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 literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_profile_selected.imageset/Contents.json b/suixinkan/Assets.xcassets/tab_profile_selected.imageset/Contents.json new file mode 100644 index 0000000..33d4fa6 --- /dev/null +++ b/suixinkan/Assets.xcassets/tab_profile_selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "tab_profile_selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tab_profile_selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tab_profile_selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected.png b/suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f747733c25df4e9a070b0ed2b082a400716c3e GIT binary patch literal 710 zcmV;%0y+JOP)P000>X1^@s6#OZ}&0007wNklI67%~aG7?0`s!kV)x z)X3=JUw^1w@K*YE8E65dt^FnU3g7EHW8WCvQdQRUjs0!kdDrc=mNDx#zn&unDTJ47 zw1A{9$(^fj-`J+p`d%bO6L(beAv>z}-7_j7Em)UirNC1&ajPJaq!dIFP9cc}Wo_;! z$*Dtet05^2qiAdUtEAR!_dM0@^WSFTRzVI~+>~WWU^Y0~9JAVo%WRgn@K>@&d}PbB zZX;AJEJ+IY;I>cw?s)@o!WL#)+o8Vuy{Yhm!lv9}O1IQLu;sEH_sq(eA)6Yhc?TBX6PpV5O0r5=x2|%b>)UHTOit?KYPBT9 z!g;j}6m9$yUEc;W83*BmbAtE`N=G68| z_Te_+21&wGIHz>Rl0SJc{w1a8M=<&7OirYe3df@cpGzOXJ(7Leigm5`R5+*hJrBlr z*Vq^n6B0>5B!|=W3J+jxGJE!3zyV!>f2ETwJy}2F4_kbK+B4uJf>M4(h6eqJhmMsm&_A=m&PX8%yI> zwDBrOM~FyvxI^inMU_x$_F_UPEvl3bdfuImN4;t39%*cxClWeo#w9<|cDtTBuSRnu zwYJdnTMKS?GXB%AX3bWmx$_U@Uh$mU6yBlg7v9EpRuC!_Zno22uz!;rTL;aF?8!kF zZne#0SEJBXshk7z&CN6?ax5oe<*X%-YvpjpN77QLoVDneV7_@7X-*^}>7=4R z8+lY3O4V@?dAtk{dngukQqi;-_RWliobaDDr|mJb4&+&@StCm-vr1iOTy%$17IIiu zY1R@+NBCwuXVE>n&YDr88aciXBjq8!t<-haqK`Wjm-=(oc%%HJ=aIoEf1|cXw7gw~ zN-ZR5=y28VIkhCT6bkhG&XT*Fhzpa9@Ql4814L~;l}^M3&v>_f!W8Ew>5>)bMet;ooYf)ho%H!o0%E6?RFEsm3X@ zqc9nfl)|wg8%^Kvu(owfnuOSRL^`Qh_iWu)Q(mEEr{0KXOCekoo_2p+T6JykV#e5V z&4~=}?fcTJg`06e*oRjNvud-O_R>&-fh z_bAdO1BJL+6A0(kB$YsO`0CEJ>GzzjZli z<%v*c`J(6V_<{%G`)hn}fX$r_micI!lYAM66sS~rV4RK^o$@qLD%GfcN9m~hFK(eSZ>w_-&o3F=_{wy{+14qBrP9EZpc)!AD^wd^T^hzU8=&F?tIg;MnS0P7# zr?P%fgFf+juhMl!%Wg|5n~BrYN2R5-(eU8TS>y_lS!3{2N_d3a2vLJdj z@?w32$T8gFA6~O6#v!aFG8h6Yw`iHmQqQ#`BA+!&bHZC!#W;kuL>^xjsoyj;C2>;p zwXdY~W~{_GgtbKWEZ=lvo}sQik;te2l(*KNwL~^*h;#Y&IxDl+4w0*7m>YG=dPD{_ z`m`nQ($>Br!8JpXhttRM|7-fl=r`R-g=c+Q>7$YzS0=tKA}mNA^@MLb9gV*``M<)( ndwTj2eEk4C00000NkvXXu0mjfegWvD literal 0 HcmV?d00001 diff --git a/suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected@3x.png b/suixinkan/Assets.xcassets/tab_profile_selected.imageset/tab_profile_selected@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b2996b3d9b83cac5727ed0fc033b65dbae6a124d GIT binary patch literal 2264 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 Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/suixinkan/SceneDelegate.swift b/suixinkan/SceneDelegate.swift index 2f9609f..8b0a23b 100644 --- a/suixinkan/SceneDelegate.swift +++ b/suixinkan/SceneDelegate.swift @@ -2,8 +2,6 @@ // SceneDelegate.swift // suixinkan // -// Created by hanqiu on 2026/7/6. -// import UIKit @@ -11,42 +9,57 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { return } - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } + let window = UIWindow(windowScene: windowScene) + window.rootViewController = AppRouter.makeRootViewController() + window.makeKeyAndVisible() + self.window = window + + registerNotifications() } func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + NotificationCenter.default.removeObserver(self) } - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + private func registerNotifications() { + NotificationCenter.default.addObserver( + self, + selector: #selector(handleSessionDidExpire), + name: NotificationName.sessionDidExpire, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleUserDidLogout), + name: NotificationName.userDidLogout, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleUserDidLogin), + name: NotificationName.userDidLogin, + object: nil + ) } - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). + @objc private func handleSessionDidExpire() { + AppStore.shared.logout() + AppRouter.setRoot(.login, on: window) } - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. + @objc private func handleUserDidLogout() { + AppStore.shared.logout() + AppRouter.setRoot(.login, on: window) } - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. + @objc private func handleUserDidLogin() { + AppRouter.setRoot(.mainTab, on: window) } - - } - diff --git a/suixinkan/Theme/AppColor.swift b/suixinkan/Theme/AppColor.swift new file mode 100644 index 0000000..9e7de15 --- /dev/null +++ b/suixinkan/Theme/AppColor.swift @@ -0,0 +1,19 @@ +// +// AppColor.swift +// suixinkan +// + +import UIKit + +/// 应用主题色,与 Android `theme/Color.kt` 对齐。 +enum AppColor { + + static let primary = UIColor(red: 0 / 255, green: 115 / 255, blue: 255 / 255, alpha: 1) + static let text333 = UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + static let text666 = UIColor(red: 102 / 255, green: 102 / 255, blue: 102 / 255, alpha: 1) + static let agreementLink = UIColor(red: 32 / 255, green: 139 / 255, blue: 255 / 255, alpha: 1) + static let inputBackground = UIColor(red: 244 / 255, green: 244 / 255, blue: 244 / 255, alpha: 1) + static let secondaryButtonBackground = UIColor(red: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1) + static let buttonDisabled = UIColor(red: 192 / 255, green: 192 / 255, blue: 192 / 255, alpha: 1) + static let placeholder = UIColor.gray +} diff --git a/suixinkan/Theme/UIColor+Hex.swift b/suixinkan/Theme/UIColor+Hex.swift new file mode 100644 index 0000000..a0e26bd --- /dev/null +++ b/suixinkan/Theme/UIColor+Hex.swift @@ -0,0 +1,19 @@ +// +// UIColor+Hex.swift +// suixinkan +// + +import UIKit + +extension UIColor { + + /// 通过 0xRRGGBB 和透明度创建 UIColor。 + convenience init(hex: UInt, alpha: CGFloat = 1.0) { + self.init( + red: CGFloat((hex >> 16) & 0xff) / 255.0, + green: CGFloat((hex >> 8) & 0xff) / 255.0, + blue: CGFloat(hex & 0xff) / 255.0, + alpha: alpha + ) + } +} diff --git a/suixinkan/UI/Common/AgreementRowView.swift b/suixinkan/UI/Common/AgreementRowView.swift new file mode 100644 index 0000000..d8572d9 --- /dev/null +++ b/suixinkan/UI/Common/AgreementRowView.swift @@ -0,0 +1,114 @@ +// +// AgreementRowView.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 协议勾选行,对应 Android `AgreementRow`。 +final class AgreementRowView: UIView, UITextViewDelegate { + + private let checkButton = UIButton(type: .custom) + private let textView = UITextView() + + var isChecked = false { + didSet { updateCheckboxImage() } + } + + var onCheckedChange: (() -> Void)? + var onUserAgreementTap: (() -> Void)? + var onPrivacyPolicyTap: (() -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + checkButton.addTarget(self, action: #selector(checkButtonTapped), for: .touchUpInside) + updateCheckboxImage() + + textView.backgroundColor = .clear + textView.isEditable = false + textView.isScrollEnabled = false + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + textView.delegate = self + textView.linkTextAttributes = [ + .foregroundColor: AppColor.agreementLink, + .font: UIFont.systemFont(ofSize: 12, weight: .semibold), + ] + textView.attributedText = makeAgreementText() + + addSubview(checkButton) + addSubview(textView) + } + + private func setupConstraints() { + checkButton.snp.makeConstraints { make in + make.leading.top.equalToSuperview() + make.size.equalTo(18) + } + + textView.snp.makeConstraints { make in + make.leading.equalTo(checkButton.snp.trailing).offset(8) + make.trailing.top.bottom.equalToSuperview() + } + } + + private func updateCheckboxImage() { + let imageName = isChecked ? "icon_cb_privacy_selected" : "icon_cb_privacy_unselect" + checkButton.setImage(UIImage(named: imageName), for: .normal) + } + + private func makeAgreementText() -> NSAttributedString { + let result = NSMutableAttributedString() + let normalAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 12), + .foregroundColor: AppColor.text666, + ] + let linkFont = UIFont.systemFont(ofSize: 12, weight: .semibold) + + result.append(NSAttributedString(string: "已阅读并同意", attributes: normalAttributes)) + result.append(NSAttributedString(string: "《用户协议》", attributes: [ + .font: linkFont, + .foregroundColor: AppColor.agreementLink, + .link: "tos", + ])) + result.append(NSAttributedString(string: "与", attributes: normalAttributes)) + result.append(NSAttributedString(string: "《隐私政策》", attributes: [ + .font: linkFont, + .foregroundColor: AppColor.agreementLink, + .link: "privacy", + ])) + return result + } + + @objc private func checkButtonTapped() { + onCheckedChange?() + } + + func textView( + _ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction + ) -> Bool { + switch URL.absoluteString { + case "tos": + onUserAgreementTap?() + case "privacy": + onPrivacyPolicyTap?() + default: + break + } + return false + } +} diff --git a/suixinkan/UI/Common/AppButton.swift b/suixinkan/UI/Common/AppButton.swift new file mode 100644 index 0000000..f70af10 --- /dev/null +++ b/suixinkan/UI/Common/AppButton.swift @@ -0,0 +1,59 @@ +// +// AppButton.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 主按钮,对应 Android `AppButton`。 +final class AppButton: UIButton { + + enum Style { + case primary + case secondary + } + + private let style: Style + + init(title: String, style: Style = .primary) { + self.style = style + super.init(frame: .zero) + setupUI(title: title) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var isEnabled: Bool { + didSet { updateAppearance() } + } + + private func setupUI(title: String) { + titleLabel?.font = .systemFont(ofSize: 14, weight: .regular) + layer.cornerRadius = 10 + clipsToBounds = true + setTitle(title, for: .normal) + updateAppearance() + + snp.makeConstraints { make in + make.height.equalTo(46) + } + } + + private func updateAppearance() { + switch style { + case .primary: + backgroundColor = isEnabled ? AppColor.primary : AppColor.buttonDisabled + setTitleColor(.white, for: .normal) + setTitleColor(.white, for: .disabled) + case .secondary: + backgroundColor = AppColor.secondaryButtonBackground + setTitleColor(AppColor.primary, for: .normal) + setTitleColor(AppColor.primary, for: .disabled) + alpha = isEnabled ? 1 : 0.6 + } + } +} diff --git a/suixinkan/UI/Common/LoginTextField.swift b/suixinkan/UI/Common/LoginTextField.swift new file mode 100644 index 0000000..a291a22 --- /dev/null +++ b/suixinkan/UI/Common/LoginTextField.swift @@ -0,0 +1,83 @@ +// +// LoginTextField.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 登录页账号输入框,对应 Android `PhoneInputField`。 +final class LoginTextField: UIView { + + private let textField = UITextField() + + var text: String { + get { textField.text ?? "" } + set { textField.text = newValue } + } + + var onTextChange: ((String) -> Void)? + var onReturnKey: (() -> Void)? + + init(placeholder: String) { + super.init(frame: .zero) + setupUI(placeholder: placeholder) + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + textField.becomeFirstResponder() + } + + private func setupUI(placeholder: String) { + backgroundColor = AppColor.inputBackground + layer.cornerRadius = 12 + clipsToBounds = true + + textField.font = .systemFont(ofSize: 14) + textField.textColor = AppColor.text333 + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.returnKeyType = .next + textField.clearButtonMode = .whileEditing + textField.delegate = self + + let attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: AppColor.placeholder, + .font: UIFont.systemFont(ofSize: 14), + ] + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes) + + textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) + addSubview(textField) + } + + private func setupConstraints() { + snp.makeConstraints { make in + make.height.equalTo(46) + } + + textField.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.centerY.equalToSuperview() + } + } + + @objc private func textDidChange() { + onTextChange?(text) + } +} + +extension LoginTextField: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + onReturnKey?() + return true + } +} diff --git a/suixinkan/UI/Common/PasswordInputField.swift b/suixinkan/UI/Common/PasswordInputField.swift new file mode 100644 index 0000000..bd402d6 --- /dev/null +++ b/suixinkan/UI/Common/PasswordInputField.swift @@ -0,0 +1,104 @@ +// +// PasswordInputField.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 登录页密码输入框,对应 Android `PasswordInputField`。 +final class PasswordInputField: UIView { + + private let textField = UITextField() + private let toggleButton = UIButton(type: .custom) + + private var isPasswordVisible = false + + var text: String { + get { textField.text ?? "" } + set { textField.text = newValue } + } + + var onTextChange: ((String) -> Void)? + var onReturnKey: (() -> Void)? + + init(placeholder: String = "请输入密码") { + super.init(frame: .zero) + setupUI(placeholder: placeholder) + setupConstraints() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + textField.becomeFirstResponder() + } + + private func setupUI(placeholder: String) { + backgroundColor = AppColor.inputBackground + layer.cornerRadius = 12 + clipsToBounds = true + + textField.font = .systemFont(ofSize: 14) + textField.textColor = AppColor.text333 + textField.isSecureTextEntry = true + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.returnKeyType = .done + textField.delegate = self + + let attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: AppColor.placeholder, + .font: UIFont.systemFont(ofSize: 14), + ] + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes) + textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) + + toggleButton.setImage(UIImage(named: "icon_pwd_invisible"), for: .normal) + toggleButton.addTarget(self, action: #selector(togglePasswordVisibility), for: .touchUpInside) + + addSubview(textField) + addSubview(toggleButton) + } + + private func setupConstraints() { + snp.makeConstraints { make in + make.height.equalTo(46) + } + + toggleButton.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(8) + make.centerY.equalToSuperview() + make.size.equalTo(30) + } + + textField.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(16) + make.trailing.equalTo(toggleButton.snp.leading).offset(-4) + make.centerY.equalToSuperview() + } + } + + @objc private func textDidChange() { + onTextChange?(text) + } + + @objc private func togglePasswordVisibility() { + isPasswordVisible.toggle() + textField.isSecureTextEntry = !isPasswordVisible + let imageName = isPasswordVisible ? "icon_pwd_visible" : "icon_pwd_invisible" + toggleButton.setImage(UIImage(named: imageName), for: .normal) + } +} + +extension PasswordInputField: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + onReturnKey?() + return true + } +} diff --git a/suixinkan/UI/Home/HomeViewController.swift b/suixinkan/UI/Home/HomeViewController.swift new file mode 100644 index 0000000..5f3929f --- /dev/null +++ b/suixinkan/UI/Home/HomeViewController.swift @@ -0,0 +1,32 @@ +// +// HomeViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 首页 Tab 根页面。 +final class HomeViewController: BaseViewController { + + private let titleLabel = UILabel() + + override func setupNavigationBar() { + navigationController?.setNavigationBarHidden(true, animated: false) + } + + override func setupUI() { + view.backgroundColor = UIColor(hex: 0xF7FAFF) + titleLabel.text = "首页" + titleLabel.font = .systemFont(ofSize: 24, weight: .medium) + titleLabel.textAlignment = .center + titleLabel.textColor = AppColor.text333 + view.addSubview(titleLabel) + } + + override func setupConstraints() { + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } +} diff --git a/suixinkan/UI/Login/LoginViewController.swift b/suixinkan/UI/Login/LoginViewController.swift new file mode 100644 index 0000000..0f33604 --- /dev/null +++ b/suixinkan/UI/Login/LoginViewController.swift @@ -0,0 +1,224 @@ +// +// LoginViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 登录页,UI 严格对齐 Android `LoginScreen`。 +final class LoginViewController: BaseViewController { + + private let viewModel = LoginViewModel() + + private let backgroundImageView = UIImageView() + private let welcomeLabel = UILabel() + private let formCardView = UIView() + private let topFormStack = UIStackView() + private let accountField = LoginTextField(placeholder: "请输入账号") + private let passwordField = PasswordInputField() + private let agreementRow = AgreementRowView() + private let loginButton = AppButton(title: "登录", style: .primary) + private let loginByCodeButton = AppButton(title: "验证码登录", style: .secondary) + private let registerContainer = UIStackView() + private let registerPrefixLabel = UILabel() + private let registerButton = UIButton(type: .system) + + private var hasPlayedEntranceAnimation = false + + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } + + override func setupNavigationBar() { + navigationController?.setNavigationBarHidden(true, animated: false) + } + + override func setupUI() { + view.backgroundColor = .black + + backgroundImageView.image = UIImage(named: "img_login_bg") + backgroundImageView.contentMode = .scaleAspectFill + backgroundImageView.clipsToBounds = true + + welcomeLabel.text = "欢迎使用\n随心瞰商家版" + welcomeLabel.numberOfLines = 2 + welcomeLabel.font = .systemFont(ofSize: 28, weight: .semibold) + welcomeLabel.textColor = .white + + formCardView.backgroundColor = .white + formCardView.layer.cornerRadius = 16 + formCardView.clipsToBounds = true + + topFormStack.axis = .vertical + topFormStack.spacing = 0 + topFormStack.alignment = .fill + + loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside) + loginByCodeButton.addTarget(self, action: #selector(loginByCodeButtonTapped), for: .touchUpInside) + + registerContainer.axis = .horizontal + registerContainer.alignment = .center + registerContainer.spacing = 0 + + registerPrefixLabel.text = "没有账号?" + registerPrefixLabel.font = .systemFont(ofSize: 14, weight: .medium) + registerPrefixLabel.textColor = AppColor.text333 + + registerButton.setTitle("去注册", for: .normal) + registerButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium) + registerButton.setTitleColor(AppColor.primary, for: .normal) + registerButton.addTarget(self, action: #selector(registerButtonTapped), for: .touchUpInside) + + registerContainer.addArrangedSubview(registerPrefixLabel) + registerContainer.addArrangedSubview(registerButton) + + view.addSubview(backgroundImageView) + view.addSubview(welcomeLabel) + view.addSubview(formCardView) + formCardView.addSubview(topFormStack) + formCardView.addSubview(registerContainer) + + topFormStack.addArrangedSubview(accountField) + topFormStack.setCustomSpacing(8, after: accountField) + topFormStack.addArrangedSubview(passwordField) + topFormStack.setCustomSpacing(24, after: passwordField) + topFormStack.addArrangedSubview(agreementRow) + topFormStack.setCustomSpacing(16, after: agreementRow) + topFormStack.addArrangedSubview(loginButton) + topFormStack.setCustomSpacing(8, after: loginButton) + topFormStack.addArrangedSubview(loginByCodeButton) + + bindViewModel() + updateUI() + } + + override func setupConstraints() { + backgroundImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + welcomeLabel.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(48) + make.leading.trailing.equalToSuperview().inset(24) + } + + formCardView.snp.makeConstraints { make in + make.top.equalTo(welcomeLabel.snp.bottom).offset(48) + make.leading.trailing.equalToSuperview().inset(24) + make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24) + } + + topFormStack.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview().inset(16) + make.bottom.equalToSuperview().inset(16) + } + + registerContainer.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().inset(16) + } + } + + override func bindActions() { + accountField.onTextChange = { [weak self] text in + self?.viewModel.updateAccount(text) + } + accountField.onReturnKey = { [weak self] in + self?.passwordField.becomeFirstResponder() + } + + passwordField.onTextChange = { [weak self] text in + self?.viewModel.updatePassword(text) + } + passwordField.onReturnKey = { [weak self] in + self?.view.endEditing(true) + self?.viewModel.login() + } + + agreementRow.onCheckedChange = { [weak self] in + self?.viewModel.togglePrivacyChecked() + } + agreementRow.onUserAgreementTap = { [weak self] in + self?.viewModel.onUserAgreement?() + } + agreementRow.onPrivacyPolicyTap = { [weak self] in + self?.viewModel.onPrivacyPolicy?() + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + playEntranceAnimationIfNeeded() + } + + private func bindViewModel() { + viewModel.onStateChange = { [weak self] in + self?.updateUI() + } + viewModel.onShowMessage = { [weak self] message in + self?.showToast(message) + } + viewModel.onLogin = { [weak self] in + self?.showToast("登录功能待接入") + } + viewModel.onLoginByCode = { [weak self] in + self?.showToast("验证码登录待接入") + } + viewModel.onRegister = { [weak self] in + self?.showToast("注册待接入") + } + viewModel.onUserAgreement = { [weak self] in + self?.showToast("用户协议待接入") + } + viewModel.onPrivacyPolicy = { [weak self] in + self?.showToast("隐私政策待接入") + } + } + + private func updateUI() { + agreementRow.isChecked = viewModel.isPrivacyChecked + loginButton.isEnabled = viewModel.isLoginEnabled + + let showRegisterSection = viewModel.enableRegister + loginByCodeButton.isHidden = !showRegisterSection + registerContainer.isHidden = !showRegisterSection + + topFormStack.snp.remakeConstraints { make in + make.top.leading.trailing.equalToSuperview().inset(16) + if showRegisterSection { + make.bottom.lessThanOrEqualTo(registerContainer.snp.top).offset(-16) + } else { + make.bottom.equalToSuperview().inset(16) + } + } + } + + private func playEntranceAnimationIfNeeded() { + guard !hasPlayedEntranceAnimation else { return } + hasPlayedEntranceAnimation = true + + formCardView.transform = CGAffineTransform(translationX: 0, y: view.bounds.height) + welcomeLabel.alpha = 0 + + UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) { + self.formCardView.transform = .identity + self.welcomeLabel.alpha = 1 + } + } + + @objc private func loginButtonTapped() { + view.endEditing(true) + viewModel.login() + } + + @objc private func loginByCodeButtonTapped() { + view.endEditing(true) + viewModel.onLoginByCode?() + } + + @objc private func registerButtonTapped() { + view.endEditing(true) + viewModel.onRegister?() + } +} diff --git a/suixinkan/UI/Login/LoginViewModel.swift b/suixinkan/UI/Login/LoginViewModel.swift new file mode 100644 index 0000000..520cbed --- /dev/null +++ b/suixinkan/UI/Login/LoginViewModel.swift @@ -0,0 +1,61 @@ +// +// LoginViewModel.swift +// suixinkan +// + +import Foundation + +/// 登录页 ViewModel,UI 状态与 Android `LoginViewModel` 对齐。 +final class LoginViewModel { + + private(set) var account = "" + private(set) var password = "" + private(set) var isPrivacyChecked = false + private(set) var enableRegister = false + + var isLoginEnabled: Bool { + !account.isEmpty && !password.isEmpty + } + + var onStateChange: (() -> Void)? + + func updateAccount(_ value: String) { + account = value + notifyStateChange() + } + + func updatePassword(_ value: String) { + password = value + notifyStateChange() + } + + func togglePrivacyChecked() { + isPrivacyChecked.toggle() + notifyStateChange() + } + + func updateEnableRegister(_ enabled: Bool) { + enableRegister = enabled + notifyStateChange() + } + + func login() { + guard isPrivacyChecked else { + onShowMessage?("请先阅读并同意用户协议与隐私政策") + return + } + guard isLoginEnabled else { return } + onLogin?() + } + + var onShowMessage: ((String) -> Void)? + var onLogin: (() -> Void)? + var onLoginByCode: (() -> Void)? + var onRegister: (() -> Void)? + var onUserAgreement: (() -> Void)? + var onPrivacyPolicy: (() -> Void)? + + private func notifyStateChange() { + onStateChange?() + } +} diff --git a/suixinkan/UI/MainTab/AppTab.swift b/suixinkan/UI/MainTab/AppTab.swift new file mode 100644 index 0000000..e60c9e6 --- /dev/null +++ b/suixinkan/UI/MainTab/AppTab.swift @@ -0,0 +1,64 @@ +// +// AppTab.swift +// suixinkan +// + +import UIKit + +/// 主 Tab 实体,描述底部导航中的一级入口。 +enum AppTab: String, CaseIterable, Hashable { + case home + case orders + case statistics + case profile + + var title: String { + switch self { + case .home: "首页" + case .orders: "订单" + case .statistics: "数据" + case .profile: "我的" + } + } + + var selectedImageName: String { + switch self { + case .home: "tab_home_selected" + case .orders: "tab_order_selected" + case .statistics: "tab_data_selected" + case .profile: "tab_profile_selected" + } + } + + var unselectedImageName: String { + switch self { + case .home: "tab_home_unselected" + case .orders: "tab_order_unselected" + case .statistics: "tab_data_unselected" + case .profile: "tab_profile_unselected" + } + } + + /// 构建系统 TabBarItem。 + func makeTabBarItem() -> UITabBarItem { + let unselectedImage = UIImage(named: unselectedImageName)?.withRenderingMode(.alwaysOriginal) + let selectedImage = UIImage(named: selectedImageName)?.withRenderingMode(.alwaysOriginal) + let item = UITabBarItem(title: title, image: unselectedImage, selectedImage: selectedImage) + item.accessibilityIdentifier = "main.tab.\(rawValue)" + return item + } + + /// 构建 Tab 根页面。 + func makeRootViewController() -> UIViewController { + switch self { + case .home: + return HomeViewController() + case .orders: + return OrdersViewController() + case .statistics: + return StatisticsViewController() + case .profile: + return ProfileViewController() + } + } +} diff --git a/suixinkan/UI/MainTab/MainScanTabItem.swift b/suixinkan/UI/MainTab/MainScanTabItem.swift new file mode 100644 index 0000000..ebf7792 --- /dev/null +++ b/suixinkan/UI/MainTab/MainScanTabItem.swift @@ -0,0 +1,19 @@ +// +// MainScanTabItem.swift +// suixinkan +// + +import UIKit + +/// 主 TabBar 中间扫码核销入口。 +enum MainScanTabItem { + + /// 构建扫码 TabBarItem,点击行为由 `MainTabBarController` 拦截。 + static func makeTabBarItem() -> UITabBarItem { + let image = UIImage(named: "icon_scan")?.withRenderingMode(.alwaysOriginal) + let item = UITabBarItem(title: nil, image: image, selectedImage: image) + item.accessibilityLabel = "扫码核销" + item.accessibilityIdentifier = "main.scan" + return item + } +} diff --git a/suixinkan/UI/MainTab/MainTabBadgeViewModel.swift b/suixinkan/UI/MainTab/MainTabBadgeViewModel.swift new file mode 100644 index 0000000..5597684 --- /dev/null +++ b/suixinkan/UI/MainTab/MainTabBadgeViewModel.swift @@ -0,0 +1,21 @@ +// +// MainTabBadgeViewModel.swift +// suixinkan +// + +import Foundation + +/// 主 Tab 角标 ViewModel,负责订单 Tab 待核销数量。 +final class MainTabBadgeViewModel { + + var onChange: (() -> Void)? + + private(set) var pendingWriteOffCount: Int? { + didSet { onChange?() } + } + + /// 刷新待核销数量;接口未接入前保持为空。 + func refreshPendingWriteOffCount() { + pendingWriteOffCount = nil + } +} diff --git a/suixinkan/UI/MainTab/MainTabBarController.swift b/suixinkan/UI/MainTab/MainTabBarController.swift new file mode 100644 index 0000000..a2309f7 --- /dev/null +++ b/suixinkan/UI/MainTab/MainTabBarController.swift @@ -0,0 +1,154 @@ +// +// MainTabBarController.swift +// suixinkan +// + +import UIKit + +/// 主 Tab 容器,展示四个一级入口及中间扫码按钮。 +final class MainTabBarController: UITabBarController { + + private let badgeViewModel = MainTabBadgeViewModel() + private var tabNavigationControllers: [AppTab: TabNavigationController] = [:] + private var isSyncingTabSelection = false + + /// 测试时可注入扫码点击处理。 + var onScanTabSelected: (() -> Void)? + + private enum TabSlot: CaseIterable { + case home + case orders + case scan + case statistics + case profile + + var appTab: AppTab? { + switch self { + case .home: return .home + case .orders: return .orders + case .scan: return nil + case .statistics: return .statistics + case .profile: return .profile + } + } + + static func index(for tab: AppTab) -> Int? { + allCases.firstIndex { $0.appTab == tab } + } + + static var scanIndex: Int? { + allCases.firstIndex(of: .scan) + } + } + + override func viewDidLoad() { + super.viewDidLoad() + delegate = self + configureTabBarAppearance() + configureTabs() + bindBadgeViewModel() + badgeViewModel.refreshPendingWriteOffCount() + } + + /// 选中指定主 Tab。 + func selectTab(_ tab: AppTab) { + guard let index = TabSlot.index(for: tab) else { return } + guard selectedIndex != index else { return } + isSyncingTabSelection = true + selectedIndex = index + isSyncingTabSelection = false + if tab == .orders { + badgeViewModel.refreshPendingWriteOffCount() + } + } + + private func configureTabBarAppearance() { + tabBar.tintColor = AppColor.primary + tabBar.backgroundColor = .white + tabBar.shadowImage = UIImage() + + let normalAttributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: UIColor(hex: 0x7D8DA3), + .font: UIFont.systemFont(ofSize: 12, weight: .regular), + ] + let selectedAttributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: AppColor.primary, + .font: UIFont.systemFont(ofSize: 12, weight: .medium), + ] + + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = .white + appearance.shadowColor = .clear + appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes + appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes + tabBar.standardAppearance = appearance + tabBar.scrollEdgeAppearance = appearance + } + + private func configureTabs() { + tabNavigationControllers.removeAll() + viewControllers = TabSlot.allCases.map { slot in + if let tab = slot.appTab { + let navigationController = TabNavigationController(tab: tab) + navigationController.tabBarItem = tab.makeTabBarItem() + tabNavigationControllers[tab] = navigationController + return navigationController + } + + let scannerPlaceholder = UIViewController() + scannerPlaceholder.tabBarItem = MainScanTabItem.makeTabBarItem() + return scannerPlaceholder + } + } + + private func bindBadgeViewModel() { + badgeViewModel.onChange = { [weak self] in + self?.updateOrderBadge() + } + } + + private func updateOrderBadge() { + let badgeValue: String? + if let count = badgeViewModel.pendingWriteOffCount, count > 0 { + badgeValue = "\(count)" + } else { + badgeValue = nil + } + tabNavigationControllers[.orders]?.tabBarItem.badgeValue = badgeValue + } + + private func presentGlobalScanner() { + guard presentedViewController == nil else { return } + + let scanner = ScanPlaceholderViewController() + let navigationController = UINavigationController(rootViewController: scanner) + navigationController.modalPresentationStyle = .fullScreen + present(navigationController, animated: true) + } +} + +extension MainTabBarController: UITabBarControllerDelegate { + + func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { + guard viewControllers?.firstIndex(of: viewController) == TabSlot.scanIndex else { + return true + } + + if let onScanTabSelected { + onScanTabSelected() + } else { + presentGlobalScanner() + } + return false + } + + func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { + guard !isSyncingTabSelection, + let navigationController = viewController as? TabNavigationController else { return } + + if navigationController.appTab == .orders { + badgeViewModel.refreshPendingWriteOffCount() + } + } +} diff --git a/suixinkan/UI/MainTab/TabNavigationController.swift b/suixinkan/UI/MainTab/TabNavigationController.swift new file mode 100644 index 0000000..63e946a --- /dev/null +++ b/suixinkan/UI/MainTab/TabNavigationController.swift @@ -0,0 +1,73 @@ +// +// TabNavigationController.swift +// suixinkan +// + +import UIKit + +/// 单个 Tab 的导航栈。 +final class TabNavigationController: UINavigationController { + + let appTab: AppTab + + init(tab: AppTab) { + self.appTab = tab + super.init(nibName: nil, bundle: nil) + delegate = self + navigationBar.prefersLargeTitles = false + setViewControllers([tab.makeRootViewController()], animated: false) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func pushViewController(_ viewController: UIViewController, animated: Bool) { + viewController.hidesBottomBarWhenPushed = viewControllers.count > 0 + updateNavigationBarVisibility(for: viewController, animated: animated) + super.pushViewController(viewController, animated: animated) + } + + override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + if let topViewController = viewControllers.last { + updateNavigationBarVisibility(for: topViewController, animated: animated) + } + super.setViewControllers(viewControllers, animated: animated) + } + + override func popViewController(animated: Bool) -> UIViewController? { + let poppedViewController = super.popViewController(animated: animated) + if !animated, let topViewController { + updateNavigationBarVisibility(for: topViewController, animated: false) + } + return poppedViewController + } + + override func popToRootViewController(animated: Bool) -> [UIViewController]? { + let poppedViewControllers = super.popToRootViewController(animated: animated) + if !animated, let topViewController { + updateNavigationBarVisibility(for: topViewController, animated: false) + } + return poppedViewControllers + } + + private func shouldHideNavigationBar(for viewController: UIViewController) -> Bool { + viewController is HomeViewController + } + + private func updateNavigationBarVisibility(for viewController: UIViewController, animated: Bool) { + setNavigationBarHidden(shouldHideNavigationBar(for: viewController), animated: animated) + } +} + +extension TabNavigationController: UINavigationControllerDelegate { + + func navigationController( + _ navigationController: UINavigationController, + willShow viewController: UIViewController, + animated: Bool + ) { + updateNavigationBarVisibility(for: viewController, animated: animated) + } +} diff --git a/suixinkan/UI/Orders/OrdersViewController.swift b/suixinkan/UI/Orders/OrdersViewController.swift new file mode 100644 index 0000000..84e48cc --- /dev/null +++ b/suixinkan/UI/Orders/OrdersViewController.swift @@ -0,0 +1,28 @@ +// +// OrdersViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 订单 Tab 根页面。 +final class OrdersViewController: BaseViewController { + + private let titleLabel = UILabel() + + override func setupUI() { + title = "订单" + titleLabel.text = "订单" + titleLabel.font = .systemFont(ofSize: 24, weight: .medium) + titleLabel.textAlignment = .center + titleLabel.textColor = AppColor.text333 + view.addSubview(titleLabel) + } + + override func setupConstraints() { + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } +} diff --git a/suixinkan/UI/Profile/ProfileViewController.swift b/suixinkan/UI/Profile/ProfileViewController.swift new file mode 100644 index 0000000..dcead50 --- /dev/null +++ b/suixinkan/UI/Profile/ProfileViewController.swift @@ -0,0 +1,28 @@ +// +// ProfileViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 我的 Tab 根页面。 +final class ProfileViewController: BaseViewController { + + private let titleLabel = UILabel() + + override func setupUI() { + title = "我的" + titleLabel.text = "我的" + titleLabel.font = .systemFont(ofSize: 24, weight: .medium) + titleLabel.textAlignment = .center + titleLabel.textColor = AppColor.text333 + view.addSubview(titleLabel) + } + + override func setupConstraints() { + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } +} diff --git a/suixinkan/UI/Scan/ScanPlaceholderViewController.swift b/suixinkan/UI/Scan/ScanPlaceholderViewController.swift new file mode 100644 index 0000000..5f9fef2 --- /dev/null +++ b/suixinkan/UI/Scan/ScanPlaceholderViewController.swift @@ -0,0 +1,50 @@ +// +// ScanPlaceholderViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 扫码核销占位页,后续替换为真实扫码页。 +final class ScanPlaceholderViewController: BaseViewController { + + private let titleLabel = UILabel() + private let closeButton = UIButton(type: .system) + + override func setupNavigationBar() { + navigationController?.setNavigationBarHidden(true, animated: false) + } + + override func setupUI() { + view.backgroundColor = .black + + titleLabel.text = "扫码核销" + titleLabel.font = .systemFont(ofSize: 20, weight: .medium) + titleLabel.textColor = .white + titleLabel.textAlignment = .center + + closeButton.setTitle("关闭", for: .normal) + closeButton.setTitleColor(.white, for: .normal) + closeButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside) + + view.addSubview(titleLabel) + view.addSubview(closeButton) + } + + override func setupConstraints() { + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + closeButton.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(12) + make.trailing.equalToSuperview().inset(16) + } + } + + @objc private func closeTapped() { + dismiss(animated: true) + } +} diff --git a/suixinkan/UI/Statistics/StatisticsViewController.swift b/suixinkan/UI/Statistics/StatisticsViewController.swift new file mode 100644 index 0000000..fcf9d49 --- /dev/null +++ b/suixinkan/UI/Statistics/StatisticsViewController.swift @@ -0,0 +1,28 @@ +// +// StatisticsViewController.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 数据 Tab 根页面。 +final class StatisticsViewController: BaseViewController { + + private let titleLabel = UILabel() + + override func setupUI() { + title = "数据" + titleLabel.text = "数据" + titleLabel.font = .systemFont(ofSize: 24, weight: .medium) + titleLabel.textAlignment = .center + titleLabel.textColor = AppColor.text333 + view.addSubview(titleLabel) + } + + override func setupConstraints() { + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } +} diff --git a/suixinkan/ViewController.swift b/suixinkan/ViewController.swift deleted file mode 100644 index 7f6fc1e..0000000 --- a/suixinkan/ViewController.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ViewController.swift -// suixinkan -// -// Created by hanqiu on 2026/7/6. -// - -import UIKit - -class ViewController: BaseViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } - -} -