refactor: 移除 Core Data 相关代码并添加新的消息列表视图控制器

主要变更:
1. 从 AppDelegate 中移除 Core Data 相关的属性和方法,简化应用结构。
2. 新增 EPBaseListViewController 作为消息列表的基础类,提供通用的表视图功能。
3. 添加 EPMessageListVC、EPFriendListVC、EPFollowingListVC 和 EPFansListVC,分别用于展示消息、朋友、关注和粉丝列表。
4. 引入 EPMessageSegmentView 以支持消息主界面的分段控制。

此更新旨在提升代码的可维护性,简化数据管理,并增强用户界面的功能性和交互性。
This commit is contained in:
edwinQQQ
2025-10-20 11:25:33 +08:00
parent 6f5ab10562
commit c8173bf034
21 changed files with 1076 additions and 752 deletions

View File

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

View File

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

View File

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

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

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

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

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

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

View File

@@ -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
// = 3itemW + 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,9 +191,9 @@ NSString *const EPMomentPublishSuccessNotification = @"EPMomentPublishSuccessNot
[self.emotionButton setImage:colorDotImage forState:UIControlStateNormal];
//
NSString *emotionName = [EPEmotionColorStorage emotionNameForColor:self.selectedEmotionColor];
NSString *title = emotionName
NSString *title = emotionName
? [NSString stringWithFormat:@" Selected Emotion: %@", emotionName]
: @" Emotion Selected";
[self.emotionButton setTitle:title forState:UIControlStateNormal];
@@ -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,58 +217,58 @@ 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
[[EPSDKManager shared] uploadImages:self.images
progress:^(NSInteger uploaded, NSInteger total) {
[EPProgressHUD showProgress:uploaded total:total];
}
}
success:^(NSArray<NSDictionary *> *resList) {
[EPProgressHUD dismiss];
[apiHelper publishMomentWithType:@"2"
content:self.textView.text ?: @""
resList:resList
[apiHelper publishMomentWithType:@"2"
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) {
// TODO: Toast
NSLog(@"发布失败: %ld - %@", (long)code, msg);
}];
}
}
failure:^(NSString *errorMsg) {
[EPProgressHUD dismiss];
// TODO: Toast
NSLog(@"上传失败: %@", errorMsg);
}];
} else {
//
[apiHelper publishMomentWithType:@"0"
content:self.textView.text
resList:@[]
[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,73 +353,83 @@ 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; }
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.text = YMLocalizedString(@"publish.title");
_titleLabel.textColor = [UIColor whiteColor]; //
_titleLabel.font = [UIFont systemFontOfSize:17];
}
return _titleLabel;
- (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;
}
- (UIButton *)publishButton {
if (!_publishButton) {
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_publishButton setTitle:YMLocalizedString(@"common.publish") forState:UIControlStateNormal];
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.text = YMLocalizedString(@"publish.title");
_titleLabel.textColor = [UIColor whiteColor];
_titleLabel.font = [UIFont systemFontOfSize:17];
}
return _titleLabel;
}
- (UIButton *)publishButton {
if (!_publishButton) {
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_publishButton setTitle:YMLocalizedString(@"common.publish") forState:UIControlStateNormal];
[_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 addTarget:self action:@selector(onPublish) forControlEvents:UIControlEventTouchUpInside];
}
return _publishButton;
_publishButton.layer.masksToBounds = NO;
[_publishButton addTarget:self action:@selector(onPublish) forControlEvents:UIControlEventTouchUpInside];
}
return _publishButton;
}
- (UIView *)contentView { if (!_contentView) { _contentView = [UIView new]; _contentView.backgroundColor = [UIColor clearColor]; } return _contentView; }
- (SZTextView *)textView {
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]; //
- (SZTextView *)textView {
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.layer.cornerRadius = 12;
_textView.layer.masksToBounds = YES;
_textView.font = [UIFont systemFontOfSize:15];
_textView.delegate = self;
}
return _textView;
_textView.font = [UIFont systemFontOfSize:15];
_textView.delegate = self;
}
return _textView;
}
- (UILabel *)limitLabel {
if (!_limitLabel) {
_limitLabel = [UILabel new];
_limitLabel.text = @"0/500";
_limitLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6]; //
_limitLabel.font = [UIFont systemFontOfSize:12];
}
return _limitLabel;
- (UILabel *)limitLabel {
if (!_limitLabel) {
_limitLabel = [UILabel new];
_limitLabel.text = @"0/500";
_limitLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6];
_limitLabel.font = [UIFont systemFontOfSize:12];
}
return _limitLabel;
}
- (UIView *)lineView { if (!_lineView) { _lineView = [UIView new]; _lineView.backgroundColor = [DJDKMIMOMColor dividerColor]; } return _lineView; }
- (UIButton *)emotionButton {
if (!_emotionButton) {
_emotionButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_emotionButton setTitle:@"🎨 Add Emotion" forState:UIControlStateNormal];
[_emotionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; //
- (UIButton *)emotionButton {
if (!_emotionButton) {
_emotionButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_emotionButton setTitle:@"🎨 Add Emotion" 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];
}
return _emotionButton;
[_emotionButton addTarget:self action:@selector(onEmotionButtonTapped) forControlEvents:UIControlEventTouchUpInside];
}
return _emotionButton;
}
- (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.minimumLineSpacing = 10; layout.minimumInteritemSpacing = 10; CGFloat itemW = (KScreenWidth - 15*2 - 10*2)/3.0; layout.itemSize = CGSizeMake(itemW, itemW); _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.delegate = self; _collectionView.dataSource = self; _collectionView.backgroundColor = [UIColor clearColor]; [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"ep.publish.cell"]; } return _collectionView; }
- (NSMutableArray<UIImage *> *)images { if (!_images) { _images = [NSMutableArray array]; } return _images; }
- (NSMutableArray *)selectedAssets { if (!_selectedAssets) { _selectedAssets = [NSMutableArray array]; } return _selectedAssets; }
@end

View File

@@ -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
/// MVVMView
@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
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onMomentPublishSuccess:)
name:EPMomentPublishSuccessNotification
object:nil];
//
[self scheduleAutoRefreshIfNeeded];
NSLog(@"[EPMomentViewController] 页面加载完成UI 已设置");
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"[EPMomentViewController] 页面加载完成");
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

View File

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

View File

@@ -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]) {
@@ -91,7 +89,7 @@ static NSString *const kUserSignatureTimestampKey = @"EP_User_Signature_Timestam
if (!hexColor) return;
[[NSUserDefaults standardUserDefaults] setObject:hexColor forKey:kUserSignatureColorKey];
[[NSUserDefaults standardUserDefaults] setObject:@([[NSDate date] timeIntervalSince1970])
[[NSUserDefaults standardUserDefaults] setObject:@([[NSDate date] timeIntervalSince1970])
forKey:kUserSignatureTimestampKey];
[[NSUserDefaults standardUserDefaults] synchronize];
@@ -115,4 +113,3 @@ static NSString *const kUserSignatureTimestampKey = @"EP_User_Signature_Timestam
}
@end

View File

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

View File

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

View File

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

View File

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

View File

@@ -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); // 0renderImages 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;
}

View File

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

View File

@@ -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)); // 280x280360x360
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

View File

@@ -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,26 +96,26 @@ 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",
tag: 1
)
let mineButton = createTabButton(
normalImage: "tab_mine_off",
selectedImage: "tab_mine_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")
//
messageVC.unreadCountDidChange = { [weak self] c in
let value: String? = c > 0 ? (c > 99 ? "99+" : "\(c)") : nil
self?.viewControllers?[1].tabBarItem.badgeValue = value
}
let v3 = UINavigationController(rootViewController: UIViewController())
v3.view.backgroundColor = .white
v3.tabBarItem = createTabBarItem(title: YMLocalizedString("tab.mine"), normalImage: "tab_mine_normal", selectedImage: "tab_mine_selected")
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 = [v1, v2, v3]
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"
)
// 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")
//
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")
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,
func tabBarController(_ tabBarController: UITabBarController,
animationControllerForTransitionFrom fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// nil 使
return nil
}
///
func tabBarController(_ tabBarController: UITabBarController,
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool {
// nil animationController
return true
}
}
@@ -461,19 +424,19 @@ extension EPTabBarController: UITabBarControllerDelegate {
extension EPTabBarController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
willShow viewController: UIViewController,
func navigationController(_ navigationController: UINavigationController,
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
// keyWindowiOS 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)
}

View File

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