refactor: 移除 Core Data 相关代码并添加新的消息列表视图控制器
主要变更: 1. 从 AppDelegate 中移除 Core Data 相关的属性和方法,简化应用结构。 2. 新增 EPBaseListViewController 作为消息列表的基础类,提供通用的表视图功能。 3. 添加 EPMessageListVC、EPFriendListVC、EPFollowingListVC 和 EPFansListVC,分别用于展示消息、朋友、关注和粉丝列表。 4. 引入 EPMessageSegmentView 以支持消息主界面的分段控制。 此更新旨在提升代码的可维护性,简化数据管理,并增强用户界面的功能性和交互性。
This commit is contained in:
@@ -442,6 +442,11 @@
|
||||
4C520D882D89A78C0051C784 /* VisitorListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C520D872D89A78C0051C784 /* VisitorListViewController.m */; };
|
||||
4C5527BC2D1BDCDE00833FFD /* RoomLevelInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5527BB2D1BDCDE00833FFD /* RoomLevelInfoModel.m */; };
|
||||
4C5527BF2D1C099500833FFD /* RoomResourceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5527BE2D1C099500833FFD /* RoomResourceManager.m */; };
|
||||
4C59C0FD2EA2508F00D1F7BD /* EPFriendFollowingFans.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59C0F82EA2508F00D1F7BD /* EPFriendFollowingFans.swift */; };
|
||||
4C59C0FE2EA2508F00D1F7BD /* EPMessageSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59C0FB2EA2508F00D1F7BD /* EPMessageSegmentView.swift */; };
|
||||
4C59C0FF2EA2508F00D1F7BD /* EPMessageMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59C0FA2EA2508F00D1F7BD /* EPMessageMainViewController.swift */; };
|
||||
4C59C1002EA2508F00D1F7BD /* EPBaseListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59C0F72EA2508F00D1F7BD /* EPBaseListViewController.swift */; };
|
||||
4C59C1012EA2508F00D1F7BD /* EPMessageListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C59C0F92EA2508F00D1F7BD /* EPMessageListVC.swift */; };
|
||||
4C5C37232D0C1C7900BA9AB8 /* RegionListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C37222D0C1C7900BA9AB8 /* RegionListViewController.m */; };
|
||||
4C6C92C02D1172D9000A4693 /* RegionListInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C6C92BF2D1172D9000A4693 /* RegionListInfo.m */; };
|
||||
4C6E1F752CEAEC3C0073D0A3 /* ShoppingMallTagView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C6E1F742CEAEC3C0073D0A3 /* ShoppingMallTagView.m */; };
|
||||
@@ -884,7 +889,7 @@
|
||||
54FFD3832C9BD12600DE61E5 /* 5.svga in Resources */ = {isa = PBXBuildFile; fileRef = 54FFD37F2C9BD12600DE61E5 /* 5.svga */; };
|
||||
54FFD3842C9BD12600DE61E5 /* 3.svga in Resources */ = {isa = PBXBuildFile; fileRef = 54FFD37D2C9BD12600DE61E5 /* 3.svga */; };
|
||||
54FFD3852C9BD12600DE61E5 /* 2.svga in Resources */ = {isa = PBXBuildFile; fileRef = 54FFD37C2C9BD12600DE61E5 /* 2.svga */; };
|
||||
73FFADDC93E195344047A2EC /* Pods_YuMi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CACF623970097D653132D69A /* Pods_YuMi.framework */; };
|
||||
6996748A711C8E6931880AD0 /* Pods_YuMi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3148BBD005110C1B57D04595 /* Pods_YuMi.framework */; };
|
||||
9B0086C627BA392B0032BD2B /* AnchorStageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B0086C527BA392B0032BD2B /* AnchorStageView.m */; };
|
||||
9B0086CA27BA4F570032BD2B /* AnchorMicroView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B0086C927BA4F570032BD2B /* AnchorMicroView.m */; };
|
||||
9B044DA0282D32F700DE4859 /* MicroInviteExtModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B044D9F282D32F700DE4859 /* MicroInviteExtModel.m */; };
|
||||
@@ -2431,6 +2436,7 @@
|
||||
23FF42782AA6E19C0055733C /* HomeMenuSourceModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeMenuSourceModel.m; sourceTree = "<group>"; };
|
||||
23FF428C2AAB2D3A0055733C /* XPCandyTreeBuyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPCandyTreeBuyView.h; sourceTree = "<group>"; };
|
||||
23FF428D2AAB2D3A0055733C /* XPCandyTreeBuyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPCandyTreeBuyView.m; sourceTree = "<group>"; };
|
||||
3148BBD005110C1B57D04595 /* Pods_YuMi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YuMi.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4C0642722E97BD6D00BAF413 /* EPMineHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMineHeaderView.h; sourceTree = "<group>"; };
|
||||
4C0642732E97BD6D00BAF413 /* EPMineHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMineHeaderView.m; sourceTree = "<group>"; };
|
||||
4C0642792E97BD6D00BAF413 /* EPMomentCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentCell.h; sourceTree = "<group>"; };
|
||||
@@ -2527,6 +2533,11 @@
|
||||
4C5527BB2D1BDCDE00833FFD /* RoomLevelInfoModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomLevelInfoModel.m; sourceTree = "<group>"; };
|
||||
4C5527BD2D1C099500833FFD /* RoomResourceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomResourceManager.h; sourceTree = "<group>"; };
|
||||
4C5527BE2D1C099500833FFD /* RoomResourceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomResourceManager.m; sourceTree = "<group>"; };
|
||||
4C59C0F72EA2508F00D1F7BD /* EPBaseListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPBaseListViewController.swift; sourceTree = "<group>"; };
|
||||
4C59C0F82EA2508F00D1F7BD /* EPFriendFollowingFans.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPFriendFollowingFans.swift; sourceTree = "<group>"; };
|
||||
4C59C0F92EA2508F00D1F7BD /* EPMessageListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPMessageListVC.swift; sourceTree = "<group>"; };
|
||||
4C59C0FA2EA2508F00D1F7BD /* EPMessageMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPMessageMainViewController.swift; sourceTree = "<group>"; };
|
||||
4C59C0FB2EA2508F00D1F7BD /* EPMessageSegmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPMessageSegmentView.swift; sourceTree = "<group>"; };
|
||||
4C5C37212D0C1C7900BA9AB8 /* RegionListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegionListViewController.h; sourceTree = "<group>"; };
|
||||
4C5C37222D0C1C7900BA9AB8 /* RegionListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RegionListViewController.m; sourceTree = "<group>"; };
|
||||
4C6C92BE2D1172D9000A4693 /* RegionListInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegionListInfo.h; sourceTree = "<group>"; };
|
||||
@@ -2982,8 +2993,8 @@
|
||||
4CD19EAD2E9CDFC30069DAA0 /* EPLoginInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPLoginInputView.swift; sourceTree = "<group>"; };
|
||||
4CD19EB02E9D12600069DAA0 /* EPEditSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPEditSettingViewController.swift; sourceTree = "<group>"; };
|
||||
4CD19EB22E9D141A0069DAA0 /* EPMineViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMineViewController.h; sourceTree = "<group>"; };
|
||||
4CD19EB62E9D15000069DAA0 /* EPAboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPAboutUsViewController.swift; sourceTree = "<group>"; };
|
||||
4CD19EB32E9D141A0069DAA0 /* EPMineViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMineViewController.m; sourceTree = "<group>"; };
|
||||
4CD19EB62E9D15000069DAA0 /* EPAboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPAboutUsViewController.swift; sourceTree = "<group>"; };
|
||||
4CD401452E7183A8003F5009 /* XPPartyRoomItemCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPPartyRoomItemCollectionViewCell.h; sourceTree = "<group>"; };
|
||||
4CD401462E7183A8003F5009 /* XPPartyRoomItemCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPPartyRoomItemCollectionViewCell.m; sourceTree = "<group>"; };
|
||||
4CD401482E718E36003F5009 /* XPBlankRoomModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPBlankRoomModel.h; sourceTree = "<group>"; };
|
||||
@@ -3209,7 +3220,7 @@
|
||||
54FFD37D2C9BD12600DE61E5 /* 3.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = 3.svga; sourceTree = "<group>"; };
|
||||
54FFD37E2C9BD12600DE61E5 /* 4.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = 4.svga; sourceTree = "<group>"; };
|
||||
54FFD37F2C9BD12600DE61E5 /* 5.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = 5.svga; sourceTree = "<group>"; };
|
||||
7DB00EC07F1D0ADFF900B38D /* Pods-YuMi.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YuMi.debug.xcconfig"; path = "Target Support Files/Pods-YuMi/Pods-YuMi.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
56B26C27109C1DD2002D0624 /* Pods-YuMi.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YuMi.release.xcconfig"; path = "Target Support Files/Pods-YuMi/Pods-YuMi.release.xcconfig"; sourceTree = "<group>"; };
|
||||
9B0086C427BA392B0032BD2B /* AnchorStageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnchorStageView.h; sourceTree = "<group>"; };
|
||||
9B0086C527BA392B0032BD2B /* AnchorStageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AnchorStageView.m; sourceTree = "<group>"; };
|
||||
9B0086C827BA4F570032BD2B /* AnchorMicroView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AnchorMicroView.h; sourceTree = "<group>"; };
|
||||
@@ -3495,8 +3506,6 @@
|
||||
9BFE0D912899042600F53C24 /* XPTaskCompleteTipView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPTaskCompleteTipView.m; sourceTree = "<group>"; };
|
||||
9BFE992C288142FD009DA429 /* RoomClassifyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomClassifyModel.h; sourceTree = "<group>"; };
|
||||
9BFE992D288142FD009DA429 /* RoomClassifyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomClassifyModel.m; sourceTree = "<group>"; };
|
||||
B66633E061B1B34177CD011C /* Pods-YuMi.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YuMi.release.xcconfig"; path = "Target Support Files/Pods-YuMi/Pods-YuMi.release.xcconfig"; sourceTree = "<group>"; };
|
||||
CACF623970097D653132D69A /* Pods_YuMi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YuMi.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E801273E27E323C800BAC3F2 /* XPRoomPKViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPRoomPKViewController.h; sourceTree = "<group>"; };
|
||||
E801273F27E323C800BAC3F2 /* XPRoomPKViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPRoomPKViewController.m; sourceTree = "<group>"; };
|
||||
E801274127E323E500BAC3F2 /* XPRoomPKPresenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPRoomPKPresenter.h; sourceTree = "<group>"; };
|
||||
@@ -4803,6 +4812,7 @@
|
||||
E8F65C1E286998C9009BB5B9 /* XPMineShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMineShareViewController.m; sourceTree = "<group>"; };
|
||||
E8F65C202869A36F009BB5B9 /* ContentShareMonentsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContentShareMonentsModel.h; sourceTree = "<group>"; };
|
||||
E8F65C212869A36F009BB5B9 /* ContentShareMonentsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContentShareMonentsModel.m; sourceTree = "<group>"; };
|
||||
ECF5B2AAE493057A3DBCCFAF /* Pods-YuMi.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YuMi.debug.xcconfig"; path = "Target Support Files/Pods-YuMi/Pods-YuMi.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
F1D8556D2931FC86008C418F /* XPRoomYearActivityView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPRoomYearActivityView.h; sourceTree = "<group>"; };
|
||||
F1D8556E2931FC86008C418F /* XPRoomYearActivityView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPRoomYearActivityView.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -4821,11 +4831,11 @@
|
||||
237701082BCF73CE00D661F1 /* Security.framework in Frameworks */,
|
||||
4CD15D922D7EC2AC00D9279F /* CoreTelephony.framework in Frameworks */,
|
||||
234489082AC3C5DA0070E5D5 /* SudMGP.framework in Frameworks */,
|
||||
73FFADDC93E195344047A2EC /* Pods_YuMi.framework in Frameworks */,
|
||||
186A531926FC592100D67B2C /* libresolv.tbd in Frameworks */,
|
||||
E87888F42738C30E00BF1D57 /* StoreKit.framework in Frameworks */,
|
||||
9BA8A47727C60DF7000365A3 /* AVFoundation.framework in Frameworks */,
|
||||
9BA8A47527C60D9F000365A3 /* AudioToolbox.framework in Frameworks */,
|
||||
6996748A711C8E6931880AD0 /* Pods_YuMi.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -6473,6 +6483,7 @@
|
||||
4C0642922E98EF0A00BAF413 /* E-P */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C59C0FC2EA2508F00D1F7BD /* NewMessage */,
|
||||
4CD19C852E9CB31C0069DAA0 /* NewLogin */,
|
||||
4C1E98C22E9A45160031AE79 /* Common */,
|
||||
4C0642752E97BD6D00BAF413 /* NewMine */,
|
||||
@@ -6532,6 +6543,18 @@
|
||||
path = GZIP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C59C0FC2EA2508F00D1F7BD /* NewMessage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C59C0F72EA2508F00D1F7BD /* EPBaseListViewController.swift */,
|
||||
4C59C0F82EA2508F00D1F7BD /* EPFriendFollowingFans.swift */,
|
||||
4C59C0F92EA2508F00D1F7BD /* EPMessageListVC.swift */,
|
||||
4C59C0FA2EA2508F00D1F7BD /* EPMessageMainViewController.swift */,
|
||||
4C59C0FB2EA2508F00D1F7BD /* EPMessageSegmentView.swift */,
|
||||
);
|
||||
path = NewMessage;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C7989F02D195293006AE07B /* RoomMode */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -7965,7 +7988,7 @@
|
||||
23E56B3B2B03564B00C8DAC9 /* CoreTelephony.framework */,
|
||||
E87888F32738C30E00BF1D57 /* StoreKit.framework */,
|
||||
186A531826FC591100D67B2C /* libresolv.tbd */,
|
||||
CACF623970097D653132D69A /* Pods_YuMi.framework */,
|
||||
3148BBD005110C1B57D04595 /* Pods_YuMi.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -7973,8 +7996,8 @@
|
||||
D09C770DC30B9BAAEAFC7945 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7DB00EC07F1D0ADFF900B38D /* Pods-YuMi.debug.xcconfig */,
|
||||
B66633E061B1B34177CD011C /* Pods-YuMi.release.xcconfig */,
|
||||
ECF5B2AAE493057A3DBCCFAF /* Pods-YuMi.debug.xcconfig */,
|
||||
56B26C27109C1DD2002D0624 /* Pods-YuMi.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@@ -11634,14 +11657,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 189DD54226DE255600AB55B1 /* Build configuration list for PBXNativeTarget "YuMi" */;
|
||||
buildPhases = (
|
||||
1865B406E358C680125F108D /* [CP] Check Pods Manifest.lock */,
|
||||
1BE44CE1939801EC8D8A3B98 /* [CP] Check Pods Manifest.lock */,
|
||||
189DD52526DE255300AB55B1 /* Sources */,
|
||||
189DD52626DE255300AB55B1 /* Frameworks */,
|
||||
189DD52726DE255300AB55B1 /* Resources */,
|
||||
8311720C3643AC2030B96510 /* [CP] Embed Pods Frameworks */,
|
||||
4C25F8F9E2D1F501119C383D /* [CP] Copy Pods Resources */,
|
||||
18E7B21326E8CD220064BC9B /* Embed Frameworks */,
|
||||
232C43E62AB0754700D4B2ED /* CopyFiles */,
|
||||
5D1CA1F69A65602D6A906867 /* [CP] Embed Pods Frameworks */,
|
||||
86EC598DD301B1F442E90F99 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -11937,7 +11960,7 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
1865B406E358C680125F108D /* [CP] Check Pods Manifest.lock */ = {
|
||||
1BE44CE1939801EC8D8A3B98 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -11959,28 +11982,7 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4C25F8F9E2D1F501119C383D /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
8311720C3643AC2030B96510 /* [CP] Embed Pods Frameworks */ = {
|
||||
5D1CA1F69A65602D6A906867 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@@ -12001,6 +12003,27 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
86EC598DD301B1F442E90F99 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -13068,6 +13091,11 @@
|
||||
E87AE7FC277AAC450037823A /* XPRoomTagPresenter.m in Sources */,
|
||||
E85E7B652A4EC35A00B6D00A /* XPExchangeDiamondsModel.m in Sources */,
|
||||
E878894C273A607C00BF1D57 /* XPGiftUserCollectionViewCell.m in Sources */,
|
||||
4C59C0FD2EA2508F00D1F7BD /* EPFriendFollowingFans.swift in Sources */,
|
||||
4C59C0FE2EA2508F00D1F7BD /* EPMessageSegmentView.swift in Sources */,
|
||||
4C59C0FF2EA2508F00D1F7BD /* EPMessageMainViewController.swift in Sources */,
|
||||
4C59C1002EA2508F00D1F7BD /* EPBaseListViewController.swift in Sources */,
|
||||
4C59C1012EA2508F00D1F7BD /* EPMessageListVC.swift in Sources */,
|
||||
23E9E9A72A80F1C300B792F2 /* XPNewMineHallIncomeVC.m in Sources */,
|
||||
2331C1712A5EB71000E1D940 /* XPNobleCenterTableHeadView.m in Sources */,
|
||||
181D7F212727D9DB00B7C059 /* SocialStageView.m in Sources */,
|
||||
@@ -13457,6 +13485,7 @@
|
||||
189DD54026DE255600AB55B1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -13516,6 +13545,7 @@
|
||||
189DD54126DE255600AB55B1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -13568,7 +13598,7 @@
|
||||
};
|
||||
189DD54326DE255600AB55B1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7DB00EC07F1D0ADFF900B38D /* Pods-YuMi.debug.xcconfig */;
|
||||
baseConfigurationReference = ECF5B2AAE493057A3DBCCFAF /* Pods-YuMi.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
APP_DISPLAY_NAME = "E-Party DEBUG";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
@@ -13583,12 +13613,8 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/YuMi/Main/RTC/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Modules/YMRTC/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Resources/Client",
|
||||
"$(PROJECT_DIR)/YuMi/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Tools",
|
||||
"$(PROJECT_DIR)/YuMi/Tools/TencentOpenApiSDK",
|
||||
);
|
||||
GCC_PREFIX_HEADER = YuMi/Structure/PrefixHeader.pch;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -13830,7 +13856,7 @@
|
||||
};
|
||||
189DD54426DE255600AB55B1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B66633E061B1B34177CD011C /* Pods-YuMi.release.xcconfig */;
|
||||
baseConfigurationReference = 56B26C27109C1DD2002D0624 /* Pods-YuMi.release.xcconfig */;
|
||||
buildSettings = {
|
||||
APP_DISPLAY_NAME = "E-Party";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
@@ -13844,12 +13870,8 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/YuMi/Main/RTC/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Modules/YMRTC/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Resources/Client",
|
||||
"$(PROJECT_DIR)/YuMi/Library",
|
||||
"$(PROJECT_DIR)/YuMi/Tools",
|
||||
"$(PROJECT_DIR)/YuMi/Tools/TencentOpenApiSDK",
|
||||
);
|
||||
GCC_PREFIX_HEADER = YuMi/Structure/PrefixHeader.pch;
|
||||
INFOPLIST_FILE = YuMi/Info.plist;
|
||||
|
@@ -6,15 +6,10 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@property(nonatomic,strong,readonly)NSManagedObjectContext *managedObjectContext;
|
||||
@property(nonatomic,strong,readonly)NSManagedObjectModel *managedObjectModel;
|
||||
@property(nonatomic,strong,readonly)NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
@end
|
||||
|
||||
|
@@ -342,83 +342,4 @@ void qg_VAP_Logger_handler(VAPLogLevel level, const char* file, int line, const
|
||||
// }
|
||||
//}
|
||||
|
||||
#pragma mark - Core Data stack
|
||||
@synthesize managedObjectContext = _managedObjectContext;
|
||||
@synthesize managedObjectModel = _managedObjectModel;
|
||||
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
|
||||
|
||||
-(NSURL *)applicationDocumentsDirectory{
|
||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
}
|
||||
|
||||
- (NSManagedObjectModel *)managedObjectModel {
|
||||
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
|
||||
if (_managedObjectModel != nil) {
|
||||
return _managedObjectModel;
|
||||
}
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"_1_______" withExtension:@"momd"];
|
||||
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
return _managedObjectModel;
|
||||
}
|
||||
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
||||
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it.
|
||||
if (_persistentStoreCoordinator != nil) {
|
||||
return _persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
// Create the coordinator and store
|
||||
|
||||
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"_1_______.sqlite"];
|
||||
NSError *error = nil;
|
||||
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
|
||||
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
|
||||
// Report any error we got.
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
|
||||
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
|
||||
dict[NSUnderlyingErrorKey] = error;
|
||||
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
|
||||
// Replace this with code to handle the error appropriately.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
// NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
abort();
|
||||
}
|
||||
|
||||
return _persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContext {
|
||||
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
|
||||
if (_managedObjectContext != nil) {
|
||||
return _managedObjectContext;
|
||||
}
|
||||
|
||||
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
|
||||
if (!coordinator) {
|
||||
return nil;
|
||||
}
|
||||
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
|
||||
return _managedObjectContext;
|
||||
}
|
||||
|
||||
#pragma mark - Core Data Saving support
|
||||
|
||||
- (void)saveContext {
|
||||
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
|
||||
if (managedObjectContext != nil) {
|
||||
NSError *error = nil;
|
||||
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
// NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
54
YuMi/E-P/NewMessage/EPBaseListViewController.swift
Normal file
54
YuMi/E-P/NewMessage/EPBaseListViewController.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// EPBaseListViewController.swift
|
||||
// YuMi
|
||||
//
|
||||
// A lightweight table-view base class used by EP Message subpages.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
class EPBaseListViewController<Cell: UITableViewCell>: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
var itemsCount: Int = 0 { didSet { tableView.reloadData() } }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = UIColor(named: "ep.background.dark") ?? UIColor.black.withAlphaComponent(0.9)
|
||||
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.rowHeight = 72
|
||||
tableView.contentInsetAdjustmentBehavior = .never
|
||||
tableView.keyboardDismissMode = .onDrag
|
||||
tableView.register(Cell.self, forCellReuseIdentifier: "cell")
|
||||
|
||||
view.addSubview(tableView)
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.edges.equalTo(view.safeAreaLayoutGuide)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return itemsCount
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell
|
||||
cell.backgroundColor = .clear
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
func simulateItems(_ count: Int) {
|
||||
itemsCount = count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
126
YuMi/E-P/NewMessage/EPFriendFollowingFans.swift
Normal file
126
YuMi/E-P/NewMessage/EPFriendFollowingFans.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
import UIKit
|
||||
|
||||
final class EPFriendListVC: EPBaseListViewController<EPUserBriefCell> {
|
||||
override func viewDidLoad() { super.viewDidLoad(); simulateItems(6) }
|
||||
}
|
||||
|
||||
final class EPFollowingListVC: EPBaseListViewController<EPUserBriefCell> {
|
||||
override func viewDidLoad() { super.viewDidLoad(); simulateItems(10) }
|
||||
}
|
||||
|
||||
final class EPFansListVC: EPBaseListViewController<EPUserBriefCell> {
|
||||
override func viewDidLoad() { super.viewDidLoad(); simulateItems(12) }
|
||||
}
|
||||
|
||||
final class EPUserBriefCell: UITableViewCell {
|
||||
private let avatar = UIImageView()
|
||||
private let nameLabel = UILabel()
|
||||
private let subtitleLabel = UILabel()
|
||||
private let followButton = EPFollowButton()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setup()
|
||||
}
|
||||
required init?(coder: NSCoder) { super.init(coder: coder); setup() }
|
||||
|
||||
private func setup() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
||||
avatar.contentMode = .scaleAspectFill
|
||||
avatar.layer.cornerRadius = 24
|
||||
avatar.layer.masksToBounds = true
|
||||
avatar.image = UIImage(named: "pi_login_new_logo")
|
||||
|
||||
nameLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||
nameLabel.textColor = .white
|
||||
nameLabel.text = "Momoyy"
|
||||
|
||||
subtitleLabel.font = .systemFont(ofSize: 16)
|
||||
subtitleLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||
subtitleLabel.text = "Welcome to play"
|
||||
|
||||
contentView.addSubview(avatar)
|
||||
contentView.addSubview(nameLabel)
|
||||
contentView.addSubview(subtitleLabel)
|
||||
contentView.addSubview(followButton)
|
||||
|
||||
avatar.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
followButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
avatar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
avatar.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
avatar.widthAnchor.constraint(equalToConstant: 48),
|
||||
avatar.heightAnchor.constraint(equalToConstant: 48),
|
||||
|
||||
nameLabel.leadingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: 12),
|
||||
nameLabel.topAnchor.constraint(equalTo: avatar.topAnchor, constant: -2),
|
||||
|
||||
subtitleLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
|
||||
subtitleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 6),
|
||||
|
||||
followButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
||||
followButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
followButton.widthAnchor.constraint(equalToConstant: 120),
|
||||
followButton.heightAnchor.constraint(equalToConstant: 40)
|
||||
])
|
||||
|
||||
followButton.setFollowed(false)
|
||||
}
|
||||
}
|
||||
|
||||
final class EPFollowButton: UIButton {
|
||||
private var isFollowedState: Bool = false
|
||||
|
||||
override init(frame: CGRect) { super.init(frame: frame); setup() }
|
||||
required init?(coder: NSCoder) { super.init(coder: coder); setup() }
|
||||
|
||||
private func setup() {
|
||||
titleLabel?.font = .systemFont(ofSize: 18, weight: .semibold)
|
||||
layer.cornerRadius = 20
|
||||
layer.masksToBounds = true
|
||||
addTarget(self, action: #selector(onTap), for: .touchUpInside)
|
||||
}
|
||||
|
||||
func setFollowed(_ followed: Bool) {
|
||||
isFollowedState = followed
|
||||
if followed {
|
||||
setTitle("Followed", for: .normal)
|
||||
backgroundColor = .clear
|
||||
layer.borderWidth = 1
|
||||
layer.borderColor = UIColor.systemPurple.withAlphaComponent(0.6).cgColor
|
||||
setTitleColor(UIColor.systemPurple.withAlphaComponent(0.9), for: .normal)
|
||||
} else {
|
||||
setTitle("Follow", for: .normal)
|
||||
layer.borderWidth = 0
|
||||
setTitleColor(.white, for: .normal)
|
||||
setGradientBackground()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func onTap() { setFollowed(!isFollowedState) }
|
||||
|
||||
private func setGradientBackground() {
|
||||
let gradient = CAGradientLayer()
|
||||
gradient.colors = [UIColor.systemPink.cgColor, UIColor.systemPurple.cgColor]
|
||||
gradient.startPoint = CGPoint(x: 0, y: 0.5)
|
||||
gradient.endPoint = CGPoint(x: 1, y: 0.5)
|
||||
gradient.frame = bounds
|
||||
gradient.cornerRadius = layer.cornerRadius
|
||||
layer.sublayers?.removeAll(where: { $0 is CAGradientLayer })
|
||||
layer.insertSublayer(gradient, at: 0)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if !isFollowedState { setGradientBackground() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
90
YuMi/E-P/NewMessage/EPMessageListVC.swift
Normal file
90
YuMi/E-P/NewMessage/EPMessageListVC.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
import UIKit
|
||||
|
||||
final class EPMessageListVC: EPBaseListViewController<EPMessageCell> {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
simulateItems(8)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cell
|
||||
final class EPMessageCell: UITableViewCell {
|
||||
private let avatar = UIImageView()
|
||||
private let nameLabel = UILabel()
|
||||
private let subtitleLabel = UILabel()
|
||||
private let timeLabel = UILabel()
|
||||
private let unreadView = UILabel()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setup()
|
||||
}
|
||||
required init?(coder: NSCoder) { super.init(coder: coder); setup() }
|
||||
|
||||
private func setup() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
|
||||
avatar.contentMode = .scaleAspectFill
|
||||
avatar.layer.cornerRadius = 24
|
||||
avatar.layer.masksToBounds = true
|
||||
avatar.image = UIImage(named: "pi_login_new_logo")
|
||||
|
||||
nameLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||
nameLabel.textColor = .white
|
||||
nameLabel.text = "Momoyy"
|
||||
|
||||
subtitleLabel.font = .systemFont(ofSize: 16)
|
||||
subtitleLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||
subtitleLabel.text = "Nice to meet you"
|
||||
|
||||
timeLabel.font = .systemFont(ofSize: 14)
|
||||
timeLabel.textColor = UIColor.white.withAlphaComponent(0.5)
|
||||
timeLabel.text = "11:03"
|
||||
|
||||
unreadView.backgroundColor = UIColor.systemRed
|
||||
unreadView.textColor = .white
|
||||
unreadView.font = .systemFont(ofSize: 12, weight: .bold)
|
||||
unreadView.textAlignment = .center
|
||||
unreadView.layer.cornerRadius = 12
|
||||
unreadView.layer.masksToBounds = true
|
||||
unreadView.text = "99+"
|
||||
|
||||
contentView.addSubview(avatar)
|
||||
contentView.addSubview(nameLabel)
|
||||
contentView.addSubview(subtitleLabel)
|
||||
contentView.addSubview(timeLabel)
|
||||
contentView.addSubview(unreadView)
|
||||
|
||||
avatar.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
timeLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
unreadView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
avatar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
avatar.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
avatar.widthAnchor.constraint(equalToConstant: 48),
|
||||
avatar.heightAnchor.constraint(equalToConstant: 48),
|
||||
|
||||
nameLabel.leadingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: 12),
|
||||
nameLabel.topAnchor.constraint(equalTo: avatar.topAnchor, constant: -2),
|
||||
|
||||
subtitleLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
|
||||
subtitleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 6),
|
||||
|
||||
timeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
||||
timeLabel.topAnchor.constraint(equalTo: nameLabel.topAnchor),
|
||||
|
||||
unreadView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
||||
unreadView.centerYAnchor.constraint(equalTo: subtitleLabel.centerYAnchor),
|
||||
unreadView.widthAnchor.constraint(greaterThanOrEqualToConstant: 40),
|
||||
unreadView.heightAnchor.constraint(equalToConstant: 24)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
92
YuMi/E-P/NewMessage/EPMessageMainViewController.swift
Normal file
92
YuMi/E-P/NewMessage/EPMessageMainViewController.swift
Normal file
@@ -0,0 +1,92 @@
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
final class EPMessageMainViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
|
||||
// 外部回调:未读数变化
|
||||
var unreadCountDidChange: ((Int)->Void)?
|
||||
|
||||
private let segment = EPMessageSegmentView()
|
||||
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
|
||||
|
||||
private lazy var pages: [UIViewController] = {
|
||||
return [
|
||||
EPMessageListVC(),
|
||||
EPFriendListVC(),
|
||||
EPFollowingListVC(),
|
||||
EPFansListVC()
|
||||
]
|
||||
}()
|
||||
|
||||
private var currentIndex: Int = 0
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = UIColor.black.withAlphaComponent(0.92)
|
||||
title = YMLocalizedString("XPSessionMainViewController0")
|
||||
|
||||
setupSegment()
|
||||
setupPageVC()
|
||||
|
||||
// 模拟未读变化(后续接入桥接器)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||
self?.unreadCountDidChange?(12)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSegment() {
|
||||
view.addSubview(segment)
|
||||
segment.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(8)
|
||||
make.leading.trailing.equalToSuperview().inset(20)
|
||||
make.height.equalTo(48)
|
||||
}
|
||||
segment.didSelect = { [weak self] index in
|
||||
self?.setPage(index: index, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupPageVC() {
|
||||
addChild(pageVC)
|
||||
view.addSubview(pageVC.view)
|
||||
pageVC.view.backgroundColor = .clear
|
||||
pageVC.view.snp.makeConstraints { make in
|
||||
make.top.equalTo(segment.snp.bottom).offset(8)
|
||||
make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
|
||||
}
|
||||
pageVC.didMove(toParent: self)
|
||||
pageVC.dataSource = self
|
||||
pageVC.delegate = self
|
||||
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false)
|
||||
}
|
||||
|
||||
private func setPage(index: Int, animated: Bool) {
|
||||
guard index != currentIndex, index >= 0, index < pages.count else { return }
|
||||
let direction: UIPageViewController.NavigationDirection = index > currentIndex ? .forward : .reverse
|
||||
pageVC.setViewControllers([pages[index]], direction: direction, animated: animated)
|
||||
currentIndex = index
|
||||
segment.select(index: index, animated: animated)
|
||||
title = segment.titles[index]
|
||||
}
|
||||
|
||||
// MARK: - UIPageViewControllerDataSource
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard let idx = pages.firstIndex(of: viewController), idx > 0 else { return nil }
|
||||
return pages[idx - 1]
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard let idx = pages.firstIndex(of: viewController), idx < pages.count - 1 else { return nil }
|
||||
return pages[idx + 1]
|
||||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
|
||||
guard completed, let vc = pageViewController.viewControllers?.first, let idx = pages.firstIndex(of: vc) else { return }
|
||||
currentIndex = idx
|
||||
segment.select(index: idx, animated: true)
|
||||
title = segment.titles[idx]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
84
YuMi/E-P/NewMessage/EPMessageSegmentView.swift
Normal file
84
YuMi/E-P/NewMessage/EPMessageSegmentView.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
// A simple segmented control with underline indicator for four tabs
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
final class EPMessageSegmentView: UIView {
|
||||
enum Segment: Int, CaseIterable { case message=0, friend, following, fans }
|
||||
|
||||
var titles: [String] = [
|
||||
YMLocalizedString("XPSessionMainViewController0"),
|
||||
YMLocalizedString("XPSessionMainViewController1"),
|
||||
YMLocalizedString("XPSessionMainViewController2"),
|
||||
YMLocalizedString("XPSessionMainViewController3")
|
||||
]
|
||||
|
||||
var didSelect: ((Int)->Void)?
|
||||
|
||||
private var buttons: [UIButton] = []
|
||||
private let indicator = UIView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setup()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { super.init(coder: coder); setup() }
|
||||
|
||||
private func setup() {
|
||||
backgroundColor = .clear
|
||||
|
||||
let stack = UIStackView()
|
||||
stack.axis = .horizontal
|
||||
stack.alignment = .fill
|
||||
stack.distribution = .fillEqually
|
||||
stack.spacing = 0
|
||||
addSubview(stack)
|
||||
stack.snp.makeConstraints { $0.edges.equalToSuperview() }
|
||||
|
||||
for (idx, title) in titles.enumerated() {
|
||||
let b = UIButton(type: .custom)
|
||||
b.tag = idx
|
||||
b.setTitle(title, for: .normal)
|
||||
b.setTitleColor(UIColor.white.withAlphaComponent(0.6), for: .normal)
|
||||
b.setTitleColor(.white, for: .selected)
|
||||
b.titleLabel?.font = .systemFont(ofSize: 24, weight: idx == 0 ? .heavy : .regular)
|
||||
b.addTarget(self, action: #selector(onTap(_:)), for: .touchUpInside)
|
||||
buttons.append(b)
|
||||
stack.addArrangedSubview(b)
|
||||
}
|
||||
|
||||
indicator.backgroundColor = UIColor.systemPink
|
||||
addSubview(indicator)
|
||||
layoutIfNeeded()
|
||||
select(index: 0, animated: false)
|
||||
}
|
||||
|
||||
@objc private func onTap(_ sender: UIButton) {
|
||||
select(index: sender.tag, animated: true)
|
||||
didSelect?(sender.tag)
|
||||
}
|
||||
|
||||
func select(index: Int, animated: Bool) {
|
||||
guard index >= 0 && index < buttons.count else { return }
|
||||
for (i,b) in buttons.enumerated() {
|
||||
b.isSelected = (i == index)
|
||||
b.titleLabel?.font = .systemFont(ofSize: 24, weight: b.isSelected ? .heavy : .regular)
|
||||
}
|
||||
let target = buttons[index]
|
||||
let width = target.bounds.width
|
||||
let y = bounds.height - 4
|
||||
let frame = CGRect(x: CGFloat(index) * width + width*0.15, y: y, width: width*0.7, height: 3)
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.indicator.frame = frame
|
||||
}
|
||||
} else {
|
||||
indicator.frame = frame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -1,14 +1,10 @@
|
||||
//
|
||||
// EPMomentPublishViewController.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
|
||||
// NOTE: 话题选择功能未实现
|
||||
// 旧版本 XPMonentsPublishViewController 包含话题选择 UI (addTopicView)
|
||||
// 但实际业务中话题功能使用率低,新版本暂不实现
|
||||
// 如需实现参考: YuMi/Modules/YMMonents/View/XPMonentsPublishTopicView
|
||||
|
||||
|
||||
#import "EPMomentPublishViewController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
@@ -20,7 +16,7 @@
|
||||
#import "EPEmotionColorStorage.h"
|
||||
#import "UIView+GradientLayer.h"
|
||||
|
||||
// 发布成功通知
|
||||
|
||||
NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNotification";
|
||||
|
||||
@interface EPMomentPublishViewController () <UICollectionViewDataSource, UICollectionViewDelegate, TZImagePickerControllerDelegate, UITextViewDelegate>
|
||||
@@ -37,10 +33,10 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
@property (nonatomic, strong) UIButton *emotionButton;
|
||||
@property (nonatomic, strong) UICollectionView *collectionView;
|
||||
@property (nonatomic, strong) NSMutableArray<UIImage *> *images;
|
||||
@property (nonatomic, strong) NSMutableArray *selectedAssets; // TZImagePicker 已选资源
|
||||
@property (nonatomic, copy) NSString *selectedEmotionColor; // 选中的情绪颜色
|
||||
@property (nonatomic, strong) NSMutableArray *selectedAssets;
|
||||
@property (nonatomic, copy) NSString *selectedEmotionColor;
|
||||
|
||||
@property (nonatomic, assign) BOOL hasAddedGradient; // 标记是否已添加渐变背景
|
||||
@property (nonatomic, assign) BOOL hasAddedGradient;
|
||||
|
||||
@end
|
||||
|
||||
@@ -51,27 +47,27 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
self.view.backgroundColor = [UIColor colorWithRed:0x0C/255.0 green:0x05/255.0 blue:0x27/255.0 alpha:1.0];
|
||||
[self setupUI];
|
||||
|
||||
// 自动加载用户专属颜色
|
||||
|
||||
[self loadUserSignatureColor];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
// 添加渐变背景到发布按钮(只添加一次)
|
||||
|
||||
if (!self.hasAddedGradient && self.publishButton.bounds.size.width > 0) {
|
||||
// 使用与登录页面相同的渐变颜色(EPLoginConfig.Colors)
|
||||
// gradientStart: #F854FC, gradientEnd: #500FFF
|
||||
|
||||
|
||||
[self.publishButton addGradientBackgroundWithColors:@[
|
||||
[UIColor colorWithRed:0xF8/255.0 green:0x54/255.0 blue:0xFC/255.0 alpha:1.0], // #F854FC
|
||||
[UIColor colorWithRed:0x50/255.0 green:0x0F/255.0 blue:0xFF/255.0 alpha:1.0] // #500FFF
|
||||
[UIColor colorWithRed:0xF8/255.0 green:0x54/255.0 blue:0xFC/255.0 alpha:1.0],
|
||||
[UIColor colorWithRed:0x50/255.0 green:0x0F/255.0 blue:0xFF/255.0 alpha:1.0]
|
||||
] startPoint:CGPointMake(0, 0.5) endPoint:CGPointMake(1, 0.5) cornerRadius:25];
|
||||
|
||||
self.hasAddedGradient = YES;
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载用户专属颜色作为默认选中
|
||||
|
||||
- (void)loadUserSignatureColor {
|
||||
NSString *signatureColor = [EPEmotionColorStorage userSignatureColor];
|
||||
if (signatureColor) {
|
||||
@@ -86,7 +82,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
[self.view addSubview:self.contentView];
|
||||
[self.navView addSubview:self.backButton];
|
||||
[self.navView addSubview:self.titleLabel];
|
||||
// 发布按钮移到底部
|
||||
|
||||
[self.contentView addSubview:self.textView];
|
||||
[self.contentView addSubview:self.limitLabel];
|
||||
[self.contentView addSubview:self.lineView];
|
||||
@@ -107,7 +103,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
make.centerX.equalTo(self.navView);
|
||||
make.centerY.equalTo(self.backButton);
|
||||
}];
|
||||
// 发布按钮约束移到底部
|
||||
|
||||
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.view);
|
||||
make.top.equalTo(self.navView.mas_bottom);
|
||||
@@ -128,16 +124,14 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
|
||||
// 情绪按钮
|
||||
|
||||
[self.emotionButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.contentView).inset(15);
|
||||
make.top.equalTo(self.lineView.mas_bottom).offset(10);
|
||||
make.height.mas_equalTo(44);
|
||||
}];
|
||||
|
||||
// 计算显示3行图片所需的高度
|
||||
// itemW = (屏幕宽度 - 左右边距30 - 列间距20) / 3
|
||||
// 总高度 = 3行itemW + 2个行间距(10*2)
|
||||
|
||||
CGFloat itemW = (KScreenWidth - 15*2 - 10*2)/3.0;
|
||||
CGFloat collectionHeight = itemW * 3 + 10 * 2;
|
||||
|
||||
@@ -147,7 +141,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
make.height.mas_equalTo(collectionHeight);
|
||||
}];
|
||||
|
||||
// 底部发布按钮
|
||||
|
||||
[self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.view).inset(20);
|
||||
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-20);
|
||||
@@ -164,7 +158,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
- (void)onEmotionButtonTapped {
|
||||
EPEmotionColorPicker *picker = [[EPEmotionColorPicker alloc] init];
|
||||
|
||||
// 预选中当前颜色(如果有)
|
||||
|
||||
picker.preselectedColor = self.selectedEmotionColor;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
@@ -178,10 +172,10 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
|
||||
- (void)updateEmotionButtonAppearance {
|
||||
if (self.selectedEmotionColor) {
|
||||
// 显示选中的颜色
|
||||
|
||||
UIColor *color = [self colorFromHex:self.selectedEmotionColor];
|
||||
|
||||
// 创建色块视图
|
||||
|
||||
UIView *colorDot = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
|
||||
colorDot.backgroundColor = color;
|
||||
colorDot.layer.cornerRadius = 10;
|
||||
@@ -189,7 +183,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
colorDot.layer.borderWidth = 2;
|
||||
colorDot.layer.borderColor = [UIColor whiteColor].CGColor;
|
||||
|
||||
// 转换为 UIImage
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(colorDot.bounds.size, NO, 0);
|
||||
[colorDot.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
UIImage *colorDotImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
@@ -197,7 +191,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
|
||||
[self.emotionButton setImage:colorDotImage forState:UIControlStateNormal];
|
||||
|
||||
// 获取情绪名称
|
||||
|
||||
NSString *emotionName = [EPEmotionColorStorage emotionNameForColor:self.selectedEmotionColor];
|
||||
NSString *title = emotionName
|
||||
? [NSString stringWithFormat:@" Selected Emotion: %@", emotionName]
|
||||
@@ -212,7 +206,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
- (UIColor *)colorFromHex:(NSString *)hexString {
|
||||
unsigned rgbValue = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
[scanner setScanLocation:1]; // 跳过 #
|
||||
[scanner setScanLocation:1];
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0
|
||||
green:((rgbValue & 0xFF00) >> 8)/255.0
|
||||
@@ -223,20 +217,20 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
- (void)onPublish {
|
||||
[self.view endEditing:YES];
|
||||
|
||||
// 验证:文本或图片至少有一项
|
||||
|
||||
if (self.textView.text.length == 0 && self.images.count == 0) {
|
||||
[EPProgressHUD showError:YMLocalizedString(@"publish.content_or_image_required")];
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建 Swift API Helper
|
||||
|
||||
EPMomentAPISwiftHelper *apiHelper = [[EPMomentAPISwiftHelper alloc] init];
|
||||
|
||||
// 保存情绪颜色用于发布后关联
|
||||
|
||||
NSString *emotionColorToSave = self.selectedEmotionColor;
|
||||
|
||||
if (self.images.count > 0) {
|
||||
// 有图片:上传后发布(统一入口)
|
||||
|
||||
[[EPSDKManager shared] uploadImages:self.images
|
||||
progress:^(NSInteger uploaded, NSInteger total) {
|
||||
[EPProgressHUD showProgress:uploaded total:total];
|
||||
@@ -247,11 +241,11 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
content:self.textView.text ?: @""
|
||||
resList:resList
|
||||
completion:^{
|
||||
// 保存临时情绪颜色(等待列表刷新后匹配)
|
||||
|
||||
if (emotionColorToSave) {
|
||||
[self savePendingEmotionColor:emotionColorToSave];
|
||||
}
|
||||
// 发送发布成功通知
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:EPMomentPublishSuccessNotification object:nil];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
} failure:^(NSInteger code, NSString *msg) {
|
||||
@@ -265,16 +259,16 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
NSLog(@"上传失败: %@", errorMsg);
|
||||
}];
|
||||
} else {
|
||||
// 纯文本:直接发布
|
||||
|
||||
[apiHelper publishMomentWithType:@"0"
|
||||
content:self.textView.text
|
||||
resList:@[]
|
||||
completion:^{
|
||||
// 保存临时情绪颜色(等待列表刷新后匹配)
|
||||
|
||||
if (emotionColorToSave) {
|
||||
[self savePendingEmotionColor:emotionColorToSave];
|
||||
}
|
||||
// 发送发布成功通知
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:EPMomentPublishSuccessNotification object:nil];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
} failure:^(NSInteger code, NSString *msg) {
|
||||
@@ -284,7 +278,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存待处理的情绪颜色(临时存储,供列表刷新后匹配)
|
||||
|
||||
- (void)savePendingEmotionColor:(NSString *)color {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:color forKey:@"EP_Pending_Emotion_Color"];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@([[NSDate date] timeIntervalSince1970]) forKey:@"EP_Pending_Emotion_Timestamp"];
|
||||
@@ -294,14 +288,14 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
#pragma mark - UICollectionView
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return self.images.count + 1; // 最后一个是添加按钮
|
||||
return self.images.count + 1;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ep.publish.cell" forIndexPath:indexPath];
|
||||
cell.contentView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.06];
|
||||
cell.contentView.layer.cornerRadius = 12;
|
||||
// 清空复用子视图,避免加号被覆盖
|
||||
|
||||
for (UIView *sub in cell.contentView.subviews) { [sub removeFromSuperview]; }
|
||||
BOOL showAdd = (self.images.count < 9) && (indexPath.item == self.images.count);
|
||||
if (showAdd) {
|
||||
@@ -327,15 +321,16 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
TZImagePickerController *picker = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self];
|
||||
picker.allowPickingVideo = NO;
|
||||
picker.allowTakeVideo = NO;
|
||||
picker.selectedAssets = self.selectedAssets; // 预选
|
||||
picker.maxImagesCount = 9; // 总上限
|
||||
picker.allowCameraLocation = NO; // 禁止请求定位权限
|
||||
picker.selectedAssets = self.selectedAssets;
|
||||
picker.maxImagesCount = 9;
|
||||
[self presentViewController:picker animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TZImagePickerControllerDelegate
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray<NSDictionary *> *)infos {
|
||||
// 合并选择:在已有基础上追加,最多 9 张
|
||||
|
||||
for (NSInteger i = 0; i < assets.count; i++) {
|
||||
id asset = assets[i];
|
||||
UIImage *img = [photos xpSafeObjectAtIndex:i] ?: photos[i];
|
||||
@@ -358,12 +353,24 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
#pragma mark - Lazy
|
||||
|
||||
- (UIView *)navView { if (!_navView) { _navView = [UIView new]; _navView.backgroundColor = [UIColor clearColor]; } return _navView; }
|
||||
- (UIButton *)backButton { if (!_backButton) { _backButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_backButton setImage:[UIImage imageNamed:@"common_nav_back"] forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside]; } return _backButton; }
|
||||
- (UIButton *)backButton {
|
||||
if (!_backButton) {
|
||||
_backButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
|
||||
UIImage *backImage = [UIImage systemImageNamed:@"chevron.left"];
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:20 weight:UIImageSymbolWeightMedium];
|
||||
backImage = [backImage imageByApplyingSymbolConfiguration:config];
|
||||
[_backButton setImage:backImage forState:UIControlStateNormal];
|
||||
[_backButton setTintColor:[UIColor whiteColor]];
|
||||
[_backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _backButton;
|
||||
}
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [UILabel new];
|
||||
_titleLabel.text = YMLocalizedString(@"publish.title");
|
||||
_titleLabel.textColor = [UIColor whiteColor]; // 白色适配深色背景
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:17];
|
||||
}
|
||||
return _titleLabel;
|
||||
@@ -375,8 +382,8 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
_publishButton.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
|
||||
_publishButton.layer.cornerRadius = 25;
|
||||
_publishButton.layer.masksToBounds = NO; // 改为 NO 以便渐变层正常显示
|
||||
// 渐变背景将在 viewDidLayoutSubviews 中添加(与登录页面统一)
|
||||
_publishButton.layer.masksToBounds = NO;
|
||||
|
||||
[_publishButton addTarget:self action:@selector(onPublish) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _publishButton;
|
||||
@@ -386,9 +393,9 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
if (!_textView) {
|
||||
_textView = [SZTextView new];
|
||||
_textView.placeholder = @"Enter Content";
|
||||
_textView.textColor = [UIColor whiteColor]; // 白色文本适配深色背景
|
||||
_textView.placeholderTextColor = [[UIColor whiteColor] colorWithAlphaComponent:0.4]; // 半透明白色占位符
|
||||
_textView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.08]; // 轻微背景色
|
||||
_textView.textColor = [UIColor whiteColor];
|
||||
_textView.placeholderTextColor = [[UIColor whiteColor] colorWithAlphaComponent:0.4];
|
||||
_textView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.08];
|
||||
_textView.layer.cornerRadius = 12;
|
||||
_textView.layer.masksToBounds = YES;
|
||||
_textView.font = [UIFont systemFontOfSize:15];
|
||||
@@ -400,7 +407,7 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
if (!_limitLabel) {
|
||||
_limitLabel = [UILabel new];
|
||||
_limitLabel.text = @"0/500";
|
||||
_limitLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6]; // 浅色适配深色背景
|
||||
_limitLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6];
|
||||
_limitLabel.font = [UIFont systemFontOfSize:12];
|
||||
}
|
||||
return _limitLabel;
|
||||
@@ -410,11 +417,11 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
if (!_emotionButton) {
|
||||
_emotionButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_emotionButton setTitle:@"🎨 Add Emotion" forState:UIControlStateNormal];
|
||||
[_emotionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // 白色文本
|
||||
[_emotionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
_emotionButton.titleLabel.font = [UIFont systemFontOfSize:15];
|
||||
_emotionButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
|
||||
_emotionButton.contentEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 0);
|
||||
_emotionButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.08]; // 稍微提亮背景
|
||||
_emotionButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.08];
|
||||
_emotionButton.layer.cornerRadius = 8;
|
||||
_emotionButton.layer.masksToBounds = YES;
|
||||
[_emotionButton addTarget:self action:@selector(onEmotionButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
@@ -426,5 +433,3 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
|
||||
- (NSMutableArray *)selectedAssets { if (!_selectedAssets) { _selectedAssets = [NSMutableArray array]; } return _selectedAssets; }
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@@ -1,10 +1,8 @@
|
||||
//
|
||||
// EPMomentViewController.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "EPMomentViewController.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
@@ -18,13 +16,13 @@
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
/// 列表视图(MVVM:View)
|
||||
|
||||
@property (nonatomic, strong) EPMomentListView *listView;
|
||||
|
||||
/// 顶部图标
|
||||
|
||||
@property (nonatomic, strong) UIImageView *topIconImageView;
|
||||
|
||||
/// 顶部固定文案
|
||||
|
||||
@property (nonatomic, strong) UILabel *topTipLabel;
|
||||
|
||||
@end
|
||||
@@ -38,24 +36,33 @@
|
||||
|
||||
self.title = @"Enjoy your Life Time";
|
||||
|
||||
// 设置 title 为白色
|
||||
|
||||
[self.navigationController.navigationBar setTitleTextAttributes:@{
|
||||
NSForegroundColorAttributeName: [UIColor whiteColor]
|
||||
}];
|
||||
|
||||
[self setupUI];
|
||||
[self.listView reloadFirstPage];
|
||||
|
||||
// 监听发布成功通知
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onMomentPublishSuccess:)
|
||||
name:EPMomentPublishSuccessNotification
|
||||
object:nil];
|
||||
|
||||
// ✅ 新增:冷启动时延迟检查数据,如果没有数据则自动刷新一次
|
||||
[self scheduleAutoRefreshIfNeeded];
|
||||
NSLog(@"[EPMomentViewController] 页面加载完成,UI 已设置");
|
||||
}
|
||||
|
||||
NSLog(@"[EPMomentViewController] 页面加载完成");
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSLog(@"[EPMomentViewController] 首次 viewDidAppear,延迟 0.3s 后开始加载数据");
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
NSLog(@"[EPMomentViewController] 触发首次数据加载");
|
||||
[self.listView reloadFirstPage];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
@@ -73,7 +80,7 @@
|
||||
make.edges.mas_equalTo(self.view);
|
||||
}];
|
||||
|
||||
// 顶部图标
|
||||
|
||||
[self.view addSubview:self.topIconImageView];
|
||||
[self.topIconImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self.view);
|
||||
@@ -81,21 +88,21 @@
|
||||
make.size.mas_equalTo(CGSizeMake(56, 41));
|
||||
}];
|
||||
|
||||
// 顶部固定文案
|
||||
|
||||
[self.view addSubview:self.topTipLabel];
|
||||
[self.topTipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.topIconImageView.mas_bottom).offset(14);
|
||||
make.leading.trailing.equalTo(self.view).inset(20);
|
||||
}];
|
||||
|
||||
// 列表视图
|
||||
|
||||
[self.view addSubview:self.listView];
|
||||
[self.listView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.bottom.equalTo(self.view);
|
||||
make.top.equalTo(self.topTipLabel.mas_bottom).offset(8);
|
||||
}];
|
||||
|
||||
// 右上角发布按钮
|
||||
|
||||
UIImage *addIcon = [UIImage imageNamed:@"icon_moment_add"];
|
||||
UIButton *publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
publishButton.contentMode = UIViewContentModeScaleAspectFit;
|
||||
@@ -108,26 +115,6 @@
|
||||
NSLog(@"[EPMomentViewController] UI 设置完成");
|
||||
}
|
||||
|
||||
// 不再在 VC 内部直接发请求/维护分页
|
||||
|
||||
// MARK: - Auto Refresh
|
||||
|
||||
/// 延迟检查数据,如果没有数据则自动刷新(解决冷启动数据未加载问题)
|
||||
- (void)scheduleAutoRefreshIfNeeded {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
if (!self) return;
|
||||
|
||||
// 检查是否有数据
|
||||
if (self.listView.rawList.count == 0) {
|
||||
NSLog(@"[EPMomentViewController] ⚠️ 冷启动 1 秒后检测到无数据,自动刷新一次");
|
||||
[self.listView reloadFirstPage];
|
||||
} else {
|
||||
NSLog(@"[EPMomentViewController] ✅ 冷启动 1 秒后检测到已有 %lu 条数据,无需刷新", (unsigned long)self.listView.rawList.count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@@ -155,17 +142,16 @@
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
// 列表点击回调由 listView 暴露
|
||||
|
||||
// MARK: - Lazy Loading
|
||||
|
||||
- (EPMomentListView *)listView {
|
||||
if (!_listView) {
|
||||
_listView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
_listView.onSelectMoment = ^(NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
// [self showAlertWithMessage:[NSString stringWithFormat:YMLocalizedString(@"moment.item_clicked"), (long)index]];
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
return _listView;
|
||||
@@ -190,6 +176,5 @@
|
||||
return _topTipLabel;
|
||||
}
|
||||
|
||||
// 无数据源属性
|
||||
|
||||
@end
|
||||
|
@@ -1,10 +1,7 @@
|
||||
//
|
||||
// EPEmotionColorStorage.h
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-14.
|
||||
// 本地情绪颜色存储管理器(基于 UserDefaults)
|
||||
//
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@@ -12,47 +9,38 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EPEmotionColorStorage : NSObject
|
||||
|
||||
/// 保存动态的情绪颜色
|
||||
/// @param hexColor Hex 格式颜色值,如 #FF0000
|
||||
/// @param dynamicId 动态 ID
|
||||
|
||||
+ (void)saveColor:(NSString *)hexColor forDynamicId:(NSString *)dynamicId;
|
||||
|
||||
/// 获取动态关联的情绪颜色
|
||||
/// @param dynamicId 动态 ID
|
||||
/// @return Hex 格式颜色值,若未设置则返回 nil
|
||||
|
||||
+ (nullable NSString *)colorForDynamicId:(NSString *)dynamicId;
|
||||
|
||||
/// 删除动态的情绪颜色
|
||||
/// @param dynamicId 动态 ID
|
||||
|
||||
+ (void)removeColorForDynamicId:(NSString *)dynamicId;
|
||||
|
||||
/// 获取所有预设情绪颜色(6种基础情绪)
|
||||
/// @return Hex 颜色数组
|
||||
|
||||
+ (NSArray<NSString *> *)allEmotionColors;
|
||||
|
||||
/// 获取随机情绪颜色(不持久化)
|
||||
|
||||
+ (NSString *)randomEmotionColor;
|
||||
|
||||
/// 根据颜色值获取情绪名称
|
||||
/// @param hexColor Hex 格式颜色值,如 #FFD700
|
||||
/// @return 情绪名称(如 "Joy"),若未匹配返回 nil
|
||||
|
||||
+ (nullable NSString *)emotionNameForColor:(NSString *)hexColor;
|
||||
|
||||
#pragma mark - User Signature Color
|
||||
|
||||
/// 保存用户专属颜色
|
||||
|
||||
+ (void)saveUserSignatureColor:(NSString *)hexColor;
|
||||
|
||||
/// 获取用户专属颜色
|
||||
|
||||
+ (nullable NSString *)userSignatureColor;
|
||||
|
||||
/// 是否已设置专属颜色
|
||||
|
||||
+ (BOOL)hasUserSignatureColor;
|
||||
|
||||
/// 清除专属颜色(调试用)
|
||||
|
||||
+ (void)clearUserSignatureColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPEmotionColorStorage.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-14.
|
||||
//
|
||||
|
||||
|
||||
#import "EPEmotionColorStorage.h"
|
||||
|
||||
@@ -44,14 +42,14 @@ static NSString *const kUserSignatureTimestampKey = @"EP_User_Signature_Timestam
|
||||
|
||||
+ (NSArray<NSString *> *)allEmotionColors {
|
||||
return @[
|
||||
@"#FFD700", // 喜悦 Joy(金黄)- 降低亮度,更温暖
|
||||
@"#4A90E2", // 悲伤 Sadness(天蓝)- 提高亮度,更柔和
|
||||
@"#E74C3C", // 愤怒 Anger(珊瑚红)- 降低饱和度
|
||||
@"#9B59B6", // 恐惧 Fear(紫罗兰)- 稍微提亮
|
||||
@"#FF9A3D", // 惊讶 Surprise(柔和橙)- 略微调暗
|
||||
@"#2ECC71", // 厌恶 Disgust(翡翠绿)- 大幅降低亮度
|
||||
@"#3498DB", // 信任 Trust(亮蓝)- 清新明亮
|
||||
@"#F39C12" // 期待 Anticipation(琥珀色)- 温暖期待
|
||||
@"#FFD700",
|
||||
@"#4A90E2",
|
||||
@"#E74C3C",
|
||||
@"#9B59B6",
|
||||
@"#FF9A3D",
|
||||
@"#2ECC71",
|
||||
@"#3498DB",
|
||||
@"#F39C12"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ static NSString *const kUserSignatureTimestampKey = @"EP_User_Signature_Timestam
|
||||
NSArray<NSString *> *colors = [self allEmotionColors];
|
||||
NSArray<NSString *> *emotions = @[@"Joy", @"Sadness", @"Anger", @"Fear", @"Surprise", @"Disgust", @"Trust", @"Anticipation"];
|
||||
|
||||
// 大小写不敏感比较
|
||||
|
||||
NSString *upperHex = [hexColor uppercaseString];
|
||||
for (NSInteger i = 0; i < colors.count; i++) {
|
||||
if ([[colors[i] uppercaseString] isEqualToString:upperHex]) {
|
||||
@@ -115,4 +113,3 @@ static NSString *const kUserSignatureTimestampKey = @"EP_User_Signature_Timestam
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -1,55 +1,47 @@
|
||||
//
|
||||
// EPMomentAPISwiftHelper.swift
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 动态 API 封装(Swift 现代化版本)
|
||||
/// 统一封装列表获取和发布功能,完全替代 OC 版本
|
||||
|
||||
@objc class EPMomentAPISwiftHelper: NSObject {
|
||||
|
||||
/// 拉取最新动态列表
|
||||
/// - Parameters:
|
||||
/// - nextID: 下一页 ID,首次传空字符串
|
||||
/// - completion: 成功回调 (动态列表, 下一页ID)
|
||||
/// - failure: 失败回调 (错误码, 错误信息)
|
||||
|
||||
@objc func fetchLatestMomentsWithNextID(
|
||||
_ nextID: String,
|
||||
completion: @escaping ([MomentsInfoModel], String) -> Void,
|
||||
failure: @escaping (Int, String) -> Void
|
||||
) {
|
||||
let pageSize = "20"
|
||||
let types = "0,2" // 图片+文字
|
||||
let types = "0,2"
|
||||
|
||||
NSLog("[EPMomentAPISwiftHelper] 🔄 开始请求动态列表,nextID=\(nextID.isEmpty ? "(首页)" : nextID)")
|
||||
|
||||
Api.momentsLatestList({ (data, code, msg) in
|
||||
NSLog("[EPMomentAPISwiftHelper] 📥 收到响应,code=\(code)")
|
||||
|
||||
if code == 200, let dict = data?.data as? NSDictionary {
|
||||
// 使用 MomentsListInfoModel 序列化响应数据(标准化方式)
|
||||
// 参考: XPMomentsLatestPresenter.m line 25 / EPLoginService.swift line 34
|
||||
// Swift 中使用 mj_object(withKeyValues:) 而不是 model(withJSON:)
|
||||
NSLog("[EPMomentAPISwiftHelper] 📦 开始解析数据字典")
|
||||
|
||||
if let listInfo = MomentsListInfoModel.mj_object(withKeyValues: dict) {
|
||||
let dynamicList = listInfo.dynamicList
|
||||
let nextDynamicId = listInfo.nextDynamicId
|
||||
NSLog("[EPMomentAPISwiftHelper] ✅ 解析成功,dynamicList.count=\(dynamicList.count), nextDynamicId=\(nextDynamicId)")
|
||||
completion(dynamicList, nextDynamicId)
|
||||
} else {
|
||||
// 序列化失败时返回空数据
|
||||
NSLog("[EPMomentAPISwiftHelper] ⚠️ 解析失败,返回空数组")
|
||||
completion([], "")
|
||||
}
|
||||
} else {
|
||||
NSLog("[EPMomentAPISwiftHelper] ❌ 请求失败,code=\(code), msg=\(msg ?? "无错误信息")")
|
||||
failure(Int(code), msg ?? YMLocalizedString("error.request_failed"))
|
||||
}
|
||||
}, dynamicId: nextID, pageSize: pageSize, types: types)
|
||||
}
|
||||
|
||||
/// 发布动态
|
||||
/// - Parameters:
|
||||
/// - type: "0"=纯文本, "2"=图片
|
||||
/// - content: 文本内容
|
||||
/// - resList: 图片信息数组
|
||||
/// - completion: 成功回调
|
||||
/// - failure: 失败回调 (错误码, 错误信息)
|
||||
|
||||
@objc func publishMoment(
|
||||
type: String,
|
||||
content: String,
|
||||
@@ -62,10 +54,9 @@ import Foundation
|
||||
return
|
||||
}
|
||||
|
||||
// worldId 传空字符串(话题功能不实现)
|
||||
|
||||
// NOTE: 旧版本 XPMonentsPublishViewController 包含话题选择功能
|
||||
// 但实际业务中话题功能使用率低,新版本暂不实现
|
||||
// 如需实现参考: YuMi/Modules/YMMonents/View/XPMonentsPublishTopicView
|
||||
|
||||
|
||||
Api.momentsPublish({ (data, code, msg) in
|
||||
if code == 200 {
|
||||
@@ -76,14 +67,7 @@ import Foundation
|
||||
}, uid: uid, type: type, worldId: "", content: content, resList: resList)
|
||||
}
|
||||
|
||||
/// 点赞/取消点赞动态
|
||||
/// - Parameters:
|
||||
/// - dynamicId: 动态 ID
|
||||
/// - isLike: true=点赞,false=取消点赞
|
||||
/// - likedUid: 动态发布者 UID
|
||||
/// - worldId: 话题 ID
|
||||
/// - completion: 成功回调
|
||||
/// - failure: 失败回调 (错误码, 错误信息)
|
||||
|
||||
@objc func likeMoment(
|
||||
dynamicId: String,
|
||||
isLike: Bool,
|
||||
@@ -109,4 +93,3 @@ import Foundation
|
||||
}, dynamicId: dynamicId, uid: uid, status: status, likedUid: likedUid, worldId: worldIdStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPEmotionColorPicker.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-14.
|
||||
//
|
||||
|
||||
|
||||
#import "EPEmotionColorPicker.h"
|
||||
#import "EPEmotionColorWheelView.h"
|
||||
@@ -39,27 +37,27 @@
|
||||
- (void)setupUI {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// 背景遮罩
|
||||
|
||||
[self addSubview:self.backgroundMask];
|
||||
[self.backgroundMask mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
// 底部卡片容器
|
||||
|
||||
[self addSubview:self.containerView];
|
||||
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.bottom.equalTo(self);
|
||||
make.height.mas_equalTo(450); // 增加高度以适应新布局
|
||||
make.height.mas_equalTo(450);
|
||||
}];
|
||||
|
||||
// 标题
|
||||
|
||||
[self.containerView addSubview:self.titleLabel];
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.containerView).offset(20);
|
||||
make.centerX.equalTo(self.containerView);
|
||||
}];
|
||||
|
||||
// Info 按钮(左上角)
|
||||
|
||||
[self.containerView addSubview:self.infoButton];
|
||||
[self.infoButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self.containerView).offset(16);
|
||||
@@ -67,7 +65,7 @@
|
||||
make.size.mas_equalTo(CGSizeMake(28, 28));
|
||||
}];
|
||||
|
||||
// OK 按钮(右上角)
|
||||
|
||||
[self.containerView addSubview:self.okButton];
|
||||
[self.okButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.equalTo(self.containerView).offset(-16);
|
||||
@@ -75,7 +73,7 @@
|
||||
make.size.mas_equalTo(CGSizeMake(60, 32));
|
||||
}];
|
||||
|
||||
// 选中状态显示区域
|
||||
|
||||
[self.containerView addSubview:self.selectedColorView];
|
||||
[self.selectedColorView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.titleLabel.mas_bottom).offset(20);
|
||||
@@ -84,12 +82,12 @@
|
||||
make.leading.trailing.equalTo(self.containerView).inset(20);
|
||||
}];
|
||||
|
||||
// 色轮视图(使用共享组件)
|
||||
|
||||
[self.containerView addSubview:self.colorWheelView];
|
||||
[self.colorWheelView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self.containerView);
|
||||
make.top.equalTo(self.selectedColorView.mas_bottom).offset(20);
|
||||
make.size.mas_equalTo(CGSizeMake(280, 280)); // 调整尺寸
|
||||
make.size.mas_equalTo(CGSizeMake(280, 280));
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -119,11 +117,11 @@
|
||||
make.edges.equalTo(parentView);
|
||||
}];
|
||||
|
||||
// 初始状态
|
||||
self.backgroundMask.alpha = 0;
|
||||
self.containerView.transform = CGAffineTransformMakeTranslation(0, 450); // 更新动画偏移量
|
||||
|
||||
// 弹出动画
|
||||
self.backgroundMask.alpha = 0;
|
||||
self.containerView.transform = CGAffineTransformMakeTranslation(0, 450);
|
||||
|
||||
|
||||
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
self.backgroundMask.alpha = 1;
|
||||
self.containerView.transform = CGAffineTransformIdentity;
|
||||
@@ -133,7 +131,7 @@
|
||||
- (void)dismiss {
|
||||
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
|
||||
self.backgroundMask.alpha = 0;
|
||||
self.containerView.transform = CGAffineTransformMakeTranslation(0, 450); // 更新动画偏移量
|
||||
self.containerView.transform = CGAffineTransformMakeTranslation(0, 450);
|
||||
} completion:^(BOOL finished) {
|
||||
[self removeFromSuperview];
|
||||
}];
|
||||
@@ -177,12 +175,12 @@
|
||||
if (!_infoButton) {
|
||||
_infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
|
||||
// 使用系统 info.circle 图标
|
||||
|
||||
UIImage *infoIcon = [UIImage systemImageNamed:@"info.circle"];
|
||||
[_infoButton setImage:infoIcon forState:UIControlStateNormal];
|
||||
_infoButton.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
|
||||
|
||||
// 点击效果
|
||||
|
||||
[_infoButton addTarget:self action:@selector(onInfoButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _infoButton;
|
||||
@@ -194,11 +192,11 @@
|
||||
_selectedColorView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.1];
|
||||
_selectedColorView.layer.cornerRadius = 25;
|
||||
_selectedColorView.layer.masksToBounds = YES;
|
||||
_selectedColorView.hidden = YES; // 初始隐藏
|
||||
_selectedColorView.hidden = YES;
|
||||
|
||||
|
||||
// 颜色圆点
|
||||
UIView *colorDot = [[UIView alloc] init];
|
||||
colorDot.tag = 100; // 用于后续查找
|
||||
colorDot.tag = 100;
|
||||
colorDot.layer.cornerRadius = 12;
|
||||
colorDot.layer.masksToBounds = YES;
|
||||
[_selectedColorView addSubview:colorDot];
|
||||
@@ -208,7 +206,7 @@
|
||||
make.size.mas_equalTo(CGSizeMake(24, 24));
|
||||
}];
|
||||
|
||||
// 情绪名称标签
|
||||
|
||||
[_selectedColorView addSubview:self.selectedColorLabel];
|
||||
[self.selectedColorLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(colorDot.mas_trailing).offset(12);
|
||||
@@ -238,7 +236,7 @@
|
||||
_okButton.backgroundColor = [UIColor colorWithRed:0x9B/255.0 green:0x59/255.0 blue:0xB6/255.0 alpha:1.0];
|
||||
_okButton.layer.cornerRadius = 16;
|
||||
_okButton.layer.masksToBounds = YES;
|
||||
_okButton.enabled = NO; // 初始禁用
|
||||
_okButton.enabled = NO;
|
||||
_okButton.alpha = 0.5;
|
||||
[_okButton addTarget:self action:@selector(onOkButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
@@ -256,41 +254,41 @@
|
||||
_colorWheelView.onColorTapped = ^(NSString *hexColor, NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
|
||||
// 保存当前选择
|
||||
|
||||
self.currentSelectedColor = hexColor;
|
||||
self.currentSelectedIndex = index;
|
||||
|
||||
// 更新选中状态显示
|
||||
|
||||
[self updateSelectedColorDisplay:hexColor index:index];
|
||||
};
|
||||
}
|
||||
return _colorWheelView;
|
||||
}
|
||||
|
||||
/// 更新选中颜色显示
|
||||
|
||||
- (void)updateSelectedColorDisplay:(NSString *)hexColor index:(NSInteger)index {
|
||||
NSArray<NSString *> *emotions = @[@"Joy", @"Sadness", @"Anger", @"Fear", @"Surprise", @"Disgust", @"Trust", @"Anticipation"];
|
||||
|
||||
// 显示选中状态区域
|
||||
|
||||
self.selectedColorView.hidden = NO;
|
||||
|
||||
// 更新颜色圆点
|
||||
|
||||
UIView *colorDot = [self.selectedColorView viewWithTag:100];
|
||||
colorDot.backgroundColor = [self colorFromHex:hexColor];
|
||||
|
||||
// 更新情绪名称
|
||||
|
||||
self.selectedColorLabel.text = emotions[index];
|
||||
|
||||
// 启用OK按钮
|
||||
|
||||
self.okButton.enabled = YES;
|
||||
self.okButton.alpha = 1.0;
|
||||
}
|
||||
|
||||
/// Hex 转 UIColor
|
||||
|
||||
- (UIColor *)colorFromHex:(NSString *)hexString {
|
||||
unsigned rgbValue = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
[scanner setScanLocation:1]; // 跳过 #
|
||||
[scanner setScanLocation:1];
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0
|
||||
green:((rgbValue & 0xFF00) >> 8)/255.0
|
||||
@@ -299,4 +297,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPEmotionColorWheelView.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-15.
|
||||
//
|
||||
|
||||
|
||||
#import "EPEmotionColorWheelView.h"
|
||||
#import "EPEmotionColorStorage.h"
|
||||
@@ -11,7 +9,7 @@
|
||||
@interface EPEmotionColorWheelView ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<UIButton *> *colorButtons;
|
||||
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中的索引
|
||||
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,7 +19,7 @@
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
// 默认配置
|
||||
|
||||
_radius = 80.0;
|
||||
_buttonSize = 50.0;
|
||||
_colorButtons = [NSMutableArray array];
|
||||
@@ -34,7 +32,7 @@
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
// 如果色轮还未创建,自动创建
|
||||
|
||||
if (self.colorButtons.count == 0) {
|
||||
[self createColorButtons];
|
||||
}
|
||||
@@ -45,13 +43,13 @@
|
||||
- (void)reloadWithPreselectedColor:(NSString *)color {
|
||||
self.preselectedColor = color;
|
||||
|
||||
// 清除旧按钮
|
||||
|
||||
for (UIButton *btn in self.colorButtons) {
|
||||
[btn removeFromSuperview];
|
||||
}
|
||||
[self.colorButtons removeAllObjects];
|
||||
|
||||
// 重新创建
|
||||
|
||||
[self createColorButtons];
|
||||
}
|
||||
|
||||
@@ -66,7 +64,7 @@
|
||||
CGFloat centerY = CGRectGetHeight(self.bounds) / 2.0;
|
||||
|
||||
for (NSInteger i = 0; i < colors.count; i++) {
|
||||
// 从顶部开始,顺时针排列
|
||||
|
||||
CGFloat angle = angleStep * i - M_PI_2;
|
||||
CGFloat x = centerX + self.radius * cos(angle) - self.buttonSize / 2.0;
|
||||
CGFloat y = centerY + self.radius * sin(angle) - self.buttonSize / 2.0;
|
||||
@@ -81,13 +79,13 @@
|
||||
button.tag = i;
|
||||
[button addTarget:self action:@selector(onButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
// 如果是预选中颜色,添加选中态标识
|
||||
|
||||
if (self.preselectedColor && [colors[i] isEqualToString:self.preselectedColor]) {
|
||||
button.layer.borderWidth = 5.0; // 加粗边框
|
||||
button.transform = CGAffineTransformMakeScale(1.1, 1.1); // 稍微放大
|
||||
button.layer.borderWidth = 5.0;
|
||||
button.transform = CGAffineTransformMakeScale(1.1, 1.1);
|
||||
}
|
||||
|
||||
// 添加阴影效果
|
||||
|
||||
button.layer.shadowColor = [self colorFromHex:colors[i]].CGColor;
|
||||
button.layer.shadowOffset = CGSizeMake(0, 2);
|
||||
button.layer.shadowOpacity = 0.6;
|
||||
@@ -103,10 +101,10 @@
|
||||
NSInteger index = sender.tag;
|
||||
self.selectedIndex = index;
|
||||
|
||||
// 更新选中状态
|
||||
|
||||
[self updateSelectionState];
|
||||
|
||||
// 执行回调(仅用于更新UI,不直接确认选择)
|
||||
|
||||
NSArray<NSString *> *colors = [EPEmotionColorStorage allEmotionColors];
|
||||
NSString *selectedColor = colors[index];
|
||||
if (self.onColorTapped) {
|
||||
@@ -114,16 +112,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新选中状态
|
||||
|
||||
- (void)updateSelectionState {
|
||||
for (NSInteger i = 0; i < self.colorButtons.count; i++) {
|
||||
UIButton *button = self.colorButtons[i];
|
||||
if (i == self.selectedIndex) {
|
||||
// 选中状态:加粗边框,稍微放大
|
||||
|
||||
button.layer.borderWidth = 5.0;
|
||||
button.transform = CGAffineTransformMakeScale(1.1, 1.1);
|
||||
} else {
|
||||
// 未选中状态:正常边框,正常大小
|
||||
|
||||
button.layer.borderWidth = 3.0;
|
||||
button.transform = CGAffineTransformIdentity;
|
||||
}
|
||||
@@ -135,7 +133,7 @@
|
||||
- (UIColor *)colorFromHex:(NSString *)hexString {
|
||||
unsigned rgbValue = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
[scanner setScanLocation:1]; // 跳过 #
|
||||
[scanner setScanLocation:1];
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0
|
||||
green:((rgbValue & 0xFF00) >> 8)/255.0
|
||||
@@ -144,4 +142,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPEmotionInfoView.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-16.
|
||||
//
|
||||
|
||||
|
||||
#import "EPEmotionInfoView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
@@ -33,13 +31,13 @@
|
||||
- (void)setupUI {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// 背景遮罩
|
||||
|
||||
[self addSubview:self.backgroundMask];
|
||||
[self.backgroundMask mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
// 内容容器
|
||||
|
||||
[self addSubview:self.contentContainer];
|
||||
[self.contentContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self);
|
||||
@@ -47,14 +45,14 @@
|
||||
make.height.mas_lessThanOrEqualTo(500);
|
||||
}];
|
||||
|
||||
// 标题
|
||||
|
||||
[self.contentContainer addSubview:self.titleLabel];
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contentContainer).offset(24);
|
||||
make.leading.trailing.equalTo(self.contentContainer).inset(20);
|
||||
}];
|
||||
|
||||
// 滚动视图
|
||||
|
||||
[self.contentContainer addSubview:self.scrollView];
|
||||
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.titleLabel.mas_bottom).offset(16);
|
||||
@@ -62,14 +60,14 @@
|
||||
make.height.mas_lessThanOrEqualTo(320);
|
||||
}];
|
||||
|
||||
// 内容文本
|
||||
|
||||
[self.scrollView addSubview:self.contentLabel];
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.scrollView);
|
||||
make.width.equalTo(self.scrollView);
|
||||
}];
|
||||
|
||||
// 关闭按钮
|
||||
|
||||
[self.contentContainer addSubview:self.closeButton];
|
||||
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.scrollView.mas_bottom).offset(20);
|
||||
@@ -98,12 +96,12 @@
|
||||
make.edges.equalTo(parentView);
|
||||
}];
|
||||
|
||||
// 初始状态
|
||||
|
||||
self.backgroundMask.alpha = 0;
|
||||
self.contentContainer.alpha = 0;
|
||||
self.contentContainer.transform = CGAffineTransformMakeScale(0.9, 0.9);
|
||||
|
||||
// 弹出动画
|
||||
|
||||
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
self.backgroundMask.alpha = 1;
|
||||
self.contentContainer.alpha = 1;
|
||||
@@ -170,7 +168,7 @@
|
||||
_contentLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
|
||||
_contentLabel.font = [UIFont systemFontOfSize:15];
|
||||
|
||||
// 普拉奇克情绪轮说明文本
|
||||
|
||||
NSString *content = @"Based on Plutchik's Wheel of Emotions, we use 8 core colors to represent fundamental human emotions:\n\n"
|
||||
"🟡 Joy (Gold)\n"
|
||||
"Represents happiness, delight, and cheerfulness. Like sunshine warming your heart.\n\n"
|
||||
@@ -210,4 +208,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -1,10 +1,8 @@
|
||||
//
|
||||
// NewMomentCell.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "EPMomentCell.h"
|
||||
#import "MomentsInfoModel.h"
|
||||
@@ -12,52 +10,50 @@
|
||||
#import "NetImageView.h"
|
||||
#import "EPEmotionColorStorage.h"
|
||||
#import "SDPhotoBrowser.h"
|
||||
#import "YuMi-Swift.h" // Swift 互操作
|
||||
#import "YuMi-Swift.h"
|
||||
|
||||
@interface EPMomentCell () <SDPhotoBrowserDelegate>
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
/// 卡片容器
|
||||
|
||||
@property (nonatomic, strong) UIView *cardView;
|
||||
|
||||
/// 彩色背景层(毛玻璃下方)
|
||||
|
||||
@property (nonatomic, strong) UIView *colorBackgroundView;
|
||||
|
||||
/// 毛玻璃效果视图
|
||||
|
||||
@property (nonatomic, strong) UIVisualEffectView *blurEffectView;
|
||||
|
||||
/// 头像(网络)
|
||||
|
||||
@property (nonatomic, strong) NetImageView *avatarImageView;
|
||||
|
||||
/// 用户名
|
||||
|
||||
@property (nonatomic, strong) UILabel *nameLabel;
|
||||
|
||||
/// 时间标签
|
||||
|
||||
@property (nonatomic, strong) UILabel *timeLabel;
|
||||
|
||||
/// 内容标签
|
||||
|
||||
@property (nonatomic, strong) UILabel *contentLabel;
|
||||
|
||||
/// 图片容器(九宫格)
|
||||
|
||||
@property (nonatomic, strong) UIView *imagesContainer;
|
||||
@property (nonatomic, strong) NSMutableArray<NetImageView *> *imageViews;
|
||||
|
||||
/// 底部操作栏
|
||||
|
||||
@property (nonatomic, strong) UIView *actionBar;
|
||||
|
||||
/// 点赞按钮
|
||||
|
||||
@property (nonatomic, strong) UIButton *likeButton;
|
||||
|
||||
/// 评论按钮
|
||||
|
||||
@property (nonatomic, strong) UIButton *commentButton;
|
||||
|
||||
// 分享按钮已移除
|
||||
|
||||
/// 当前数据模型
|
||||
@property (nonatomic, strong) MomentsInfoModel *currentModel;
|
||||
|
||||
/// API Helper (Swift 版本)
|
||||
|
||||
@property (nonatomic, strong) EPMomentAPISwiftHelper *apiHelper;
|
||||
|
||||
@end
|
||||
@@ -78,7 +74,7 @@
|
||||
// MARK: - Setup UI
|
||||
|
||||
- (void)setupUI {
|
||||
// 卡片容器(圆角矩形 + 阴影)
|
||||
|
||||
[self.contentView addSubview:self.cardView];
|
||||
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.contentView).inset(15);
|
||||
@@ -86,19 +82,19 @@
|
||||
make.bottom.equalTo(self.contentView).offset(-8).priority(UILayoutPriorityRequired - 1);
|
||||
}];
|
||||
|
||||
// 彩色背景层(最底层)
|
||||
|
||||
[self.cardView addSubview:self.colorBackgroundView];
|
||||
[self.colorBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.cardView);
|
||||
}];
|
||||
|
||||
// 毛玻璃效果视图(在彩色背景层之上)
|
||||
|
||||
[self.cardView addSubview:self.blurEffectView];
|
||||
[self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.cardView);
|
||||
}];
|
||||
|
||||
// 头像(
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.avatarImageView];
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self.cardView).offset(15);
|
||||
@@ -106,7 +102,7 @@
|
||||
make.size.mas_equalTo(CGSizeMake(40, 40));
|
||||
}];
|
||||
|
||||
// 用户名
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.nameLabel];
|
||||
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self.avatarImageView.mas_trailing).offset(10);
|
||||
@@ -114,7 +110,7 @@
|
||||
make.trailing.equalTo(self.cardView).offset(-15);
|
||||
}];
|
||||
|
||||
// 时间
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.timeLabel];
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self.nameLabel);
|
||||
@@ -122,32 +118,32 @@
|
||||
make.trailing.equalTo(self.cardView).offset(-15);
|
||||
}];
|
||||
|
||||
// 内容
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.contentLabel];
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
|
||||
}];
|
||||
|
||||
// 图片九宫格
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.imagesContainer];
|
||||
[self.imagesContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.cardView).inset(15);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(12);
|
||||
make.height.mas_equalTo(0); // 初始高度为0,renderImages 时会 remakeConstraints
|
||||
make.height.mas_equalTo(0);
|
||||
}];
|
||||
|
||||
// 底部操作栏
|
||||
|
||||
[self.blurEffectView.contentView addSubview:self.actionBar];
|
||||
[self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.trailing.equalTo(self.cardView);
|
||||
make.top.equalTo(self.imagesContainer.mas_bottom).offset(12);
|
||||
make.height.mas_equalTo(50);
|
||||
// 设置较高优先级,确保底部约束生效
|
||||
|
||||
make.bottom.equalTo(self.cardView).offset(-8).priority(UILayoutPriorityRequired - 2);
|
||||
}];
|
||||
|
||||
// 点赞按钮(居左显示,评论功能已隐藏)
|
||||
|
||||
[self.actionBar addSubview:self.likeButton];
|
||||
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self.actionBar);
|
||||
@@ -161,19 +157,19 @@
|
||||
- (void)configureWithModel:(MomentsInfoModel *)model {
|
||||
self.currentModel = model;
|
||||
|
||||
// 配置用户名
|
||||
|
||||
self.nameLabel.text = model.nick ?: YMLocalizedString(@"user.anonymous");
|
||||
|
||||
// 配置时间(将时间戳转换为 MM/dd 格式)
|
||||
|
||||
self.timeLabel.text = [self formatTimestampToDate:model.publishTime];
|
||||
|
||||
// 配置内容
|
||||
|
||||
self.contentLabel.text = model.content ?: @"";
|
||||
|
||||
// 配置图片九宫格
|
||||
|
||||
[self renderImages:model.dynamicResList];
|
||||
|
||||
// 配置点赞按钮状态和数字
|
||||
|
||||
NSInteger likeCnt = MAX(0, model.likeCount.integerValue);
|
||||
self.likeButton.selected = model.isLike;
|
||||
[self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCnt] forState:UIControlStateNormal];
|
||||
@@ -181,16 +177,16 @@
|
||||
|
||||
self.avatarImageView.imageUrl = model.avatar;
|
||||
|
||||
// 配置情绪颜色 border 和 shadow
|
||||
|
||||
[self applyEmotionColorEffect:model.emotionColor];
|
||||
|
||||
// 确保布局完成后 cell 高度正确
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/// 应用情绪颜色视觉效果(Background + Shadow)
|
||||
|
||||
- (void)applyEmotionColorEffect:(NSString *)emotionColorHex {
|
||||
// 获取颜色(已在列表加载时处理,这里直接使用)
|
||||
|
||||
if (!emotionColorHex) {
|
||||
NSLog(@"[EPMomentCell] 警告:emotionColorHex 为 nil");
|
||||
return;
|
||||
@@ -198,24 +194,24 @@
|
||||
|
||||
UIColor *color = [self colorFromHex:emotionColorHex];
|
||||
|
||||
// 移除边框
|
||||
|
||||
self.cardView.layer.borderWidth = 0;
|
||||
|
||||
// 设置彩色背景(50% 透明度,在毛玻璃下方)
|
||||
|
||||
self.colorBackgroundView.backgroundColor = [color colorWithAlphaComponent:0.5];
|
||||
|
||||
// 设置 shadow(使用情绪颜色)
|
||||
|
||||
self.cardView.layer.shadowColor = color.CGColor;
|
||||
self.cardView.layer.shadowOffset = CGSizeMake(0, 2);
|
||||
self.cardView.layer.shadowOpacity = 0.5;
|
||||
self.cardView.layer.shadowRadius = 16.0;
|
||||
}
|
||||
|
||||
/// Hex 转 UIColor
|
||||
|
||||
- (UIColor *)colorFromHex:(NSString *)hexString {
|
||||
unsigned rgbValue = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
[scanner setScanLocation:1]; // 跳过 #
|
||||
[scanner setScanLocation:1];
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0
|
||||
green:((rgbValue & 0xFF00) >> 8)/255.0
|
||||
@@ -226,7 +222,7 @@
|
||||
// MARK: - Images Grid
|
||||
|
||||
- (void)renderImages:(NSArray *)resList {
|
||||
// 清理旧视图
|
||||
|
||||
for (UIView *iv in self.imageViews) { [iv removeFromSuperview]; }
|
||||
[self.imageViews removeAllObjects];
|
||||
if (resList.count == 0) {
|
||||
@@ -236,14 +232,14 @@
|
||||
make.height.mas_equalTo(0);
|
||||
}];
|
||||
|
||||
// 强制触发布局更新
|
||||
|
||||
[self.contentView setNeedsLayout];
|
||||
[self.contentView layoutIfNeeded];
|
||||
return;
|
||||
}
|
||||
NSInteger columns = 3;
|
||||
CGFloat spacing = 6.0;
|
||||
CGFloat totalWidth = [UIScreen mainScreen].bounds.size.width - 30 - 30; // 左右各 15 内边距,再减卡片左右 15
|
||||
CGFloat totalWidth = [UIScreen mainScreen].bounds.size.width - 30 - 30;
|
||||
CGFloat itemW = floor((totalWidth - spacing * (columns - 1)) / columns);
|
||||
|
||||
for (NSInteger i = 0; i < resList.count && i < 9; i++) {
|
||||
@@ -255,9 +251,9 @@
|
||||
iv.layer.masksToBounds = YES;
|
||||
iv.contentMode = UIViewContentModeScaleAspectFill;
|
||||
iv.userInteractionEnabled = YES;
|
||||
iv.tag = i; // 用于识别点击的图片索引
|
||||
iv.tag = i;
|
||||
|
||||
|
||||
// 添加点击手势
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageTapped:)];
|
||||
[iv addGestureRecognizer:tap];
|
||||
|
||||
@@ -270,7 +266,7 @@
|
||||
make.top.equalTo(self.imagesContainer).offset((itemW + spacing) * row);
|
||||
make.size.mas_equalTo(CGSizeMake(itemW, itemW));
|
||||
}];
|
||||
// 绑定网络图片
|
||||
|
||||
NSString *url = nil;
|
||||
id item = resList[i];
|
||||
if ([item isKindOfClass:[NSDictionary class]]) {
|
||||
@@ -289,18 +285,18 @@
|
||||
make.height.mas_equalTo(height);
|
||||
}];
|
||||
|
||||
// 强制触发布局更新,确保 cell 高度正确计算
|
||||
|
||||
[self.contentView setNeedsLayout];
|
||||
[self.contentView layoutIfNeeded];
|
||||
}
|
||||
|
||||
/// 格式化时间戳为 MM/dd 格式
|
||||
|
||||
- (NSString *)formatTimestampToDate:(NSString *)timestampString {
|
||||
if (!timestampString || timestampString.length == 0) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
// 将字符串转换为时间戳(毫秒)
|
||||
|
||||
NSTimeInterval timestamp = [timestampString doubleValue] / 1000.0;
|
||||
|
||||
if (timestamp <= 0) {
|
||||
@@ -314,7 +310,7 @@
|
||||
return [formatter stringFromDate:date];
|
||||
}
|
||||
|
||||
/// 格式化时间戳为相对时间
|
||||
|
||||
- (NSString *)formatTimeInterval:(NSInteger)timestamp {
|
||||
if (timestamp <= 0) return YMLocalizedString(@"time.just_now");
|
||||
|
||||
@@ -340,20 +336,20 @@
|
||||
- (void)onLikeButtonTapped {
|
||||
if (!self.currentModel) return;
|
||||
|
||||
// 如果已点赞,执行取消点赞
|
||||
|
||||
if (self.currentModel.isLike) {
|
||||
[self performLikeAction:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
// 审核中的动态不可点赞
|
||||
|
||||
if (self.currentModel.status == 0) {
|
||||
NSLog(@"[EPMomentCell] 动态审核中,无法点赞");
|
||||
// TODO: 可选择显示提示 Toast
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行点赞
|
||||
|
||||
[self performLikeAction:YES];
|
||||
}
|
||||
|
||||
@@ -364,7 +360,7 @@
|
||||
NSString *likedUid = self.currentModel.uid;
|
||||
long worldId = self.currentModel.worldId;
|
||||
|
||||
// 使用 Swift API Helper
|
||||
|
||||
@kWeakify(self);
|
||||
[self.apiHelper likeMomentWithDynamicId:dynamicId
|
||||
isLike:isLike
|
||||
@@ -372,14 +368,14 @@
|
||||
worldId:worldId
|
||||
completion:^{
|
||||
@kStrongify(self);
|
||||
// 更新点赞状态
|
||||
|
||||
self.currentModel.isLike = isLike;
|
||||
NSInteger likeCount = [self.currentModel.likeCount integerValue];
|
||||
likeCount += isLike ? 1 : -1;
|
||||
likeCount = MAX(0, likeCount); // 防止负数
|
||||
likeCount = MAX(0, likeCount);
|
||||
self.currentModel.likeCount = @(likeCount).stringValue;
|
||||
|
||||
// 更新 UI
|
||||
|
||||
self.likeButton.selected = self.currentModel.isLike;
|
||||
[self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCount] forState:UIControlStateNormal];
|
||||
[self.likeButton setTitle:[NSString stringWithFormat:@" %ld", (long)likeCount] forState:UIControlStateSelected];
|
||||
@@ -390,10 +386,6 @@
|
||||
}];
|
||||
}
|
||||
|
||||
// 评论功能已隐藏
|
||||
// - (void)onCommentButtonTapped {
|
||||
// NSLog(@"[EPMomentCell] 评论");
|
||||
// }
|
||||
|
||||
- (void)onImageTapped:(UITapGestureRecognizer *)gesture {
|
||||
if (!self.currentModel || !self.currentModel.dynamicResList.count) return;
|
||||
@@ -436,9 +428,9 @@
|
||||
- (UIView *)cardView {
|
||||
if (!_cardView) {
|
||||
_cardView = [[UIView alloc] init];
|
||||
_cardView.backgroundColor = [UIColor clearColor]; // 透明背景,颜色由 colorBackgroundView 提供
|
||||
_cardView.layer.cornerRadius = 12; // 圆角
|
||||
// Shadow 将由 applyEmotionColorEffect 动态设置
|
||||
_cardView.backgroundColor = [UIColor clearColor];
|
||||
_cardView.layer.cornerRadius = 12;
|
||||
|
||||
_cardView.layer.masksToBounds = NO;
|
||||
}
|
||||
return _cardView;
|
||||
@@ -507,7 +499,7 @@
|
||||
- (UIView *)actionBar {
|
||||
if (!_actionBar) {
|
||||
_actionBar = [[UIView alloc] init];
|
||||
_actionBar.backgroundColor = [UIColor clearColor]; // 半透明白色,与毛玻璃效果搭配
|
||||
_actionBar.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return _actionBar;
|
||||
}
|
||||
@@ -526,7 +518,7 @@
|
||||
return _likeButton;
|
||||
}
|
||||
|
||||
// 评论按钮已移除
|
||||
|
||||
- (UIButton *)commentButton {
|
||||
return nil;
|
||||
}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPMomentListView.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-10.
|
||||
//
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "EPMomentListView.h"
|
||||
@@ -34,7 +32,7 @@
|
||||
_api = [[EPMomentAPISwiftHelper alloc] init];
|
||||
_mutableRawList = [NSMutableArray array];
|
||||
_sourceType = EPMomentListSourceTypeRecommend;
|
||||
_isLocalMode = NO; // 明确初始化为网络模式
|
||||
_isLocalMode = NO;
|
||||
|
||||
[self addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -49,8 +47,10 @@
|
||||
}
|
||||
|
||||
- (void)reloadFirstPage {
|
||||
NSLog(@"[EPMomentListView] 📄 开始刷新第一页,isLocalMode=%d", self.isLocalMode);
|
||||
|
||||
if (self.isLocalMode) {
|
||||
// 本地模式:调用外部刷新回调
|
||||
|
||||
if (self.refreshCallback) {
|
||||
self.refreshCallback();
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 网络模式:重新请求第一页
|
||||
|
||||
self.nextID = @"";
|
||||
[self.mutableRawList removeAllObjects];
|
||||
[self.tableView reloadData];
|
||||
@@ -76,7 +76,7 @@
|
||||
[self.mutableRawList addObjectsFromArray:dynamicInfo];
|
||||
}
|
||||
|
||||
// 隐藏加载更多 footer
|
||||
|
||||
self.tableView.mj_footer.hidden = YES;
|
||||
|
||||
[self.tableView reloadData];
|
||||
@@ -84,20 +84,27 @@
|
||||
}
|
||||
|
||||
- (void)requestNextPage {
|
||||
if (self.isLoading) return;
|
||||
if (self.isLoading) {
|
||||
NSLog(@"[EPMomentListView] ⚠️ 已有加载任务进行中,跳过本次请求");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[EPMomentListView] 🌐 发起网络请求,nextID=%@", self.nextID.length > 0 ? self.nextID : @"(首页)");
|
||||
self.isLoading = YES;
|
||||
|
||||
@kWeakify(self);
|
||||
[self.api fetchLatestMomentsWithNextID:self.nextID
|
||||
completion:^(NSArray<MomentsInfoModel *> * _Nonnull list, NSString * _Nonnull nextMomentID) {
|
||||
@kStrongify(self);
|
||||
NSLog(@"[EPMomentListView] ✅ 请求成功,获得 %lu 条数据", (unsigned long)list.count);
|
||||
[self endLoading];
|
||||
if (list.count > 0) {
|
||||
// 处理情绪颜色
|
||||
|
||||
[self processEmotionColors:list isFirstPage:(self.nextID.length == 0)];
|
||||
|
||||
self.nextID = nextMomentID;
|
||||
[self.mutableRawList addObjectsFromArray:list];
|
||||
[self removeEmptyState];
|
||||
[self.tableView reloadData];
|
||||
if (nextMomentID.length > 0) {
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
@@ -105,13 +112,21 @@
|
||||
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||
}
|
||||
} else {
|
||||
// 返回空数据:显示 "no more data" 状态
|
||||
NSLog(@"[EPMomentListView] ⚠️ 返回数据为空");
|
||||
if (self.mutableRawList.count == 0) {
|
||||
[self showEmptyStateWithMessage:YMLocalizedString(@"common.no_data")];
|
||||
}
|
||||
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||
}
|
||||
} failure:^(NSInteger code, NSString * _Nonnull msg) {
|
||||
@kStrongify(self);
|
||||
NSLog(@"[EPMomentListView] ❌ 请求失败,code=%ld, msg=%@", (long)code, msg);
|
||||
[self endLoading];
|
||||
// TODO: 完全没有数据情况下,后续补充数据异常页面
|
||||
|
||||
|
||||
if (self.mutableRawList.count == 0) {
|
||||
[self showEmptyStateWithMessage:msg ?: YMLocalizedString(@"error.request_failed")];
|
||||
}
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
}];
|
||||
}
|
||||
@@ -121,25 +136,48 @@
|
||||
[self.refreshControl endRefreshing];
|
||||
}
|
||||
|
||||
/// 处理动态的情绪颜色(从 UserDefaults 匹配 + 处理临时颜色)
|
||||
|
||||
- (void)showEmptyStateWithMessage:(NSString *)message {
|
||||
UILabel *emptyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
emptyLabel.text = [NSString stringWithFormat:@"%@\n\n%@", message, YMLocalizedString(@"common.pull_to_retry")];
|
||||
emptyLabel.textColor = [UIColor whiteColor];
|
||||
emptyLabel.textAlignment = NSTextAlignmentCenter;
|
||||
emptyLabel.numberOfLines = 0;
|
||||
emptyLabel.font = [UIFont systemFontOfSize:15];
|
||||
emptyLabel.tag = 9999;
|
||||
|
||||
|
||||
[[self.tableView viewWithTag:9999] removeFromSuperview];
|
||||
[self.tableView addSubview:emptyLabel];
|
||||
[emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self.tableView);
|
||||
make.leading.trailing.equalTo(self.tableView).inset(40);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeEmptyState {
|
||||
[[self.tableView viewWithTag:9999] removeFromSuperview];
|
||||
}
|
||||
|
||||
|
||||
- (void)processEmotionColors:(NSArray<MomentsInfoModel *> *)list isFirstPage:(BOOL)isFirstPage {
|
||||
// 检查是否有待处理的临时情绪颜色
|
||||
|
||||
NSString *pendingColor = [[NSUserDefaults standardUserDefaults] stringForKey:@"EP_Pending_Emotion_Color"];
|
||||
NSNumber *pendingTimestamp = [[NSUserDefaults standardUserDefaults] objectForKey:@"EP_Pending_Emotion_Timestamp"];
|
||||
|
||||
for (NSInteger i = 0; i < list.count; i++) {
|
||||
MomentsInfoModel *model = list[i];
|
||||
|
||||
// 优先检查临时颜色(仅第一页第一条)
|
||||
|
||||
if (isFirstPage && i == 0 && pendingColor && pendingTimestamp) {
|
||||
// 检查时间戳(5秒内有效,避免误匹配)
|
||||
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
||||
NSTimeInterval pending = pendingTimestamp.doubleValue;
|
||||
if ((now - pending) < 5.0) {
|
||||
model.emotionColor = pendingColor;
|
||||
// 保存到持久化存储
|
||||
|
||||
[EPEmotionColorStorage saveColor:pendingColor forDynamicId:model.dynamicId];
|
||||
// 清除临时数据
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"EP_Pending_Emotion_Color"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"EP_Pending_Emotion_Timestamp"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
@@ -147,12 +185,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 从持久化存储中匹配
|
||||
|
||||
NSString *savedColor = [EPEmotionColorStorage colorForDynamicId:model.dynamicId];
|
||||
if (savedColor) {
|
||||
model.emotionColor = savedColor;
|
||||
} else {
|
||||
// 无保存颜色,生成随机颜色并立即持久化
|
||||
|
||||
NSString *randomColor = [EPEmotionColorStorage randomEmotionColor];
|
||||
model.emotionColor = randomColor;
|
||||
[EPEmotionColorStorage saveColor:randomColor forDynamicId:model.dynamicId];
|
||||
@@ -190,7 +228,7 @@
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
// 本地模式下不触发加载更多
|
||||
|
||||
if (self.isLocalMode) return;
|
||||
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
@@ -213,13 +251,13 @@
|
||||
_tableView.estimatedRowHeight = 200;
|
||||
_tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
_tableView.showsVerticalScrollIndicator = NO;
|
||||
// 底部留出更高空间,避免被悬浮 TabBar 遮挡
|
||||
|
||||
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 120, 0);
|
||||
_tableView.scrollIndicatorInsets = UIEdgeInsetsMake(10, 0, 120, 0);
|
||||
[_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
|
||||
_tableView.refreshControl = self.refreshControl;
|
||||
|
||||
// MJRefresh Footer - 加载更多
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
@@ -231,7 +269,7 @@
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
}
|
||||
}];
|
||||
// 设置白色文字和指示器
|
||||
|
||||
footer.stateLabel.textColor = [UIColor whiteColor];
|
||||
footer.loadingView.color = [UIColor whiteColor];
|
||||
_tableView.mj_footer = footer;
|
||||
@@ -242,12 +280,10 @@
|
||||
- (UIRefreshControl *)refreshControl {
|
||||
if (!_refreshControl) {
|
||||
_refreshControl = [[UIRefreshControl alloc] init];
|
||||
_refreshControl.tintColor = [UIColor whiteColor]; // 白色加载指示器
|
||||
_refreshControl.tintColor = [UIColor whiteColor];
|
||||
[_refreshControl addTarget:self action:@selector(reloadFirstPage) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
return _refreshControl;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// EPSignatureColorGuideView.m
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-15.
|
||||
//
|
||||
|
||||
|
||||
#import "EPSignatureColorGuideView.h"
|
||||
#import "EPEmotionColorWheelView.h"
|
||||
@@ -22,7 +20,7 @@
|
||||
@property (nonatomic, strong) EPEmotionColorWheelView *colorWheelView;
|
||||
@property (nonatomic, strong) UIButton *confirmButton;
|
||||
@property (nonatomic, strong) UIButton *skipButton;
|
||||
@property (nonatomic, copy) NSString *selectedColor; // 当前选中的颜色
|
||||
@property (nonatomic, copy) NSString *selectedColor;
|
||||
|
||||
@end
|
||||
|
||||
@@ -38,7 +36,7 @@
|
||||
}
|
||||
|
||||
- (void)setupUI {
|
||||
// 渐变背景
|
||||
|
||||
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
|
||||
gradientLayer.colors = @[
|
||||
(id)[UIColor colorWithRed:0x1a/255.0 green:0x09/255.0 blue:0x33/255.0 alpha:1.0].CGColor,
|
||||
@@ -49,29 +47,30 @@
|
||||
[self.layer insertSublayer:gradientLayer atIndex:0];
|
||||
self.gradientLayer = gradientLayer;
|
||||
|
||||
// 内容容器
|
||||
|
||||
[self addSubview:self.contentContainer];
|
||||
[self.contentContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self);
|
||||
make.top.equalTo(self).offset(100);
|
||||
make.leading.trailing.equalTo(self).inset(30);
|
||||
make.bottom.lessThanOrEqualTo(self).offset(-30);
|
||||
}];
|
||||
|
||||
// 标题
|
||||
|
||||
[self.contentContainer addSubview:self.titleLabel];
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contentContainer);
|
||||
make.centerX.equalTo(self.contentContainer);
|
||||
}];
|
||||
|
||||
// 副标题
|
||||
|
||||
[self.contentContainer addSubview:self.subtitleLabel];
|
||||
[self.subtitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.titleLabel.mas_bottom).offset(12);
|
||||
make.top.equalTo(self.titleLabel.mas_bottom).offset(8);
|
||||
make.centerX.equalTo(self.contentContainer);
|
||||
make.leading.trailing.equalTo(self.contentContainer).inset(20);
|
||||
}];
|
||||
|
||||
// Info 按钮(左上角,与 Skip 按钮对齐)
|
||||
|
||||
[self addSubview:self.infoButton];
|
||||
[self.infoButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(self).offset(20);
|
||||
@@ -79,33 +78,34 @@
|
||||
make.size.mas_equalTo(CGSizeMake(36, 36));
|
||||
}];
|
||||
|
||||
// 选中状态显示区域
|
||||
|
||||
[self.contentContainer addSubview:self.selectedColorView];
|
||||
[self.selectedColorView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.subtitleLabel.mas_bottom).offset(30);
|
||||
make.top.equalTo(self.subtitleLabel.mas_bottom).offset(20);
|
||||
make.centerX.equalTo(self.contentContainer);
|
||||
make.height.mas_equalTo(60);
|
||||
make.leading.trailing.equalTo(self.contentContainer).inset(40);
|
||||
}];
|
||||
|
||||
// 色轮视图(使用共享组件)
|
||||
|
||||
[self.contentContainer addSubview:self.colorWheelView];
|
||||
[self.colorWheelView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.selectedColorView.mas_bottom).offset(30);
|
||||
make.top.equalTo(self.selectedColorView.mas_bottom).offset(20);
|
||||
make.centerX.equalTo(self.contentContainer);
|
||||
make.size.mas_equalTo(CGSizeMake(360, 360)); // 从280x280增加到360x360
|
||||
CGFloat wheelSize = MIN(300, [UIScreen mainScreen].bounds.size.width - 80);
|
||||
make.size.mas_equalTo(CGSizeMake(wheelSize, wheelSize));
|
||||
}];
|
||||
|
||||
// 确认按钮
|
||||
|
||||
[self.contentContainer addSubview:self.confirmButton];
|
||||
[self.confirmButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.colorWheelView.mas_bottom).offset(50);
|
||||
make.top.equalTo(self.colorWheelView.mas_bottom).offset(30);
|
||||
make.leading.trailing.equalTo(self.contentContainer).inset(20);
|
||||
make.height.mas_equalTo(56);
|
||||
make.bottom.equalTo(self.contentContainer);
|
||||
}];
|
||||
|
||||
// Skip 按钮(右上角,初始隐藏)
|
||||
|
||||
[self addSubview:self.skipButton];
|
||||
[self.skipButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self).offset(60);
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
// 更新渐变层 frame
|
||||
|
||||
self.gradientLayer.frame = self.bounds;
|
||||
}
|
||||
|
||||
@@ -125,22 +125,22 @@
|
||||
- (void)onConfirmButtonTapped {
|
||||
if (!self.selectedColor) return;
|
||||
|
||||
// 执行回调
|
||||
|
||||
if (self.onColorConfirmed) {
|
||||
self.onColorConfirmed(self.selectedColor);
|
||||
}
|
||||
|
||||
// 关闭引导页
|
||||
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)onSkipButtonTapped {
|
||||
// 执行 skip 回调
|
||||
|
||||
if (self.onSkipTapped) {
|
||||
self.onSkipTapped();
|
||||
}
|
||||
|
||||
// 关闭引导页
|
||||
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
@@ -161,14 +161,14 @@
|
||||
make.edges.equalTo(window);
|
||||
}];
|
||||
|
||||
// 控制 Skip 按钮显示
|
||||
|
||||
self.skipButton.hidden = !showSkip;
|
||||
|
||||
// 初始状态
|
||||
|
||||
self.alpha = 0;
|
||||
self.contentContainer.transform = CGAffineTransformMakeScale(0.8, 0.8);
|
||||
|
||||
// 显示动画
|
||||
|
||||
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
self.alpha = 1.0;
|
||||
self.contentContainer.transform = CGAffineTransformIdentity;
|
||||
@@ -223,11 +223,11 @@
|
||||
_selectedColorView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.15];
|
||||
_selectedColorView.layer.cornerRadius = 30;
|
||||
_selectedColorView.layer.masksToBounds = YES;
|
||||
_selectedColorView.hidden = YES; // 初始隐藏
|
||||
_selectedColorView.hidden = YES;
|
||||
|
||||
|
||||
// 颜色圆点
|
||||
UIView *colorDot = [[UIView alloc] init];
|
||||
colorDot.tag = 100; // 用于后续查找
|
||||
colorDot.tag = 100;
|
||||
colorDot.layer.cornerRadius = 16;
|
||||
colorDot.layer.masksToBounds = YES;
|
||||
[_selectedColorView addSubview:colorDot];
|
||||
@@ -237,7 +237,7 @@
|
||||
make.size.mas_equalTo(CGSizeMake(32, 32));
|
||||
}];
|
||||
|
||||
// 情绪名称标签
|
||||
|
||||
[_selectedColorView addSubview:self.selectedColorLabel];
|
||||
[self.selectedColorLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.equalTo(colorDot.mas_trailing).offset(16);
|
||||
@@ -261,20 +261,21 @@
|
||||
- (EPEmotionColorWheelView *)colorWheelView {
|
||||
if (!_colorWheelView) {
|
||||
_colorWheelView = [[EPEmotionColorWheelView alloc] init];
|
||||
_colorWheelView.radius = 100.0;
|
||||
_colorWheelView.buttonSize = 54.0;
|
||||
CGFloat wheelSize = MIN(300, [UIScreen mainScreen].bounds.size.width - 80);
|
||||
_colorWheelView.radius = wheelSize / 3.0;
|
||||
_colorWheelView.buttonSize = 48.0;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_colorWheelView.onColorTapped = ^(NSString *hexColor, NSInteger index) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
|
||||
// 保存选中的颜色
|
||||
|
||||
self.selectedColor = hexColor;
|
||||
|
||||
// 更新选中状态显示
|
||||
|
||||
[self updateSelectedColorDisplay:hexColor index:index];
|
||||
|
||||
// 启用确认按钮
|
||||
|
||||
self.confirmButton.enabled = YES;
|
||||
self.confirmButton.alpha = 1.0;
|
||||
};
|
||||
@@ -282,26 +283,26 @@
|
||||
return _colorWheelView;
|
||||
}
|
||||
|
||||
/// 更新选中颜色显示
|
||||
|
||||
- (void)updateSelectedColorDisplay:(NSString *)hexColor index:(NSInteger)index {
|
||||
NSArray<NSString *> *emotions = @[@"Joy", @"Sadness", @"Anger", @"Fear", @"Surprise", @"Disgust", @"Trust", @"Anticipation"];
|
||||
|
||||
// 显示选中状态区域
|
||||
|
||||
self.selectedColorView.hidden = NO;
|
||||
|
||||
// 更新颜色圆点
|
||||
|
||||
UIView *colorDot = [self.selectedColorView viewWithTag:100];
|
||||
colorDot.backgroundColor = [self colorFromHex:hexColor];
|
||||
|
||||
// 更新情绪名称
|
||||
|
||||
self.selectedColorLabel.text = emotions[index];
|
||||
}
|
||||
|
||||
/// Hex 转 UIColor
|
||||
|
||||
- (UIColor *)colorFromHex:(NSString *)hexString {
|
||||
unsigned rgbValue = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
[scanner setScanLocation:1]; // 跳过 #
|
||||
[scanner setScanLocation:1];
|
||||
[scanner scanHexInt:&rgbValue];
|
||||
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0
|
||||
green:((rgbValue & 0xFF00) >> 8)/255.0
|
||||
@@ -318,7 +319,7 @@
|
||||
_confirmButton.layer.cornerRadius = 28;
|
||||
_confirmButton.layer.masksToBounds = YES;
|
||||
|
||||
// 渐变背景
|
||||
|
||||
CAGradientLayer *gradient = [CAGradientLayer layer];
|
||||
gradient.colors = @[
|
||||
(id)[UIColor colorWithRed:0x9B/255.0 green:0x59/255.0 blue:0xB6/255.0 alpha:1.0].CGColor,
|
||||
@@ -326,12 +327,12 @@
|
||||
];
|
||||
gradient.startPoint = CGPointMake(0, 0);
|
||||
gradient.endPoint = CGPointMake(1, 0);
|
||||
gradient.frame = CGRectMake(0, 0, 1000, 56); // 宽度设大一点
|
||||
gradient.frame = CGRectMake(0, 0, 1000, 56);
|
||||
[_confirmButton.layer insertSublayer:gradient atIndex:0];
|
||||
|
||||
[_confirmButton addTarget:self action:@selector(onConfirmButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
// 初始禁用状态
|
||||
|
||||
_confirmButton.enabled = NO;
|
||||
_confirmButton.alpha = 0.5;
|
||||
}
|
||||
@@ -342,12 +343,12 @@
|
||||
if (!_infoButton) {
|
||||
_infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
|
||||
// 使用系统 info.circle 图标
|
||||
|
||||
UIImage *infoIcon = [UIImage systemImageNamed:@"info.circle"];
|
||||
[_infoButton setImage:infoIcon forState:UIControlStateNormal];
|
||||
_infoButton.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
|
||||
|
||||
// 点击效果
|
||||
|
||||
[_infoButton addTarget:self action:@selector(onInfoButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _infoButton;
|
||||
@@ -363,10 +364,9 @@
|
||||
_skipButton.layer.cornerRadius = 18;
|
||||
_skipButton.layer.masksToBounds = YES;
|
||||
[_skipButton addTarget:self action:@selector(onSkipButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
_skipButton.hidden = YES; // 默认隐藏
|
||||
_skipButton.hidden = YES;
|
||||
}
|
||||
return _skipButton;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -1,33 +1,27 @@
|
||||
//
|
||||
// EPTabBarController.swift
|
||||
// YuMi
|
||||
//
|
||||
|
||||
|
||||
// Created by AI on 2025-10-09.
|
||||
// Copyright © 2025 YuMi. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
/// EP 系列 TabBar 控制器
|
||||
/// 悬浮设计 + 液态玻璃效果,只包含 Moment 和 Mine 两个 Tab
|
||||
|
||||
@objc class EPTabBarController: UITabBarController {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// 全局事件管理器
|
||||
private var globalEventManager: GlobalEventManager?
|
||||
|
||||
/// 是否已登录
|
||||
private var isLoggedIn: Bool = false
|
||||
|
||||
/// 自定义悬浮 TabBar 容器
|
||||
|
||||
private var customTabBarView: UIView!
|
||||
|
||||
/// 毛玻璃背景视图
|
||||
|
||||
private var tabBarBackgroundView: UIVisualEffectView!
|
||||
|
||||
/// Tab 按钮数组
|
||||
|
||||
private var tabButtons: [UIButton] = []
|
||||
|
||||
// MARK: - Lifecycle
|
||||
@@ -35,49 +29,47 @@ import SnapKit
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// 测试域名配置
|
||||
|
||||
#if DEBUG
|
||||
APIConfig.testEncryption()
|
||||
#endif
|
||||
|
||||
// 隐藏原生 TabBar
|
||||
|
||||
self.tabBar.isHidden = true
|
||||
|
||||
// 设置 delegate 以完全控制切换行为
|
||||
|
||||
self.delegate = self
|
||||
|
||||
// ✅ 启动时验证 ticket(与 OC 版本保持一致)
|
||||
|
||||
performAutoLogin()
|
||||
|
||||
setupCustomFloatingTabBar()
|
||||
setupGlobalManagers()
|
||||
setupInitialViewControllers()
|
||||
|
||||
NSLog("[EPTabBarController] 悬浮 TabBar 初始化完成")
|
||||
}
|
||||
|
||||
deinit {
|
||||
globalEventManager?.removeAllDelegates()
|
||||
NSLog("[EPTabBarController] 已释放")
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// 设置自定义悬浮 TabBar
|
||||
|
||||
private func setupCustomFloatingTabBar() {
|
||||
// 创建悬浮容器
|
||||
|
||||
customTabBarView = UIView()
|
||||
customTabBarView.translatesAutoresizingMaskIntoConstraints = false
|
||||
customTabBarView.backgroundColor = .clear
|
||||
view.addSubview(customTabBarView)
|
||||
|
||||
// 液态玻璃/毛玻璃效果
|
||||
|
||||
let effect: UIVisualEffect
|
||||
if #available(iOS 26.0, *) {
|
||||
// iOS 26+ 使用液态玻璃(Material)
|
||||
|
||||
effect = UIGlassEffect()
|
||||
} else {
|
||||
// iOS 13-17 使用毛玻璃
|
||||
|
||||
effect = UIBlurEffect(style: .systemMaterial)
|
||||
}
|
||||
|
||||
@@ -86,13 +78,13 @@ import SnapKit
|
||||
tabBarBackgroundView.layer.cornerRadius = 28
|
||||
tabBarBackgroundView.layer.masksToBounds = true
|
||||
|
||||
// 添加边框
|
||||
|
||||
tabBarBackgroundView.layer.borderWidth = 0.5
|
||||
tabBarBackgroundView.layer.borderColor = UIColor.white.withAlphaComponent(0.2).cgColor
|
||||
|
||||
customTabBarView.addSubview(tabBarBackgroundView)
|
||||
|
||||
// 简化的布局约束(类似 Masonry 风格)
|
||||
|
||||
customTabBarView.snp.makeConstraints { make in
|
||||
make.leading.equalTo(view).offset(16)
|
||||
make.trailing.equalTo(view).offset(-16)
|
||||
@@ -104,20 +96,20 @@ import SnapKit
|
||||
make.edges.equalTo(customTabBarView)
|
||||
}
|
||||
|
||||
// 添加 Tab 按钮
|
||||
|
||||
setupTabButtons()
|
||||
|
||||
NSLog("[EPTabBarController] 悬浮 TabBar 设置完成")
|
||||
}
|
||||
|
||||
/// 设置 Tab 按钮
|
||||
|
||||
private func setupTabButtons() {
|
||||
let momentButton = createTabButton(
|
||||
normalImage: "tab_moment_off",
|
||||
selectedImage: "tab_moment_on",
|
||||
tag: 0
|
||||
)
|
||||
// 新增中间 Message 按钮
|
||||
|
||||
let messageButton = createTabButton(
|
||||
normalImage: "tab_message_off",
|
||||
selectedImage: "tab_message_on",
|
||||
@@ -146,23 +138,23 @@ import SnapKit
|
||||
make.bottom.equalTo(tabBarBackgroundView).offset(-8)
|
||||
}
|
||||
|
||||
// 默认选中第一个
|
||||
|
||||
updateTabButtonStates(selectedIndex: 0)
|
||||
}
|
||||
|
||||
/// 创建 Tab 按钮
|
||||
|
||||
private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton {
|
||||
let button = UIButton(type: .custom)
|
||||
button.tag = tag
|
||||
button.adjustsImageWhenHighlighted = false // 禁用高亮效果,避免闪烁
|
||||
button.adjustsImageWhenHighlighted = false
|
||||
|
||||
|
||||
// 尝试设置自定义图片,如果不存在则使用 SF Symbols
|
||||
if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) {
|
||||
// 正确设置:分别为 normal 和 selected 状态设置图片
|
||||
|
||||
button.setImage(normalImg, for: .normal)
|
||||
button.setImage(selectedImg, for: .selected)
|
||||
} else {
|
||||
// 使用 SF Symbols 作为备用
|
||||
|
||||
let fallbackIcons = ["sparkles", "person.circle"]
|
||||
let iconName = fallbackIcons[tag]
|
||||
let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
|
||||
@@ -173,14 +165,14 @@ import SnapKit
|
||||
button.tintColor = .white.withAlphaComponent(0.6)
|
||||
}
|
||||
|
||||
// 图片渲染模式
|
||||
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
|
||||
// 移除标题
|
||||
|
||||
button.setTitle(nil, for: .normal)
|
||||
button.setTitle(nil, for: .selected)
|
||||
|
||||
// 设置图片大小约束
|
||||
|
||||
button.imageView?.snp.makeConstraints { make in
|
||||
make.size.equalTo(28)
|
||||
}
|
||||
@@ -189,105 +181,90 @@ import SnapKit
|
||||
return button
|
||||
}
|
||||
|
||||
/// Tab 按钮点击事件
|
||||
|
||||
@objc private func tabButtonTapped(_ sender: UIButton) {
|
||||
let newIndex = sender.tag
|
||||
|
||||
// 如果点击的是当前已选中的 tab,不做任何操作
|
||||
|
||||
if newIndex == selectedIndex {
|
||||
return
|
||||
}
|
||||
|
||||
// 先更新按钮状态
|
||||
|
||||
updateTabButtonStates(selectedIndex: newIndex)
|
||||
|
||||
// 禁用 UITabBarController 的默认切换动画,避免闪烁
|
||||
|
||||
UIView.performWithoutAnimation {
|
||||
selectedIndex = newIndex
|
||||
}
|
||||
|
||||
let tabNames = [
|
||||
YMLocalizedString("tab.moment"),
|
||||
YMLocalizedString("tab.message"),
|
||||
YMLocalizedString("tab.mine")
|
||||
]
|
||||
let tabNames = [YMLocalizedString("tab.moment"),
|
||||
YMLocalizedString("tab.message"),
|
||||
YMLocalizedString("tab.mine")]
|
||||
NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])")
|
||||
}
|
||||
|
||||
/// 更新 Tab 按钮状态
|
||||
|
||||
private func updateTabButtonStates(selectedIndex: Int) {
|
||||
// 禁用按钮交互,避免快速点击
|
||||
|
||||
tabButtons.forEach { $0.isUserInteractionEnabled = false }
|
||||
|
||||
for (index, button) in tabButtons.enumerated() {
|
||||
let isSelected = (index == selectedIndex)
|
||||
|
||||
// 直接设置 isSelected 属性即可,图片会自动切换
|
||||
|
||||
button.isSelected = isSelected
|
||||
|
||||
// SF Symbols 的情况需要手动更新 tintColor
|
||||
|
||||
if button.currentImage?.isSymbolImage == true {
|
||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
||||
}
|
||||
|
||||
// 选中状态缩放动画
|
||||
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut], animations: {
|
||||
button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity
|
||||
})
|
||||
}
|
||||
|
||||
// 延迟恢复按钮交互
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||
self.tabButtons.forEach { $0.isUserInteractionEnabled = true }
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置全局管理器
|
||||
private func setupGlobalManagers() {
|
||||
globalEventManager = GlobalEventManager.shared()
|
||||
globalEventManager?.setupSDKDelegates()
|
||||
|
||||
// TODO: v0.2 版本暂时禁用房间最小化视图(无房间功能)
|
||||
// 后续版本可通过 Build Configuration 或版本号判断是否启用
|
||||
/*
|
||||
if let containerView = view {
|
||||
globalEventManager?.setupRoomMiniView(on: containerView)
|
||||
}
|
||||
*/
|
||||
|
||||
// 注册社交分享回调
|
||||
globalEventManager?.registerSocialShareCallback()
|
||||
|
||||
NSLog("[EPTabBarController] 全局管理器设置完成(v0.2 - 无 MiniRoom)")
|
||||
}
|
||||
|
||||
/// 设置初始 ViewController(未登录状态)
|
||||
private func setupInitialViewControllers() {
|
||||
// 三栏占位(Moment | Message | Mine)
|
||||
let v1 = UINavigationController(rootViewController: UIViewController())
|
||||
v1.view.backgroundColor = .white
|
||||
v1.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.moment"), normalImage: "tab_moment_normal", selectedImage: "tab_moment_selected")
|
||||
let momentVC = UIViewController()
|
||||
momentVC.view.backgroundColor = .systemBlue
|
||||
momentVC.title = "Moment"
|
||||
let momentNav = UINavigationController(rootViewController: momentVC)
|
||||
momentNav.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.moment"), normalImage: "tab_moment_normal", selectedImage: "tab_moment_selected")
|
||||
|
||||
let v2 = UINavigationController(rootViewController: UIViewController())
|
||||
v2.view.backgroundColor = .white
|
||||
v2.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.message"), normalImage: "tab_message_normal", selectedImage: "tab_message_selected")
|
||||
let messageVC = EPMessageMainViewController()
|
||||
messageVC.title = "Message"
|
||||
let messageNav = UINavigationController(rootViewController: messageVC)
|
||||
messageNav.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.message"), normalImage: "tab_message_normal", selectedImage: "tab_message_selected")
|
||||
|
||||
let v3 = UINavigationController(rootViewController: UIViewController())
|
||||
v3.view.backgroundColor = .white
|
||||
v3.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.mine"), normalImage: "tab_mine_normal", selectedImage: "tab_mine_selected")
|
||||
// 角标绑定
|
||||
messageVC.unreadCountDidChange = { [weak self] c in
|
||||
let value: String? = c > 0 ? (c > 99 ? "99+" : "\(c)") : nil
|
||||
self?.viewControllers?[1].tabBarItem.badgeValue = value
|
||||
}
|
||||
|
||||
viewControllers = [v1, v2, v3]
|
||||
let mineVC = UIViewController()
|
||||
mineVC.view.backgroundColor = .systemGreen
|
||||
mineVC.title = "Mine"
|
||||
let mineNav = UINavigationController(rootViewController: mineVC)
|
||||
mineNav.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.mine"), normalImage: "tab_mine_normal", selectedImage: "tab_mine_selected")
|
||||
|
||||
viewControllers = [momentNav, messageNav, mineNav]
|
||||
selectedIndex = 0
|
||||
|
||||
NSLog("[EPTabBarController] 初始 ViewControllers 设置完成")
|
||||
}
|
||||
|
||||
/// 创建 TabBarItem
|
||||
/// - Parameters:
|
||||
/// - title: 标题
|
||||
/// - normalImage: 未选中图标名称
|
||||
/// - selectedImage: 选中图标名称
|
||||
/// - Returns: UITabBarItem
|
||||
|
||||
private func createTabBarItem(title: String, normalImage: String, selectedImage: String) -> UITabBarItem {
|
||||
let item = UITabBarItem(
|
||||
title: title,
|
||||
@@ -299,8 +276,7 @@ import SnapKit
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
/// 登录成功后刷新 TabBar
|
||||
/// - Parameter isLogin: 是否已登录
|
||||
|
||||
func refreshTabBar(isLogin: Bool) {
|
||||
isLoggedIn = isLogin
|
||||
|
||||
@@ -313,63 +289,50 @@ import SnapKit
|
||||
NSLog("[EPTabBarController] TabBar 已刷新,登录状态: \(isLogin)")
|
||||
}
|
||||
|
||||
/// 设置登录后的 ViewControllers
|
||||
|
||||
private func setupLoggedInViewControllers() {
|
||||
// 三栏:Moment | Message | Mine
|
||||
if viewControllers?.count != 3 ||
|
||||
!(viewControllers?[0] is UINavigationController) ||
|
||||
!(viewControllers?[1] is UINavigationController) ||
|
||||
!(viewControllers?[2] is UINavigationController) {
|
||||
|
||||
// 创建动态页
|
||||
let momentVC = EPMomentViewController()
|
||||
momentVC.title = YMLocalizedString("tab.moment")
|
||||
let momentNav = createTransparentNavigationController(
|
||||
rootViewController: momentVC,
|
||||
tabTitle: YMLocalizedString("tab.moment"),
|
||||
normalImage: "tab_moment_normal",
|
||||
selectedImage: "tab_moment_selected"
|
||||
)
|
||||
// 创建动态页
|
||||
let momentVC = EPMomentViewController()
|
||||
momentVC.title = YMLocalizedString("tab.moment")
|
||||
let momentNav = createTransparentNavigationController(
|
||||
rootViewController: momentVC,
|
||||
tabTitle: YMLocalizedString("tab.moment"),
|
||||
normalImage: "tab_moment_normal",
|
||||
selectedImage: "tab_moment_selected"
|
||||
)
|
||||
|
||||
// 创建消息页(Swift UIKit 容器)
|
||||
let messageVC = EPMessageMainViewController()
|
||||
let messageNav = createTransparentNavigationController(
|
||||
rootViewController: messageVC,
|
||||
tabTitle: YMLocalizedString("tab.message"),
|
||||
normalImage: "tab_message_normal",
|
||||
selectedImage: "tab_message_selected"
|
||||
)
|
||||
|
||||
// 角标绑定
|
||||
messageVC.unreadCountDidChange = { [weak self] c in
|
||||
let value: String? = c > 0 ? (c > 99 ? "99+" : "\(c)") : nil
|
||||
self?.viewControllers?[1].tabBarItem.badgeValue = value
|
||||
}
|
||||
|
||||
// 创建我的页
|
||||
let mineVC = EPMineViewController()
|
||||
mineVC.title = YMLocalizedString("tab.mine")
|
||||
let mineNav = createTransparentNavigationController(
|
||||
rootViewController: mineVC,
|
||||
tabTitle: YMLocalizedString("tab.mine"),
|
||||
normalImage: "tab_mine_normal",
|
||||
selectedImage: "tab_mine_selected"
|
||||
)
|
||||
|
||||
viewControllers = [momentNav, messageNav, mineNav]
|
||||
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Message & Mine")
|
||||
// 创建消息页(Swift UIKit 容器)
|
||||
let messageVC = EPMessageMainViewController()
|
||||
let messageNav = createTransparentNavigationController(
|
||||
rootViewController: messageVC,
|
||||
tabTitle: YMLocalizedString("tab.message"),
|
||||
normalImage: "tab_message_normal",
|
||||
selectedImage: "tab_message_selected"
|
||||
)
|
||||
// 角标绑定
|
||||
messageVC.unreadCountDidChange = { [weak self] c in
|
||||
let value: String? = c > 0 ? (c > 99 ? "99+" : "\(c)") : nil
|
||||
self?.viewControllers?[1].tabBarItem.badgeValue = value
|
||||
}
|
||||
|
||||
// 创建我的页
|
||||
let mineVC = EPMineViewController()
|
||||
mineVC.title = YMLocalizedString("tab.mine")
|
||||
let mineNav = createTransparentNavigationController(
|
||||
rootViewController: mineVC,
|
||||
tabTitle: YMLocalizedString("tab.mine"),
|
||||
normalImage: "tab_mine_normal",
|
||||
selectedImage: "tab_mine_selected"
|
||||
)
|
||||
|
||||
viewControllers = [momentNav, messageNav, mineNav]
|
||||
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Message & Mine")
|
||||
|
||||
selectedIndex = 0
|
||||
}
|
||||
|
||||
/// 创建透明导航控制器(统一配置)
|
||||
/// - Parameters:
|
||||
/// - rootViewController: 根视图控制器
|
||||
/// - tabTitle: TabBar 标题
|
||||
/// - normalImage: 未选中图标
|
||||
/// - selectedImage: 选中图标
|
||||
/// - Returns: 配置好的 UINavigationController
|
||||
|
||||
private func createTransparentNavigationController(
|
||||
rootViewController: UIViewController,
|
||||
tabTitle: String,
|
||||
@@ -387,7 +350,7 @@ import SnapKit
|
||||
selectedImage: selectedImage
|
||||
)
|
||||
|
||||
// 设置 delegate 以监听页面切换
|
||||
|
||||
nav.delegate = self
|
||||
|
||||
return nav
|
||||
@@ -395,7 +358,7 @@ import SnapKit
|
||||
|
||||
// MARK: - TabBar Visibility Control
|
||||
|
||||
/// 显示悬浮 TabBar
|
||||
|
||||
private func showCustomTabBar(animated: Bool = true) {
|
||||
guard customTabBarView.isHidden else { return }
|
||||
|
||||
@@ -414,7 +377,7 @@ import SnapKit
|
||||
}
|
||||
}
|
||||
|
||||
/// 隐藏悬浮 TabBar
|
||||
|
||||
private func hideCustomTabBar(animated: Bool = true) {
|
||||
guard !customTabBarView.isHidden else { return }
|
||||
|
||||
@@ -441,18 +404,18 @@ extension EPTabBarController: UITabBarControllerDelegate {
|
||||
NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")")
|
||||
}
|
||||
|
||||
/// 禁用系统默认的切换动画
|
||||
|
||||
func tabBarController(_ tabBarController: UITabBarController,
|
||||
animationControllerForTransitionFrom fromVC: UIViewController,
|
||||
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
// 返回 nil 表示不使用动画
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// 完全控制是否允许切换
|
||||
|
||||
func tabBarController(_ tabBarController: UITabBarController,
|
||||
shouldSelect viewController: UIViewController) -> Bool {
|
||||
// 允许切换,但通过返回 nil 的 animationController 来禁用动画
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -465,15 +428,15 @@ extension EPTabBarController: UINavigationControllerDelegate {
|
||||
willShow viewController: UIViewController,
|
||||
animated: Bool) {
|
||||
|
||||
// 判断是否是根页面(一级页面)
|
||||
|
||||
let isRootViewController = navigationController.viewControllers.count == 1
|
||||
|
||||
if isRootViewController {
|
||||
// 一级页面:显示 TabBar
|
||||
|
||||
showCustomTabBar(animated: animated)
|
||||
NSLog("[EPTabBarController] 显示 TabBar - 根页面")
|
||||
} else {
|
||||
// 二级及以上页面:隐藏 TabBar
|
||||
|
||||
hideCustomTabBar(animated: animated)
|
||||
NSLog("[EPTabBarController] 隐藏 TabBar - 子页面 (层级: \(navigationController.viewControllers.count))")
|
||||
}
|
||||
@@ -484,16 +447,16 @@ extension EPTabBarController: UINavigationControllerDelegate {
|
||||
|
||||
extension EPTabBarController {
|
||||
|
||||
/// 自动登录:验证 ticket 有效性(与 OC MainPresenter.autoLogin 保持一致)
|
||||
|
||||
private func performAutoLogin() {
|
||||
// 1. 检查账号信息
|
||||
|
||||
guard let accountModel = AccountInfoStorage.instance().getCurrentAccountInfo() else {
|
||||
NSLog("[EPTabBarController] ⚠️ 账号信息不存在,跳转到登录页")
|
||||
handleTokenInvalid()
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查 uid 和 access_token
|
||||
|
||||
let uid = accountModel.uid
|
||||
let accessToken = accountModel.access_token
|
||||
|
||||
@@ -503,14 +466,14 @@ extension EPTabBarController {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 检查 ticket 是否已存在(内存缓存)
|
||||
|
||||
let existingTicket = AccountInfoStorage.instance().getTicket() ?? ""
|
||||
if !existingTicket.isEmpty {
|
||||
NSLog("[EPTabBarController] ✅ Ticket 已存在,自动登录成功")
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Ticket 不存在,请求新的 ticket
|
||||
|
||||
NSLog("[EPTabBarController] 🔄 Ticket 不存在,正在请求...")
|
||||
let loginService = EPLoginService()
|
||||
|
||||
@@ -520,28 +483,28 @@ extension EPTabBarController {
|
||||
} failure: { [weak self] code, msg in
|
||||
NSLog("[EPTabBarController] ❌ Ticket 请求失败 (\(code)): \(msg)")
|
||||
|
||||
// ⚠️ Ticket 失败,强制退出登录(与 OC MainPresenter 保持一致)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.handleTokenInvalid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 Token 失效:清空数据并跳转到登录页
|
||||
|
||||
private func handleTokenInvalid() {
|
||||
NSLog("[EPTabBarController] ⚠️ Token 失效,清空账号数据...")
|
||||
|
||||
// 1. 清空账号信息
|
||||
|
||||
AccountInfoStorage.instance().saveAccountInfo(nil)
|
||||
AccountInfoStorage.instance().saveTicket("")
|
||||
|
||||
// 2. 跳转到登录页
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let loginVC = EPLoginViewController()
|
||||
let nav = BaseNavigationController(rootViewController: loginVC)
|
||||
nav.modalPresentationStyle = .fullScreen
|
||||
|
||||
// 获取 keyWindow(iOS 13+ 兼容)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
for scene in UIApplication.shared.connectedScenes {
|
||||
if let windowScene = scene as? UIWindowScene,
|
||||
@@ -568,12 +531,12 @@ extension EPTabBarController {
|
||||
|
||||
extension EPTabBarController {
|
||||
|
||||
/// OC 兼容:创建实例的工厂方法
|
||||
|
||||
@objc static func create() -> EPTabBarController {
|
||||
return EPTabBarController()
|
||||
}
|
||||
|
||||
/// OC 兼容:刷新 TabBar 方法
|
||||
|
||||
@objc func refreshTabBarWithIsLogin(_ isLogin: Bool) {
|
||||
refreshTabBar(isLogin: isLogin)
|
||||
}
|
||||
|
@@ -65,7 +65,7 @@ static XPCoreDataManager *manager = nil;
|
||||
* URL:要保存的文件路径
|
||||
* options:参数信息 一般无需设置
|
||||
*/
|
||||
NSURL *url = [[self getDocumnetUrlpath] URLByAppendingPathComponent:@"sqlit.db" isDirectory:true];
|
||||
NSURL *url = [[self getDocumnetUrlpath] URLByAppendingPathComponent:@"sqlit.db" isDirectory:NO];
|
||||
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:nil];
|
||||
}
|
||||
return _persistentStoreCoordinator;
|
||||
|
Reference in New Issue
Block a user