Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
galaxy-iOS
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
mobile-group
galaxy-iOS
Commits
5e650735
Commit
5e650735
authored
Sep 28, 2025
by
Steven杜宇
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev_1013' of
http://gitlab.galaxy-immi.com/mobile-group/galaxy-iOS
into dev_1013
parents
08a43dc3
e0ed7074
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
2156 additions
and
47 deletions
+2156
-47
TUILogin.m
TUIKit/TUICore/TUILogin.m
+2
-1
project.pbxproj
galaxy/galaxy.xcodeproj/project.pbxproj
+32
-0
YHNavigationController.swift
galaxy/galaxy/Classes/Base/C/YHNavigationController.swift
+1
-1
YHCirclePublishViewController.swift
...ty(社区)/Circle(下属社区)/C/YHCirclePublishViewController.swift
+437
-26
YHCircleAddPhotoCell.swift
...s/Community(社区)/Circle(下属社区)/V/YHCircleAddPhotoCell.swift
+46
-0
YHCircleMediaCell.swift
...ules/Community(社区)/Circle(下属社区)/V/YHCircleMediaCell.swift
+192
-0
YHCirclePhotoCell.swift
...ules/Community(社区)/Circle(下属社区)/V/YHCirclePhotoCell.swift
+63
-0
YHMediaUploadSheetView.swift
...Community(社区)/Circle(下属社区)/V/YHMediaUploadSheetView.swift
+546
-0
YHCertificateUploadSheetView.swift
...MyCertificates(我的证书)/V/YHCertificateUploadSheetView.swift
+25
-2
YHDocumentUploadView.swift
...)/QMAS(优才)/MyDocuments(我的文书)/V/YHDocumentUploadView.swift
+25
-2
YHMediaBrowserViewController.swift
...es/PictureReview(图片预览)/YHMediaBrowserViewController.swift
+78
-0
YHPictureReviewManager.swift
.../Modules/PictureReview(图片预览)/YHPictureReviewManager.swift
+238
-15
YHPreviewMediaItem.swift
...sses/Modules/PictureReview(图片预览)/YHPreviewMediaItem.swift
+72
-0
YHSelectMediaItem.swift
...asses/Modules/PictureReview(图片预览)/YHSelectMediaItem.swift
+30
-0
YHVideoCell.swift
...axy/Classes/Modules/PictureReview(图片预览)/YHVideoCell.swift
+281
-0
Contents.json
...Community/Circle/circle_pause_icon.imageset/Contents.json
+22
-0
circle_pause_icon@2x.png
...ircle/circle_pause_icon.imageset/circle_pause_icon@2x.png
+0
-0
circle_pause_icon@3x.png
...ircle/circle_pause_icon.imageset/circle_pause_icon@3x.png
+0
-0
Contents.json
.../Community/Circle/circle_play_icon.imageset/Contents.json
+22
-0
circle_play_icon@2x.png
.../Circle/circle_play_icon.imageset/circle_play_icon@2x.png
+0
-0
circle_play_icon@3x.png
.../Circle/circle_play_icon.imageset/circle_play_icon@3x.png
+0
-0
Contents.json
.../Community/Circle/circle_plus_icon.imageset/Contents.json
+22
-0
circle_plus_icon@2x.png
.../Circle/circle_plus_icon.imageset/circle_plus_icon@2x.png
+0
-0
circle_plus_icon@3x.png
.../Circle/circle_plus_icon.imageset/circle_plus_icon@3x.png
+0
-0
Contents.json
...mmunity/Circle/media_brower_delete.imageset/Contents.json
+22
-0
media_brower_delete@2x.png
...e/media_brower_delete.imageset/media_brower_delete@2x.png
+0
-0
media_brower_delete@3x.png
...e/media_brower_delete.imageset/media_brower_delete@3x.png
+0
-0
No files found.
TUIKit/TUICore/TUILogin.m
View file @
5e650735
...
@@ -254,7 +254,8 @@ NSString *const TUILogoutFailNotification = @"TUILogoutFailNotification";
...
@@ -254,7 +254,8 @@ NSString *const TUILogoutFailNotification = @"TUILogoutFailNotification";
[
NSNotificationCenter
.
defaultCenter
postNotificationName
:
TUILoginSuccessNotification
object
:
nil
];
[
NSNotificationCenter
.
defaultCenter
postNotificationName
:
TUILoginSuccessNotification
object
:
nil
];
}
}
fail
:^
(
int
code
,
NSString
*
desc
)
{
fail
:^
(
int
code
,
NSString
*
desc
)
{
self
.
loginWithInit
=
NO
;
__strong
__typeof
(
weakSelf
)
strongSelf
=
weakSelf
;
strongSelf
.
loginWithInit
=
NO
;
if
(
fail
)
{
if
(
fail
)
{
fail
(
code
,
desc
);
fail
(
code
,
desc
);
}
}
...
...
galaxy/galaxy.xcodeproj/project.pbxproj
View file @
5e650735
...
@@ -1294,6 +1294,14 @@
...
@@ -1294,6 +1294,14 @@
04D8FFB62DAE489A00703C75
/* YHVisitHKAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
;
};
04D8FFB62DAE489A00703C75
/* YHVisitHKAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
;
};
04D8FFB82DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
;
};
04D8FFB82DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
;
};
04D8FFBA2DB0D95A00703C75
/* YHGalaxyNewsCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
;
};
04D8FFBA2DB0D95A00703C75
/* YHGalaxyNewsCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
;
};
04E0D3C82E866A6300F1824B
/* YHCirclePhotoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
;
};
04E0D3CA2E866A9800F1824B
/* YHCircleAddPhotoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
;
};
04E0D3CC2E877D4D00F1824B
/* YHMediaUploadSheetView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
;
};
04E0D3CE2E87980E00F1824B
/* YHCircleMediaCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
;
};
04E0D3D02E87C5C700F1824B
/* YHMediaBrowserViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
;
};
04E0D3D22E87CCB300F1824B
/* YHVideoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
;
};
04E0D3D42E87CF3200F1824B
/* YHPreviewMediaItem.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
;
};
04E0D3D62E87CF7400F1824B
/* YHSelectMediaItem.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
;
};
04E4CF3E2D5C6D32004D4013
/* YHCountryMessageView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
;
};
04E4CF3E2D5C6D32004D4013
/* YHCountryMessageView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
;
};
04E4CF402D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
;
};
04E4CF402D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
;
};
04E507D62D6EE856005F758B
/* YHUserLevelAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
;
};
04E507D62D6EE856005F758B
/* YHUserLevelAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
;
};
...
@@ -2650,6 +2658,14 @@
...
@@ -2650,6 +2658,14 @@
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHVisitHKAlertView.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHVisitHKAlertView.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsListViewController.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsListViewController.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsCell.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCirclePhotoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCircleAddPhotoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHMediaUploadSheetView.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCircleMediaCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHMediaBrowserViewController.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHVideoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHPreviewMediaItem.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHSelectMediaItem.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCountryMessageView.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCountryMessageView.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHSelectPhoneCountryViewController.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHSelectPhoneCountryViewController.swift
;
sourceTree
=
"<group>"
;
};
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHUserLevelAlertView.swift
;
sourceTree
=
"<group>"
;
};
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHUserLevelAlertView.swift
;
sourceTree
=
"<group>"
;
};
...
@@ -6112,9 +6128,13 @@
...
@@ -6112,9 +6128,13 @@
045C0F002D12CA5E00BD2DC0
/* PictureReview(图片预览) */
=
{
045C0F002D12CA5E00BD2DC0
/* PictureReview(图片预览) */
=
{
isa
=
PBXGroup
;
isa
=
PBXGroup
;
children
=
(
children
=
(
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
,
045C0EFE2D12CA5E00BD2DC0
/* YHLongtapPictureSheetView.swift */
,
045C0EFE2D12CA5E00BD2DC0
/* YHLongtapPictureSheetView.swift */
,
045C0EFF2D12CA5E00BD2DC0
/* YHPictureReviewManager.swift */
,
045C0EFF2D12CA5E00BD2DC0
/* YHPictureReviewManager.swift */
,
04D8FFB12DA5007A00703C75
/* YHPictureBrowserViewController.swift */
,
04D8FFB12DA5007A00703C75
/* YHPictureBrowserViewController.swift */
,
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
,
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
,
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
,
);
);
path
=
"PictureReview(图片预览)"
;
path
=
"PictureReview(图片预览)"
;
sourceTree
=
"<group>"
;
sourceTree
=
"<group>"
;
...
@@ -6969,7 +6989,11 @@
...
@@ -6969,7 +6989,11 @@
04D4EC2A2E839B3000B0329B
/* V */
=
{
04D4EC2A2E839B3000B0329B
/* V */
=
{
isa
=
PBXGroup
;
isa
=
PBXGroup
;
children
=
(
children
=
(
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
,
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
,
04D4EC4D2E84F22500B0329B
/* YHCircleHeaderReusableView.swift */
,
04D4EC4D2E84F22500B0329B
/* YHCircleHeaderReusableView.swift */
,
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
,
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
,
04D4EC452E83D11500B0329B
/* YHCircleCollectionViewCell.swift */
,
04D4EC452E83D11500B0329B
/* YHCircleCollectionViewCell.swift */
,
);
);
path
=
V
;
path
=
V
;
...
@@ -7905,6 +7929,7 @@
...
@@ -7905,6 +7929,7 @@
045C10E22D12CA5F00BD2DC0
/* YHInvitationWithGiftsDetailView.swift in Sources */
,
045C10E22D12CA5F00BD2DC0
/* YHInvitationWithGiftsDetailView.swift in Sources */
,
045C10E32D12CA5F00BD2DC0
/* YHRiskWarningCell.swift in Sources */
,
045C10E32D12CA5F00BD2DC0
/* YHRiskWarningCell.swift in Sources */
,
045C10E42D12CA5F00BD2DC0
/* YHFileRenameInputView.swift in Sources */
,
045C10E42D12CA5F00BD2DC0
/* YHFileRenameInputView.swift in Sources */
,
04E0D3CA2E866A9800F1824B
/* YHCircleAddPhotoCell.swift in Sources */
,
045C10E52D12CA5F00BD2DC0
/* YHLookResignResultFootView.swift in Sources */
,
045C10E52D12CA5F00BD2DC0
/* YHLookResignResultFootView.swift in Sources */
,
04AE204E2D1941FC00891D24
/* YHGCCertificateListContainerVC.swift in Sources */
,
04AE204E2D1941FC00891D24
/* YHGCCertificateListContainerVC.swift in Sources */
,
045C10E62D12CA5F00BD2DC0
/* YHInvitationWithGiftsViewController.swift in Sources */
,
045C10E62D12CA5F00BD2DC0
/* YHInvitationWithGiftsViewController.swift in Sources */
,
...
@@ -8038,6 +8063,7 @@
...
@@ -8038,6 +8063,7 @@
045C11522D12CA5F00BD2DC0
/* YHResignAppointSubmitTipsView.swift in Sources */
,
045C11522D12CA5F00BD2DC0
/* YHResignAppointSubmitTipsView.swift in Sources */
,
045C11532D12CA5F00BD2DC0
/* YHLookResignResultStateOneTableViewCell.swift in Sources */
,
045C11532D12CA5F00BD2DC0
/* YHLookResignResultStateOneTableViewCell.swift in Sources */
,
045C11542D12CA5F00BD2DC0
/* YHWorkExperienceCompanyModel.swift in Sources */
,
045C11542D12CA5F00BD2DC0
/* YHWorkExperienceCompanyModel.swift in Sources */
,
04E0D3CE2E87980E00F1824B
/* YHCircleMediaCell.swift in Sources */
,
045C11552D12CA5F00BD2DC0
/* YHResignInfoItemView.swift in Sources */
,
045C11552D12CA5F00BD2DC0
/* YHResignInfoItemView.swift in Sources */
,
04AE203B2D13C01B00891D24
/* YHGCEducationInfoListVC.swift in Sources */
,
04AE203B2D13C01B00891D24
/* YHGCEducationInfoListVC.swift in Sources */
,
045C11562D12CA5F00BD2DC0
/* YHHUDSquareBaseView.swift in Sources */
,
045C11562D12CA5F00BD2DC0
/* YHHUDSquareBaseView.swift in Sources */
,
...
@@ -8117,7 +8143,9 @@
...
@@ -8117,7 +8143,9 @@
045C11942D12CA5F00BD2DC0
/* YHServiceBannerView.swift in Sources */
,
045C11942D12CA5F00BD2DC0
/* YHServiceBannerView.swift in Sources */
,
045C11952D12CA5F00BD2DC0
/* YHFourKingViewController.swift in Sources */
,
045C11952D12CA5F00BD2DC0
/* YHFourKingViewController.swift in Sources */
,
045C11962D12CA5F00BD2DC0
/* YHActivityTipsItemView.swift in Sources */
,
045C11962D12CA5F00BD2DC0
/* YHActivityTipsItemView.swift in Sources */
,
04E0D3D22E87CCB300F1824B
/* YHVideoCell.swift in Sources */
,
045C11972D12CA5F00BD2DC0
/* YHHUDRotatingImageView.swift in Sources */
,
045C11972D12CA5F00BD2DC0
/* YHHUDRotatingImageView.swift in Sources */
,
04E0D3D62E87CF7400F1824B
/* YHSelectMediaItem.swift in Sources */
,
04307B6C2D1A547C00ED8E8D
/* YHIncomeDateTillNowCell.swift in Sources */
,
04307B6C2D1A547C00ED8E8D
/* YHIncomeDateTillNowCell.swift in Sources */
,
045C11982D12CA5F00BD2DC0
/* YHCerAppointViewModel.swift in Sources */
,
045C11982D12CA5F00BD2DC0
/* YHCerAppointViewModel.swift in Sources */
,
045C11992D12CA5F00BD2DC0
/* YHPayMembersCell.swift in Sources */
,
045C11992D12CA5F00BD2DC0
/* YHPayMembersCell.swift in Sources */
,
...
@@ -8217,6 +8245,7 @@
...
@@ -8217,6 +8245,7 @@
045C11EA2D12CA5F00BD2DC0
/* YHMySignatureDetailModel.swift in Sources */
,
045C11EA2D12CA5F00BD2DC0
/* YHMySignatureDetailModel.swift in Sources */
,
045C11EB2D12CA5F00BD2DC0
/* YHPlayerControlView.swift in Sources */
,
045C11EB2D12CA5F00BD2DC0
/* YHPlayerControlView.swift in Sources */
,
04307B6E2D1A5F4200ED8E8D
/* YHIncomeUploadWorkIDCell.swift in Sources */
,
04307B6E2D1A5F4200ED8E8D
/* YHIncomeUploadWorkIDCell.swift in Sources */
,
04E0D3D02E87C5C700F1824B
/* YHMediaBrowserViewController.swift in Sources */
,
045C11EC2D12CA5F00BD2DC0
/* YHMyDocumentsListViewController.swift in Sources */
,
045C11EC2D12CA5F00BD2DC0
/* YHMyDocumentsListViewController.swift in Sources */
,
045C11ED2D12CA5F00BD2DC0
/* YHCertificateEntryBottomView.swift in Sources */
,
045C11ED2D12CA5F00BD2DC0
/* YHCertificateEntryBottomView.swift in Sources */
,
045C11EE2D12CA5F00BD2DC0
/* YHAppointItem.swift in Sources */
,
045C11EE2D12CA5F00BD2DC0
/* YHAppointItem.swift in Sources */
,
...
@@ -8246,6 +8275,7 @@
...
@@ -8246,6 +8275,7 @@
045C12002D12CA5F00BD2DC0
/* YHPrincipleApprovedAlertView.swift in Sources */
,
045C12002D12CA5F00BD2DC0
/* YHPrincipleApprovedAlertView.swift in Sources */
,
045C12012D12CA5F00BD2DC0
/* YHSchemeTableHeadView.swift in Sources */
,
045C12012D12CA5F00BD2DC0
/* YHSchemeTableHeadView.swift in Sources */
,
045C12022D12CA5F00BD2DC0
/* YHResignDocumentUploadStatus.swift in Sources */
,
045C12022D12CA5F00BD2DC0
/* YHResignDocumentUploadStatus.swift in Sources */
,
04E0D3D42E87CF3200F1824B
/* YHPreviewMediaItem.swift in Sources */
,
045C12032D12CA5F00BD2DC0
/* YHActivityListCell.swift in Sources */
,
045C12032D12CA5F00BD2DC0
/* YHActivityListCell.swift in Sources */
,
045C12042D12CA5F00BD2DC0
/* YHTravelCertificateTipsCell.swift in Sources */
,
045C12042D12CA5F00BD2DC0
/* YHTravelCertificateTipsCell.swift in Sources */
,
045C12052D12CA5F00BD2DC0
/* YHVisaRenewalPayMethodQrcodeCell.swift in Sources */
,
045C12052D12CA5F00BD2DC0
/* YHVisaRenewalPayMethodQrcodeCell.swift in Sources */
,
...
@@ -8263,6 +8293,7 @@
...
@@ -8263,6 +8293,7 @@
045C12102D12CA5F00BD2DC0
/* YHBaseViewController.swift in Sources */
,
045C12102D12CA5F00BD2DC0
/* YHBaseViewController.swift in Sources */
,
045C12112D12CA5F00BD2DC0
/* YHInfoItemOptionView.swift in Sources */
,
045C12112D12CA5F00BD2DC0
/* YHInfoItemOptionView.swift in Sources */
,
045C12122D12CA5F00BD2DC0
/* YHPreviewInfoWorkSummaryView.swift in Sources */
,
045C12122D12CA5F00BD2DC0
/* YHPreviewInfoWorkSummaryView.swift in Sources */
,
04E0D3CC2E877D4D00F1824B
/* YHMediaUploadSheetView.swift in Sources */
,
045C12132D12CA5F00BD2DC0
/* YHVisaRenewalPayOccupyingSpaceCell.swift in Sources */
,
045C12132D12CA5F00BD2DC0
/* YHVisaRenewalPayOccupyingSpaceCell.swift in Sources */
,
045C12142D12CA5F00BD2DC0
/* YHCheckEamilAlertView.swift in Sources */
,
045C12142D12CA5F00BD2DC0
/* YHCheckEamilAlertView.swift in Sources */
,
045C12152D12CA5F00BD2DC0
/* YHResignAppointedScheduleRiskTipsView.swift in Sources */
,
045C12152D12CA5F00BD2DC0
/* YHResignAppointedScheduleRiskTipsView.swift in Sources */
,
...
@@ -8658,6 +8689,7 @@
...
@@ -8658,6 +8689,7 @@
045C13562D12CA5F00BD2DC0
/* YHOtherPickerView.swift in Sources */
,
045C13562D12CA5F00BD2DC0
/* YHOtherPickerView.swift in Sources */
,
045C13572D12CA5F00BD2DC0
/* YHLivePlayerViewController.swift in Sources */
,
045C13572D12CA5F00BD2DC0
/* YHLivePlayerViewController.swift in Sources */
,
045C13582D12CA5F00BD2DC0
/* YHAIChatInputShadowView.swift in Sources */
,
045C13582D12CA5F00BD2DC0
/* YHAIChatInputShadowView.swift in Sources */
,
04E0D3C82E866A6300F1824B
/* YHCirclePhotoCell.swift in Sources */
,
0411CF002D1A805A00644D35
/* YHGCMySignatureListViewModel.swift in Sources */
,
0411CF002D1A805A00644D35
/* YHGCMySignatureListViewModel.swift in Sources */
,
045C13592D12CA5F00BD2DC0
/* YHMyFileListNoneCell.swift in Sources */
,
045C13592D12CA5F00BD2DC0
/* YHMyFileListNoneCell.swift in Sources */
,
045C135A2D12CA5F00BD2DC0
/* YHCardButton.swift in Sources */
,
045C135A2D12CA5F00BD2DC0
/* YHCardButton.swift in Sources */
,
...
...
galaxy/galaxy/Classes/Base/C/YHNavigationController.swift
View file @
5e650735
...
@@ -39,7 +39,7 @@ class YHNavigationController: UINavigationController {
...
@@ -39,7 +39,7 @@ class YHNavigationController: UINavigationController {
if
let
lastVC
=
viewControllers
.
last
{
if
let
lastVC
=
viewControllers
.
last
{
let
className
=
String
(
describing
:
type
(
of
:
lastVC
))
let
className
=
String
(
describing
:
type
(
of
:
lastVC
))
if
!
className
.
hasPrefix
(
"TUI"
)
{
// 模糊匹配类名,使得腾讯IM页面不用隐藏NavigationBar
if
!
className
.
hasPrefix
(
"TUI"
)
{
// 模糊匹配类名,使得腾讯IM页面不用隐藏NavigationBar
var
needAnimated
=
false
var
needAnimated
=
animated
let
lastSecondCount
=
viewControllers
.
count
-
2
let
lastSecondCount
=
viewControllers
.
count
-
2
if
lastSecondCount
>=
0
{
if
lastSecondCount
>=
0
{
let
lastSecondVC
=
viewControllers
[
lastSecondCount
]
let
lastSecondVC
=
viewControllers
[
lastSecondCount
]
...
...
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/C/YHCirclePublishViewController.swift
View file @
5e650735
...
@@ -7,48 +7,459 @@
...
@@ -7,48 +7,459 @@
//
//
import
UIKit
import
UIKit
import
SnapKit
class
YHCirclePublishViewController
:
YHBaseViewController
{
class
YHCirclePublishViewController
:
YHBaseViewController
{
private
let
marginX
:
CGFloat
=
24
private
let
itemSpace
:
CGFloat
=
8
var
completion
:
(()
->
Void
)?
var
completion
:
(()
->
Void
)?
// MARK: - Navigation Items
private
lazy
var
leftBarItem
:
UIBarButtonItem
=
{
let
item
=
UIBarButtonItem
(
image
:
UIImage
(
named
:
"nav_black_24"
)?
.
withRenderingMode
(
.
alwaysOriginal
),
style
:
.
plain
,
target
:
self
,
action
:
#selector(
cancelButtonTapped
)
)
return
item
}()
private
lazy
var
rightButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
frame
=
CGRect
(
x
:
0
,
y
:
0
,
width
:
56
,
height
:
32
)
button
.
layer
.
cornerRadius
=
16
button
.
clipsToBounds
=
true
button
.
setTitle
(
"发布"
,
for
:
.
normal
)
button
.
titleLabel
?
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
12
)
button
.
setTitleColor
(
UIColor
.
brandGrayColor0
,
for
:
.
normal
)
button
.
setTitleColor
(
UIColor
.
brandGrayColor0
,
for
:
.
disabled
)
button
.
isEnabled
=
false
button
.
backgroundColor
=
UIColor
.
brandGrayColor4
button
.
addTarget
(
self
,
action
:
#selector(
publishButtonTapped
)
,
for
:
.
touchUpInside
)
return
button
}()
// MARK: - UI Components
private
lazy
var
scrollView
:
UIScrollView
=
{
let
scrollView
=
UIScrollView
()
scrollView
.
backgroundColor
=
.
white
scrollView
.
showsVerticalScrollIndicator
=
false
return
scrollView
}()
private
lazy
var
contentView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
private
lazy
var
userInfoView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
private
lazy
var
avatarImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"people_head_default"
)
// 设置默认头像
imageView
.
layer
.
cornerRadius
=
17
imageView
.
clipsToBounds
=
true
imageView
.
contentMode
=
.
scaleAspectFill
return
imageView
}()
private
lazy
var
usernameLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"Monica杨晓丽"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
label
.
textColor
=
.
brandGrayColor8
return
label
}()
private
lazy
var
subtitleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"董事长"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
11
)
label
.
textColor
=
UIColor
.
brandGrayColor6
return
label
}()
private
lazy
var
textView
:
UITextView
=
{
let
textView
=
UITextView
()
textView
.
font
=
UIFont
.
PFSC_B
(
ofSize
:
17
)
textView
.
textColor
=
.
brandGrayColor8
textView
.
backgroundColor
=
.
clear
textView
.
delegate
=
self
textView
.
textContainer
.
lineFragmentPadding
=
0
textView
.
textContainerInset
=
UIEdgeInsets
.
zero
textView
.
showsVerticalScrollIndicator
=
false
return
textView
}()
private
lazy
var
placeholderLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"添加标题"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
17
)
label
.
textColor
=
UIColor
.
brandGrayColor5
return
label
}()
private
lazy
var
detailTextView
:
UITextView
=
{
let
textView
=
UITextView
()
textView
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
textView
.
textColor
=
UIColor
.
brandGrayColor8
textView
.
backgroundColor
=
.
clear
textView
.
delegate
=
self
textView
.
textContainer
.
lineFragmentPadding
=
0
textView
.
textContainerInset
=
UIEdgeInsets
.
zero
textView
.
showsVerticalScrollIndicator
=
false
return
textView
}()
private
lazy
var
detailPlaceholderLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"分享生活,表达想法,随时随地..."
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
label
.
textColor
=
UIColor
.
brandGrayColor5
label
.
numberOfLines
=
0
return
label
}()
private
lazy
var
mediaCollectionView
:
UICollectionView
=
{
let
layout
=
UICollectionViewFlowLayout
()
layout
.
minimumInteritemSpacing
=
itemSpace
layout
.
minimumLineSpacing
=
itemSpace
let
itemWidth
=
getItemWidth
()
layout
.
itemSize
=
CGSize
(
width
:
itemWidth
,
height
:
itemWidth
)
let
collectionView
=
UICollectionView
(
frame
:
.
zero
,
collectionViewLayout
:
layout
)
collectionView
.
backgroundColor
=
.
white
collectionView
.
delegate
=
self
collectionView
.
dataSource
=
self
collectionView
.
register
(
YHCircleMediaCell
.
self
,
forCellWithReuseIdentifier
:
"YHCircleMediaCell"
)
collectionView
.
register
(
YHCircleAddPhotoCell
.
self
,
forCellWithReuseIdentifier
:
"YHCircleAddPhotoCell"
)
collectionView
.
showsVerticalScrollIndicator
=
false
return
collectionView
}()
private
lazy
var
divideLine
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
brandGrayColor3
return
view
}()
// MARK: - Properties
private
var
mediaItems
:
[
YHSelectMediaItem
]
=
[]
private
let
maxMediaCount
=
9
override
func
viewDidLoad
()
{
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
super
.
viewDidLoad
()
setupNav
()
setupUI
()
setupConstraints
()
setupNotifications
()
}
deinit
{
NotificationCenter
.
default
.
removeObserver
(
self
)
printLog
(
"YHCirclePublishViewController deinit"
)
}
// MARK: - Setup Methods
private
func
setupNav
()
{
gk_navTitle
=
"发布动态"
gk_navLeftBarButtonItem
=
leftBarItem
gk_navRightBarButtonItem
=
UIBarButtonItem
(
customView
:
rightButton
)
gk_navItemRightSpace
=
16
gk_navItemLeftSpace
=
16
gk_navBackgroundColor
=
.
white
}
private
func
setupUI
()
{
view
.
backgroundColor
=
.
white
view
.
backgroundColor
=
.
white
gk_navigationBar
.
isHidden
=
true
title
=
"发布动态"
navigationItem
.
leftBarButtonItem
=
UIBarButtonItem
(
title
:
"取消"
,
style
:
.
plain
,
target
:
self
,
action
:
#selector(
cancelButtonTapped
)
)
navigationItem
.
rightBarButtonItem
=
UIBarButtonItem
(
title
:
"发布"
,
style
:
.
done
,
target
:
self
,
action
:
#selector(
publishButtonTapped
)
)
let
label
=
UILabel
()
view
.
addSubview
(
scrollView
)
label
.
text
=
"发布页面"
scrollView
.
addSubview
(
contentView
)
label
.
textAlignment
=
.
center
label
.
textColor
=
.
gray
contentView
.
addSubview
(
userInfoView
)
view
.
addSubview
(
label
)
userInfoView
.
addSubview
(
avatarImageView
)
userInfoView
.
addSubview
(
usernameLabel
)
userInfoView
.
addSubview
(
subtitleLabel
)
contentView
.
addSubview
(
textView
)
contentView
.
addSubview
(
placeholderLabel
)
contentView
.
addSubview
(
divideLine
)
contentView
.
addSubview
(
detailTextView
)
contentView
.
addSubview
(
detailPlaceholderLabel
)
contentView
.
addSubview
(
mediaCollectionView
)
}
private
func
setupConstraints
()
{
scrollView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
k_Height_NavigationtBarAndStatuBar
)
make
.
left
.
right
.
bottom
.
equalToSuperview
()
}
contentView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
make
.
width
.
equalTo
(
view
.
snp
.
width
)
}
userInfoView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalToSuperview
()
.
offset
(
20
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
}
avatarImageView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
top
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
34
)
make
.
bottom
.
equalToSuperview
()
}
usernameLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalTo
(
avatarImageView
.
snp
.
right
)
.
offset
(
8
)
make
.
top
.
equalTo
(
avatarImageView
.
snp
.
top
)
make
.
right
.
equalToSuperview
()
}
label
.
snp
.
makeConstraints
{
make
in
subtitleLabel
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
left
.
equalTo
(
usernameLabel
)
make
.
bottom
.
equalTo
(
avatarImageView
.
snp
.
bottom
)
make
.
right
.
equalToSuperview
()
}
}
textView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
userInfoView
.
snp
.
bottom
)
.
offset
(
24
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
greaterThanOrEqualTo
(
24
)
}
placeholderLabel
.
snp
.
makeConstraints
{
make
in
make
.
top
.
left
.
equalTo
(
textView
)
}
divideLine
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
textView
.
snp
.
bottom
)
.
offset
(
16
)
make
.
left
.
right
.
equalTo
(
textView
)
make
.
height
.
equalTo
(
1
)
}
detailTextView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
divideLine
.
snp
.
bottom
)
.
offset
(
16
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
greaterThanOrEqualTo
(
20
)
}
detailPlaceholderLabel
.
snp
.
makeConstraints
{
make
in
make
.
top
.
left
.
equalTo
(
detailTextView
)
}
mediaCollectionView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
detailTextView
.
snp
.
bottom
)
.
offset
(
96
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
equalTo
(
200
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
30
)
}
updateCollectionViewHeight
()
}
}
private
func
setupNotifications
()
{
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
keyboardWillShow
)
,
name
:
UIResponder
.
keyboardWillShowNotification
,
object
:
nil
)
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
keyboardWillHide
)
,
name
:
UIResponder
.
keyboardWillHideNotification
,
object
:
nil
)
}
private
func
getItemWidth
()
->
CGFloat
{
let
itemWidth
=
(
UIScreen
.
main
.
bounds
.
width
-
marginX
*
2
-
itemSpace
*
2
)
/
3
return
itemWidth
}
// MARK: - Actions
@objc
private
func
cancelButtonTapped
()
{
@objc
private
func
cancelButtonTapped
()
{
dismiss
(
animated
:
true
)
if
hasContent
()
{
showCancelAlert
()
}
else
{
dismiss
(
animated
:
true
)
}
}
}
@objc
private
func
publishButtonTapped
()
{
@objc
private
func
publishButtonTapped
()
{
completion
?()
// 发布逻辑
dismiss
(
animated
:
true
)
showPublishingAlert
()
// 模拟发布延迟
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2.0
)
{
self
.
completion
?()
self
.
dismiss
(
animated
:
true
)
}
}
// MARK: - Keyboard Handling
@objc
private
func
keyboardWillShow
(
notification
:
NSNotification
)
{
guard
let
keyboardFrame
=
notification
.
userInfo
?[
UIResponder
.
keyboardFrameEndUserInfoKey
]
as?
CGRect
,
let
duration
=
notification
.
userInfo
?[
UIResponder
.
keyboardAnimationDurationUserInfoKey
]
as?
Double
else
{
return
}
let
keyboardHeight
=
keyboardFrame
.
height
UIView
.
animate
(
withDuration
:
duration
)
{
self
.
scrollView
.
contentInset
.
bottom
=
keyboardHeight
self
.
scrollView
.
verticalScrollIndicatorInsets
.
bottom
=
keyboardHeight
}
}
@objc
private
func
keyboardWillHide
(
notification
:
NSNotification
)
{
guard
let
duration
=
notification
.
userInfo
?[
UIResponder
.
keyboardAnimationDurationUserInfoKey
]
as?
Double
else
{
return
}
UIView
.
animate
(
withDuration
:
duration
)
{
self
.
scrollView
.
contentInset
.
bottom
=
0
self
.
scrollView
.
verticalScrollIndicatorInsets
.
bottom
=
0
}
}
// MARK: - Helper Methods
private
func
hasContent
()
->
Bool
{
return
!
textView
.
text
.
isEmpty
||
!
detailTextView
.
text
.
isEmpty
||
!
mediaItems
.
isEmpty
}
private
func
updatePublishButton
()
{
let
hasContent
=
hasContent
()
rightButton
.
isEnabled
=
hasContent
rightButton
.
backgroundColor
=
hasContent
?
UIColor
.
brandGrayColor8
:
UIColor
.
brandGrayColor4
}
private
func
updateCollectionViewHeight
()
{
let
rows
=
ceil
(
Double
(
mediaItems
.
count
+
1
)
/
3.0
)
let
itemWidth
=
getItemWidth
()
let
height
=
CGFloat
(
rows
)
*
itemWidth
+
CGFloat
(
max
(
0
,
rows
-
1
))
*
8
mediaCollectionView
.
snp
.
updateConstraints
{
make
in
make
.
height
.
equalTo
(
max
(
height
,
itemWidth
))
}
}
private
func
showCancelAlert
()
{
let
alert
=
UIAlertController
(
title
:
"确定要离开吗?"
,
message
:
"离开后内容将不会保存"
,
preferredStyle
:
.
alert
)
alert
.
addAction
(
UIAlertAction
(
title
:
"取消"
,
style
:
.
cancel
))
alert
.
addAction
(
UIAlertAction
(
title
:
"确定"
,
style
:
.
destructive
)
{
_
in
self
.
dismiss
(
animated
:
true
)
})
present
(
alert
,
animated
:
true
)
}
private
func
showPublishingAlert
()
{
let
alert
=
UIAlertController
(
title
:
"发布中..."
,
message
:
nil
,
preferredStyle
:
.
alert
)
present
(
alert
,
animated
:
true
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1.5
)
{
alert
.
dismiss
(
animated
:
true
)
}
}
}
// MARK: - UITextViewDelegate
extension
YHCirclePublishViewController
:
UITextViewDelegate
{
func
textViewDidChange
(
_
textView
:
UITextView
)
{
if
textView
==
self
.
textView
{
placeholderLabel
.
isHidden
=
!
textView
.
text
.
isEmpty
}
else
if
textView
==
detailTextView
{
detailPlaceholderLabel
.
isHidden
=
!
textView
.
text
.
isEmpty
}
updatePublishButton
()
}
func
textView
(
_
textView
:
UITextView
,
shouldChangeTextIn
range
:
NSRange
,
replacementText
text
:
String
)
->
Bool
{
if
textView
==
self
.
textView
{
let
currentText
=
textView
.
text
??
""
guard
let
stringRange
=
Range
(
range
,
in
:
currentText
)
else
{
return
false
}
let
updatedText
=
currentText
.
replacingCharacters
(
in
:
stringRange
,
with
:
text
)
return
updatedText
.
count
<=
100
}
return
true
}
}
// MARK: - UICollectionViewDataSource & Delegate
extension
YHCirclePublishViewController
:
UICollectionViewDataSource
,
UICollectionViewDelegate
{
func
collectionView
(
_
collectionView
:
UICollectionView
,
numberOfItemsInSection
section
:
Int
)
->
Int
{
return
min
(
mediaItems
.
count
+
1
,
maxMediaCount
)
}
func
collectionView
(
_
collectionView
:
UICollectionView
,
cellForItemAt
indexPath
:
IndexPath
)
->
UICollectionViewCell
{
if
indexPath
.
item
<
mediaItems
.
count
{
if
let
cell
=
collectionView
.
dequeueReusableCell
(
withReuseIdentifier
:
"YHCircleMediaCell"
,
for
:
indexPath
)
as?
YHCircleMediaCell
{
cell
.
configure
(
with
:
mediaItems
[
indexPath
.
item
])
cell
.
deleteCallback
=
{
[
weak
self
]
in
self
?
.
removeMedia
(
at
:
indexPath
.
item
)
}
return
cell
}
}
else
{
if
let
cell
=
collectionView
.
dequeueReusableCell
(
withReuseIdentifier
:
"YHCircleAddPhotoCell"
,
for
:
indexPath
)
as?
YHCircleAddPhotoCell
{
return
cell
}
}
return
UICollectionViewCell
()
}
func
collectionView
(
_
collectionView
:
UICollectionView
,
didSelectItemAt
indexPath
:
IndexPath
)
{
if
indexPath
.
item
>=
mediaItems
.
count
{
showMediaUploadSheet
()
}
else
{
// 预览本地选中的媒体
previewLocalMedia
(
at
:
indexPath
.
item
)
}
}
// 预览本地选择的媒体文件
private
func
previewLocalMedia
(
at
index
:
Int
)
{
YHPictureReviewManager
.
shared
.
showLocalMedia
(
curIndex
:
index
,
selectMediaItems
:
self
.
mediaItems
)
{
[
weak
self
]
deletedIndex
in
// 删除回调处理
guard
let
self
=
self
else
{
return
}
DispatchQueue
.
main
.
async
{
// 从本地媒体数组中删除对应项
if
deletedIndex
<
self
.
mediaItems
.
count
{
self
.
mediaItems
.
remove
(
at
:
deletedIndex
)
// 刷新集合视图
self
.
mediaCollectionView
.
reloadData
()
self
.
updateCollectionViewHeight
()
self
.
updatePublishButton
()
printLog
(
"已从发布页面删除媒体项,剩余:
\(
self
.
mediaItems
.
count
)
"
)
}
}
}
}
private
func
removeMedia
(
at
index
:
Int
)
{
mediaItems
.
remove
(
at
:
index
)
mediaCollectionView
.
reloadData
()
updateCollectionViewHeight
()
updatePublishButton
()
}
private
func
showMediaUploadSheet
()
{
YHMediaUploadSheetView
.
sheetView
()
.
show
{
[
weak
self
]
selectedMediaItems
in
guard
let
self
=
self
else
{
return
}
// 检查是否超出最大数量限制
let
remainingSlots
=
maxMediaCount
-
mediaItems
.
count
let
itemsToAdd
=
Array
(
selectedMediaItems
.
prefix
(
remainingSlots
))
if
itemsToAdd
.
count
<
selectedMediaItems
.
count
{
YHHUD
.
flash
(
message
:
"最多只能选择
\(
maxMediaCount
)
个媒体文件"
)
}
// 添加新选择的媒体项
mediaItems
.
append
(
contentsOf
:
itemsToAdd
)
mediaCollectionView
.
reloadData
()
updateCollectionViewHeight
()
updatePublishButton
()
print
(
"获得
\(
itemsToAdd
.
count
)
个媒体文件"
)
}
}
}
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCircleAddPhotoCell.swift
0 → 100644
View file @
5e650735
//
// YHCircleAddPhotoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
class
YHCircleAddPhotoCell
:
UICollectionViewCell
{
private
lazy
var
containerView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
systemGray6
return
view
}()
private
lazy
var
plusImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"circle_plus_icon"
)
return
imageView
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
setupUI
()
{
contentView
.
addSubview
(
containerView
)
containerView
.
addSubview
(
plusImageView
)
containerView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
plusImageView
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
40
)
}
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCircleMediaCell.swift
0 → 100644
View file @
5e650735
//
// YHCircleMediaCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
import
AVFoundation
class
YHCircleMediaCell
:
UICollectionViewCell
{
var
deleteCallback
:
(()
->
Void
)?
// MARK: - UI Components
private
lazy
var
imageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
contentMode
=
.
scaleAspectFill
imageView
.
clipsToBounds
=
true
imageView
.
backgroundColor
=
UIColor
.
brandGrayColor2
return
imageView
}()
private
lazy
var
deleteButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
systemName
:
"xmark.circle.fill"
),
for
:
.
normal
)
button
.
tintColor
=
.
white
button
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
button
.
layer
.
cornerRadius
=
10
button
.
addTarget
(
self
,
action
:
#selector(
deleteButtonTapped
)
,
for
:
.
touchUpInside
)
button
.
isHidden
=
true
return
button
}()
private
lazy
var
playIcon
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"circle_play_icon"
)
imageView
.
isHidden
=
true
return
imageView
}()
private
lazy
var
durationLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
12
,
weight
:
.
medium
)
label
.
textColor
=
.
white
label
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
label
.
textAlignment
=
.
center
label
.
layer
.
cornerRadius
=
8
label
.
clipsToBounds
=
true
label
.
isHidden
=
true
return
label
}()
// MARK: - Initialization
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
// MARK: - Setup UI
private
func
setupUI
()
{
contentView
.
addSubview
(
imageView
)
contentView
.
addSubview
(
playIcon
)
contentView
.
addSubview
(
durationLabel
)
contentView
.
addSubview
(
deleteButton
)
imageView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
playIcon
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
40
)
}
durationLabel
.
snp
.
makeConstraints
{
make
in
make
.
bottom
.
right
.
equalToSuperview
()
.
inset
(
8
)
make
.
height
.
equalTo
(
20
)
make
.
width
.
greaterThanOrEqualTo
(
35
)
}
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
right
.
equalToSuperview
()
.
inset
(
4
)
make
.
width
.
height
.
equalTo
(
20
)
}
}
// MARK: - Configure Cell
func
configure
(
with
mediaItem
:
YHSelectMediaItem
)
{
resetCell
()
switch
mediaItem
.
type
{
case
.
image
:
configureForImage
(
mediaItem
)
case
.
video
:
configureForVideo
(
mediaItem
)
}
}
private
func
resetCell
()
{
imageView
.
image
=
nil
playIcon
.
isHidden
=
true
durationLabel
.
isHidden
=
true
durationLabel
.
text
=
nil
}
private
func
configureForImage
(
_
mediaItem
:
YHSelectMediaItem
)
{
imageView
.
image
=
mediaItem
.
image
playIcon
.
isHidden
=
true
durationLabel
.
isHidden
=
true
}
private
func
configureForVideo
(
_
mediaItem
:
YHSelectMediaItem
)
{
// 显示视频相关UI
playIcon
.
isHidden
=
false
// 生成视频缩略图
if
let
videoURL
=
mediaItem
.
videoURL
{
generateVideoThumbnail
(
from
:
videoURL
)
{
[
weak
self
]
thumbnail
in
DispatchQueue
.
main
.
async
{
self
?
.
imageView
.
image
=
thumbnail
}
}
}
// 显示视频时长
// if let duration = mediaItem.duration {
// durationLabel.text = formatDuration(duration)
// durationLabel.isHidden = false
// } else if let videoURL = mediaItem.videoURL {
// // 如果没有时长信息,尝试获取
// getVideoDuration(from: videoURL) { [weak self] duration in
// DispatchQueue.main.async {
// if duration > 0 {
// self?.durationLabel.text = self?.formatDuration(duration)
// self?.durationLabel.isHidden = false
// }
// }
// }
// }
}
// MARK: - Video Helpers
private
func
generateVideoThumbnail
(
from
url
:
URL
,
completion
:
@escaping
(
UIImage
?)
->
Void
)
{
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
let
asset
=
AVAsset
(
url
:
url
)
let
imageGenerator
=
AVAssetImageGenerator
(
asset
:
asset
)
imageGenerator
.
appliesPreferredTrackTransform
=
true
imageGenerator
.
maximumSize
=
CGSize
(
width
:
300
,
height
:
300
)
let
time
=
CMTime
(
seconds
:
0.0
,
preferredTimescale
:
600
)
do
{
let
cgImage
=
try
imageGenerator
.
copyCGImage
(
at
:
time
,
actualTime
:
nil
)
let
thumbnail
=
UIImage
(
cgImage
:
cgImage
)
completion
(
thumbnail
)
}
catch
{
print
(
"生成视频缩略图失败:
\(
error
)
"
)
completion
(
nil
)
}
}
}
private
func
getVideoDuration
(
from
url
:
URL
,
completion
:
@escaping
(
TimeInterval
)
->
Void
)
{
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
let
asset
=
AVAsset
(
url
:
url
)
let
duration
=
CMTimeGetSeconds
(
asset
.
duration
)
completion
(
duration
.
isNaN
?
0
:
duration
)
}
}
private
func
formatDuration
(
_
duration
:
TimeInterval
)
->
String
{
let
totalSeconds
=
Int
(
duration
)
let
minutes
=
totalSeconds
/
60
let
seconds
=
totalSeconds
%
60
if
minutes
>
0
{
return
String
(
format
:
"%d:%02d"
,
minutes
,
seconds
)
}
else
{
return
String
(
format
:
"0:%02d"
,
seconds
)
}
}
// MARK: - Actions
@objc
private
func
deleteButtonTapped
()
{
deleteCallback
?()
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCirclePhotoCell.swift
0 → 100644
View file @
5e650735
//
// YHCirclePhotoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
// MARK: - Custom Cells
class
YHCirclePhotoCell
:
UICollectionViewCell
{
var
deleteCallback
:
(()
->
Void
)?
private
lazy
var
imageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
contentMode
=
.
scaleAspectFill
imageView
.
clipsToBounds
=
true
return
imageView
}()
private
lazy
var
deleteButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
systemName
:
"xmark.circle.fill"
),
for
:
.
normal
)
button
.
tintColor
=
.
white
button
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
button
.
layer
.
cornerRadius
=
10
button
.
addTarget
(
self
,
action
:
#selector(
deleteButtonTapped
)
,
for
:
.
touchUpInside
)
button
.
isHidden
=
true
return
button
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
setupUI
()
{
contentView
.
addSubview
(
imageView
)
contentView
.
addSubview
(
deleteButton
)
imageView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
right
.
equalToSuperview
()
.
inset
(
4
)
make
.
width
.
height
.
equalTo
(
20
)
}
}
func
configure
(
with
image
:
UIImage
)
{
imageView
.
image
=
image
}
@objc
private
func
deleteButtonTapped
()
{
deleteCallback
?()
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHMediaUploadSheetView.swift
0 → 100644
View file @
5e650735
//
// YHMediaUploadSheetView.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
/*
【Usage】
YHMediaUploadSheetView.sheetView().show { [weak self] mediaItems in
guard let self = self else { return }
print("获得 \(mediaItems.count) 个媒体文件")
// 处理选中的媒体文件
}
*/
import
UIKit
import
Photos
import
PhotosUI
import
AVFoundation
import
MobileCoreServices
import
SnapKit
enum
YHMediaUploadType
:
Int
{
case
camera
=
1
// 拍照/录像
case
photoLibrary
=
2
// 相册选择
case
cancel
=
3
// 取消
}
class
YHMediaUploadItem
{
var
type
:
YHMediaUploadType
var
title
:
String
init
(
type
:
YHMediaUploadType
,
title
:
String
)
{
self
.
type
=
type
self
.
title
=
title
}
}
class
YHMediaUploadSheetView
:
UIView
{
// 决定最终媒体选择数量最大值
var
maxSelectCount
:
Int
=
9
var
uploadTypeArr
=
[
YHMediaUploadItem
(
type
:
.
camera
,
title
:
"拍照"
),
YHMediaUploadItem
(
type
:
.
photoLibrary
,
title
:
"从手机相册选择"
),
YHMediaUploadItem
(
type
:
.
cancel
,
title
:
"取消"
)
]
// 上传媒体回调
var
uploadMediaBlock
:
(([
YHSelectMediaItem
])
->
Void
)?
private
var
needSelectVideo
=
false
lazy
var
blackMaskView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
(
hex
:
0x0F1214
,
alpha
:
0.5
)
let
tap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
dismiss
)
)
view
.
addGestureRecognizer
(
tap
)
return
view
}()
lazy
var
whiteContentView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
lazy
var
tableView
:
UITableView
=
{
let
tableView
=
UITableView
(
frame
:
.
zero
,
style
:
.
grouped
)
if
#available(iOS 11.0, *)
{
tableView
.
contentInsetAdjustmentBehavior
=
.
never
}
tableView
.
showsVerticalScrollIndicator
=
false
tableView
.
separatorStyle
=
.
none
tableView
.
delegate
=
self
tableView
.
dataSource
=
self
tableView
.
backgroundColor
=
.
white
tableView
.
isScrollEnabled
=
false
tableView
.
register
(
YHMediaUploadTypeCell
.
self
,
forCellReuseIdentifier
:
YHMediaUploadTypeCell
.
cellReuseIdentifier
)
return
tableView
}()
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
createUI
()
}
static
func
sheetView
()
->
YHMediaUploadSheetView
{
let
view
=
YHMediaUploadSheetView
(
frame
:
UIScreen
.
main
.
bounds
)
return
view
}
func
createUI
()
{
self
.
addSubview
(
blackMaskView
)
self
.
addSubview
(
whiteContentView
)
whiteContentView
.
addSubview
(
tableView
)
blackMaskView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
// 计算内容高度:2个section,第一个section 2行,第二个section 1行,加上section间距和安全区域
let
contentHeight
=
54.0
*
3
+
8.0
+
k_Height_safeAreaInsetsBottom
()
whiteContentView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
bottom
.
equalToSuperview
()
make
.
height
.
equalTo
(
contentHeight
)
}
tableView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
}
}
extension
YHMediaUploadSheetView
{
func
show
(
needSelectVideo
:
Bool
=
false
,
completion
:
@escaping
([
YHSelectMediaItem
])
->
Void
)
{
self
.
needSelectVideo
=
needSelectVideo
self
.
uploadMediaBlock
=
completion
UIApplication
.
shared
.
yhKeyWindow
()?
.
addSubview
(
self
)
}
@objc
func
dismiss
()
{
self
.
removeFromSuperview
()
}
}
// MARK: - TableView DataSource & Delegate
extension
YHMediaUploadSheetView
:
UITableViewDelegate
,
UITableViewDataSource
{
func
numberOfSections
(
in
tableView
:
UITableView
)
->
Int
{
return
2
}
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
if
section
==
0
{
return
2
// 拍照 + 从手机相册选择
}
else
{
return
1
// 取消
}
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
guard
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
YHMediaUploadTypeCell
.
cellReuseIdentifier
,
for
:
indexPath
)
as?
YHMediaUploadTypeCell
else
{
return
UITableViewCell
()
}
var
itemIndex
=
0
if
indexPath
.
section
==
0
{
itemIndex
=
indexPath
.
row
}
else
{
itemIndex
=
2
// 取消按钮
}
if
itemIndex
<
uploadTypeArr
.
count
{
cell
.
item
=
uploadTypeArr
[
itemIndex
]
// 第一个section的第一行显示分割线
if
indexPath
.
section
==
0
&&
indexPath
.
row
==
0
{
cell
.
showSeparator
=
true
}
else
{
cell
.
showSeparator
=
false
}
}
return
cell
}
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
tableView
.
deselectRow
(
at
:
indexPath
,
animated
:
true
)
var
itemIndex
=
0
if
indexPath
.
section
==
0
{
itemIndex
=
indexPath
.
row
}
else
{
itemIndex
=
2
// 取消按钮
}
if
itemIndex
<
uploadTypeArr
.
count
{
let
operationItem
=
uploadTypeArr
[
itemIndex
]
if
operationItem
.
type
==
.
cancel
{
dismiss
()
}
else
if
operationItem
.
type
==
.
photoLibrary
{
selectMediaFromPhotoLibrary
()
}
else
if
operationItem
.
type
==
.
camera
{
showCameraWithBothModes
()
}
}
}
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
return
54.0
}
func
tableView
(
_
tableView
:
UITableView
,
heightForHeaderInSection
section
:
Int
)
->
CGFloat
{
return
section
==
0
?
0.01
:
8.0
}
func
tableView
(
_
tableView
:
UITableView
,
heightForFooterInSection
section
:
Int
)
->
CGFloat
{
return
0.01
}
func
tableView
(
_
tableView
:
UITableView
,
viewForHeaderInSection
section
:
Int
)
->
UIView
?
{
if
section
==
0
{
let
view
=
UIView
()
view
.
backgroundColor
=
nil
return
view
}
else
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
brandGrayColor3
return
view
}
}
func
tableView
(
_
tableView
:
UITableView
,
viewForFooterInSection
section
:
Int
)
->
UIView
?
{
return
UIView
()
}
}
// MARK: - Camera & Photo Library
extension
YHMediaUploadSheetView
{
// 显示相机(拍照和录像在同一界面)
func
showCameraWithBothModes
()
{
checkCameraPermission
{
[
weak
self
]
in
self
?
.
presentCameraWithBothModes
()
}
}
// 从相册选择
func
selectMediaFromPhotoLibrary
()
{
checkPhotoLibraryPermission
{
[
weak
self
]
in
self
?
.
presentPhotoLibraryPicker
()
}
}
// 展示相机(同时支持拍照和录像)
func
presentCameraWithBothModes
()
{
guard
UIImagePickerController
.
isSourceTypeAvailable
(
.
camera
)
else
{
YHHUD
.
flash
(
message
:
"设备不支持相机功能"
)
return
}
let
picker
=
UIImagePickerController
()
picker
.
delegate
=
self
picker
.
sourceType
=
.
camera
// 同时支持拍照和录像
var
mediaTypes
:
[
String
]
=
[]
// 检查是否支持拍照和录像
if
let
availableTypes
=
UIImagePickerController
.
availableMediaTypes
(
for
:
.
camera
)
{
if
availableTypes
.
contains
(
kUTTypeImage
as
String
)
{
mediaTypes
.
append
(
kUTTypeImage
as
String
)
}
if
needSelectVideo
,
availableTypes
.
contains
(
kUTTypeMovie
as
String
)
{
mediaTypes
.
append
(
kUTTypeMovie
as
String
)
}
}
guard
!
mediaTypes
.
isEmpty
else
{
YHHUD
.
flash
(
message
:
"设备不支持拍照或录像功能"
)
return
}
picker
.
mediaTypes
=
mediaTypes
// 设置视频录制参数
picker
.
videoQuality
=
.
typeMedium
picker
.
videoMaximumDuration
=
60.0
// 60秒
// 默认是拍照模式,用户可以在界面中切换到录像
picker
.
cameraCaptureMode
=
.
photo
picker
.
allowsEditing
=
true
UIViewController
.
current
?
.
present
(
picker
,
animated
:
true
)
}
// 展示相册选择器
func
presentPhotoLibraryPicker
()
{
if
#available(iOS 14.0, *)
{
// iOS 14+ 使用 PHPickerViewController,支持多选
var
configuration
=
PHPickerConfiguration
()
configuration
.
selectionLimit
=
maxSelectCount
// 同时支持图片和视频
configuration
.
filter
=
needSelectVideo
?
.
any
(
of
:
[
.
images
,
.
videos
])
:
.
any
(
of
:
[
.
images
])
let
picker
=
PHPickerViewController
(
configuration
:
configuration
)
picker
.
delegate
=
self
UIViewController
.
current
?
.
present
(
picker
,
animated
:
true
)
}
else
{
// iOS 14以下使用 UIImagePickerController,只能单选
if
UIImagePickerController
.
isSourceTypeAvailable
(
.
photoLibrary
)
{
let
imagePicker
=
UIImagePickerController
()
imagePicker
.
delegate
=
self
imagePicker
.
sourceType
=
.
photoLibrary
imagePicker
.
mediaTypes
=
needSelectVideo
?
[
kUTTypeImage
as
String
,
kUTTypeMovie
as
String
]
:
[
kUTTypeImage
as
String
]
UIViewController
.
current
?
.
present
(
imagePicker
,
animated
:
true
)
}
}
}
}
// MARK: - 权限检查
extension
YHMediaUploadSheetView
{
func
checkCameraPermission
(
completion
:
@escaping
()
->
Void
)
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
completion
()
case
.
notDetermined
:
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
granted
in
DispatchQueue
.
main
.
async
{
if
granted
{
completion
()
}
else
{
YHHUD
.
flash
(
message
:
"需要相机权限才能拍照"
)
}
}
}
case
.
denied
,
.
restricted
:
YHHUD
.
flash
(
message
:
"请在设置中打开相机权限"
)
@unknown
default
:
YHHUD
.
flash
(
message
:
"无法获取相机权限"
)
}
}
func
checkPhotoLibraryPermission
(
completion
:
@escaping
()
->
Void
)
{
let
photoAuthStatus
=
PHPhotoLibrary
.
authorizationStatus
()
switch
photoAuthStatus
{
case
.
authorized
,
.
limited
:
completion
()
case
.
notDetermined
:
PHPhotoLibrary
.
requestAuthorization
{
status
in
DispatchQueue
.
main
.
async
{
if
status
==
.
authorized
||
status
==
.
limited
{
completion
()
}
else
{
YHHUD
.
flash
(
message
:
"需要相册权限才能选择照片"
)
}
}
}
case
.
denied
,
.
restricted
:
YHHUD
.
flash
(
message
:
"请在设置中打开相册权限"
)
@unknown
default
:
YHHUD
.
flash
(
message
:
"无法获取相册权限"
)
}
}
}
// MARK: - UIImagePickerControllerDelegate
extension
YHMediaUploadSheetView
:
UIImagePickerControllerDelegate
,
UINavigationControllerDelegate
{
func
imagePickerController
(
_
picker
:
UIImagePickerController
,
didFinishPickingMediaWithInfo
info
:
[
UIImagePickerController
.
InfoKey
:
Any
])
{
guard
let
mediaType
=
info
[
.
mediaType
]
as?
String
else
{
picker
.
dismiss
(
animated
:
true
)
return
}
var
mediaItems
:
[
YHSelectMediaItem
]
=
[]
if
mediaType
==
kUTTypeImage
as
String
{
// 处理图片
if
let
image
=
info
[
.
originalImage
]
as?
UIImage
{
var
imageName
=
""
if
let
imageUrl
=
info
[
.
imageURL
]
as?
URL
{
imageName
=
imageUrl
.
lastPathComponent
}
if
imageName
.
isEmpty
{
let
timestamp
=
Date
()
.
timeIntervalSince1970
imageName
=
"
\(
timestamp
)
.jpg"
.
replacingOccurrences
(
of
:
"."
,
with
:
""
)
}
let
item
=
YHSelectMediaItem
(
name
:
imageName
,
type
:
.
image
,
image
:
image
)
mediaItems
.
append
(
item
)
}
}
else
if
mediaType
==
kUTTypeMovie
as
String
{
// 处理视频
if
let
videoURL
=
info
[
.
mediaURL
]
as?
URL
{
let
videoName
=
videoURL
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
videoName
,
type
:
.
video
,
videoURL
:
videoURL
)
mediaItems
.
append
(
item
)
}
}
picker
.
dismiss
(
animated
:
true
)
{
self
.
uploadMediaBlock
?(
mediaItems
)
self
.
dismiss
()
}
}
func
imagePickerControllerDidCancel
(
_
picker
:
UIImagePickerController
)
{
picker
.
dismiss
(
animated
:
true
)
}
}
// MARK: - PHPickerViewControllerDelegate
@available(iOS 14.0, *)
extension
YHMediaUploadSheetView
:
PHPickerViewControllerDelegate
{
func
picker
(
_
picker
:
PHPickerViewController
,
didFinishPicking
results
:
[
PHPickerResult
])
{
picker
.
dismiss
(
animated
:
true
)
self
.
dismiss
()
if
results
.
count
<=
0
{
return
}
let
group
=
DispatchGroup
()
// 使用数组保持顺序,用可选类型处理失败情况
var
mediaItems
:
[
YHSelectMediaItem
?]
=
Array
(
repeating
:
nil
,
count
:
results
.
count
)
YHHUD
.
show
(
.
progress
(
message
:
"处理中..."
))
for
(
index
,
result
)
in
results
.
enumerated
()
{
group
.
enter
()
// 检查是否是图片
if
result
.
itemProvider
.
hasItemConformingToTypeIdentifier
(
UTType
.
image
.
identifier
)
{
result
.
itemProvider
.
loadFileRepresentation
(
forTypeIdentifier
:
UTType
.
image
.
identifier
)
{
url
,
_
in
defer
{
group
.
leave
()
}
if
let
url
=
url
,
let
imageData
=
try
?
Data
(
contentsOf
:
url
),
let
image
=
UIImage
(
data
:
imageData
)
{
let
imageName
=
url
.
lastPathComponent
.
isEmpty
?
"
\(
Date
()
.
timeIntervalSince1970
)
.jpg"
:
url
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
imageName
,
type
:
.
image
,
image
:
image
)
mediaItems
[
index
]
=
item
}
}
}
// 检查是否是视频
else
if
result
.
itemProvider
.
hasItemConformingToTypeIdentifier
(
UTType
.
movie
.
identifier
)
{
result
.
itemProvider
.
loadFileRepresentation
(
forTypeIdentifier
:
UTType
.
movie
.
identifier
)
{
url
,
error
in
defer
{
group
.
leave
()
}
if
let
url
=
url
{
// 复制视频文件到临时目录
let
tempURL
=
URL
(
fileURLWithPath
:
NSTemporaryDirectory
())
.
appendingPathComponent
(
url
.
lastPathComponent
)
do
{
if
FileManager
.
default
.
fileExists
(
atPath
:
tempURL
.
path
)
{
try
FileManager
.
default
.
removeItem
(
at
:
tempURL
)
}
try
FileManager
.
default
.
copyItem
(
at
:
url
,
to
:
tempURL
)
let
videoName
=
tempURL
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
videoName
,
type
:
.
video
,
videoURL
:
tempURL
)
mediaItems
[
index
]
=
item
}
catch
{
printLog
(
"视频文件复制失败:
\(
error
)
"
)
// 失败时 mediaItems[index] 保持为 nil
}
}
}
}
else
{
// 不支持的类型,直接 leave
group
.
leave
()
}
}
// 等待所有任务完成
group
.
notify
(
queue
:
.
main
)
{
YHHUD
.
hide
()
// 过滤掉失败的项目,保持成功项目的相对顺序
let
successfulItems
=
mediaItems
.
compactMap
{
$0
}
self
.
uploadMediaBlock
?(
successfulItems
)
}
}
}
// MARK: - YHMediaUploadTypeCell
class
YHMediaUploadTypeCell
:
UITableViewCell
{
static
let
cellReuseIdentifier
=
"YHMediaUploadTypeCell"
var
showSeparator
:
Bool
=
false
{
didSet
{
separatorView
.
isHidden
=
!
showSeparator
}
}
var
item
:
YHMediaUploadItem
?
{
didSet
{
guard
let
item
=
item
else
{
return
}
titleLabel
.
text
=
item
.
title
// 根据类型设置样式
if
item
.
type
==
.
cancel
{
titleLabel
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
15
)
}
else
{
titleLabel
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
15
)
}
}
}
lazy
var
titleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
15
)
label
.
textColor
=
UIColor
.
brandGrayColor8
label
.
textAlignment
=
.
center
return
label
}()
lazy
var
separatorView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
separator
view
.
isHidden
=
true
return
view
}()
override
init
(
style
:
UITableViewCell
.
CellStyle
,
reuseIdentifier
:
String
?)
{
super
.
init
(
style
:
style
,
reuseIdentifier
:
reuseIdentifier
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
func
setupUI
()
{
selectionStyle
=
.
none
backgroundColor
=
.
white
contentView
.
addSubview
(
titleLabel
)
contentView
.
addSubview
(
separatorView
)
titleLabel
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
}
separatorView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
make
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
make
.
height
.
equalTo
(
0.5
)
}
}
}
galaxy/galaxy/Classes/Modules/IntelligentService(服务中心)/QMAS(优才)/MyCertificates(我的证书)/V/YHCertificateUploadSheetView.swift
View file @
5e650735
...
@@ -405,8 +405,31 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
...
@@ -405,8 +405,31 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
return
false
return
false
}
}
private
func
getCaptureDeviceAuthorization
(
notDeterminedBlock
:
(()
->
Void
)?)
->
Bool
?
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
// 已授权,直接使用相机
return
true
case
.
notDetermined
:
// 未请求过权限,主动请求
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
_
in
notDeterminedBlock
?()
}
return
nil
case
.
denied
,
.
restricted
:
// 权限被拒绝或受限
break
@unknown
default
:
break
}
return
false
}
func
takePhoto
()
{
func
takePhoto
()
{
guard
let
authorization
=
get
PhotoLibrary
Authorization
(
notDeterminedBlock
:
{
guard
let
authorization
=
get
CaptureDevice
Authorization
(
notDeterminedBlock
:
{
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
self
.
takePhoto
()
self
.
takePhoto
()
}
}
...
@@ -415,7 +438,7 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
...
@@ -415,7 +438,7 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
}
}
if
!
authorization
{
if
!
authorization
{
YHHUD
.
flash
(
message
:
"请在设置中打开相
册
权限"
)
YHHUD
.
flash
(
message
:
"请在设置中打开相
机
权限"
)
return
return
}
}
...
...
galaxy/galaxy/Classes/Modules/IntelligentService(服务中心)/QMAS(优才)/MyDocuments(我的文书)/V/YHDocumentUploadView.swift
View file @
5e650735
...
@@ -310,8 +310,31 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
...
@@ -310,8 +310,31 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
return
false
return
false
}
}
private
func
getCaptureDeviceAuthorization
(
notDeterminedBlock
:
(()
->
Void
)?)
->
Bool
?
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
// 已授权,直接使用相机
return
true
case
.
notDetermined
:
// 未请求过权限,主动请求
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
_
in
notDeterminedBlock
?()
}
return
nil
case
.
denied
,
.
restricted
:
// 权限被拒绝或受限
break
@unknown
default
:
break
}
return
false
}
func
takePhoto
()
{
func
takePhoto
()
{
guard
let
authorization
=
get
PhotoLibrary
Authorization
(
notDeterminedBlock
:
{
guard
let
authorization
=
get
CaptureDevice
Authorization
(
notDeterminedBlock
:
{
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
self
.
takePhoto
()
self
.
takePhoto
()
}
}
...
@@ -320,7 +343,7 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
...
@@ -320,7 +343,7 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
}
}
if
!
authorization
{
if
!
authorization
{
YHHUD
.
flash
(
message
:
"请在设置中打开相
册
权限"
)
YHHUD
.
flash
(
message
:
"请在设置中打开相
机
权限"
)
return
return
}
}
...
...
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHMediaBrowserViewController.swift
0 → 100644
View file @
5e650735
//
// YHMediaBrowserViewController.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
Kingfisher
import
UIKit
import
JXPhotoBrowser
import
Photos
import
PhotosUI
class
YHMediaBrowserViewController
:
JXPhotoBrowser
{
var
deleteMediaBlock
:
((
Int
)
->
Void
)?
var
dismissBlock
:
(()
->
Void
)?
lazy
var
navBar
:
UIView
=
{
let
v
=
UIView
()
let
backBtn
=
UIButton
()
backBtn
.
setImage
(
UIImage
(
named
:
"nav_back_white"
),
for
:
.
normal
)
backBtn
.
addTarget
(
self
,
action
:
#selector(
didBackBtnClicked
)
,
for
:
.
touchUpInside
)
v
.
addSubview
(
backBtn
)
let
deleteBtn
=
UIButton
()
let
img
=
UIImage
(
named
:
"media_brower_delete"
)
let
templateImage
=
img
?
.
withRenderingMode
(
.
alwaysTemplate
)
deleteBtn
.
setImage
(
templateImage
,
for
:
.
normal
)
deleteBtn
.
imageView
?
.
tintColor
=
.
white
deleteBtn
.
addTarget
(
self
,
action
:
#selector(
didDeleteBtnClicked
)
,
for
:
.
touchUpInside
)
v
.
addSubview
(
deleteBtn
)
backBtn
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
44
)
make
.
left
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
deleteBtn
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
44
)
make
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
return
v
}()
@objc
func
didBackBtnClicked
()
{
dismiss
()
}
@objc
func
didDeleteBtnClicked
()
{
let
index
=
self
.
browserView
.
pageIndex
deleteMediaBlock
?(
index
)
}
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
view
.
addSubview
(
navBar
)
navBar
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
top
.
equalToSuperview
()
make
.
height
.
equalTo
(
k_Height_NavigationtBarAndStatuBar
)
}
}
deinit
{
printLog
(
"YHMediaBrowserViewController deinit"
)
}
override
func
dismiss
()
{
super
.
dismiss
()
dismissBlock
?()
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHPictureReviewManager.swift
View file @
5e650735
...
@@ -13,28 +13,28 @@ import SDWebImage
...
@@ -13,28 +13,28 @@ import SDWebImage
class
YHPictureReviewManager
:
NSObject
{
class
YHPictureReviewManager
:
NSObject
{
static
let
shared
=
YHPictureReviewManager
()
static
let
shared
=
YHPictureReviewManager
()
override
init
()
{
override
init
()
{
super
.
init
()
super
.
init
()
}
}
private
var
curIndex
:
Int
=
0
private
var
curIndex
:
Int
=
0
private
var
arrPics
:
[
String
]
=
[]
private
var
arrPics
:
[
String
]
=
[]
private
var
arrPreviewItems
:
[
YHPreviewMediaItem
]
=
[]
}
}
extension
YHPictureReviewManager
{
extension
YHPictureReviewManager
{
func
showNetWorkPicturs
(
curIndex
:
Int
,
arrPicturs
:
[
String
])
{
func
showNetWorkPicturs
(
curIndex
:
Int
,
arrPicturs
:
[
String
])
{
guard
curIndex
>
-
1
,
arrPicturs
.
count
>
0
else
{
return
}
guard
curIndex
>
-
1
,
arrPicturs
.
count
>
0
else
{
return
}
self
.
curIndex
=
curIndex
self
.
curIndex
=
curIndex
self
.
arrPics
=
arrPicturs
arrPics
=
arrPicturs
let
browser
=
YHPictureBrowserViewController
()
let
browser
=
YHPictureBrowserViewController
()
browser
.
numberOfItems
=
{
browser
.
numberOfItems
=
{
self
.
arrPics
.
count
self
.
arrPics
.
count
}
}
browser
.
reloadCellAtIndex
=
{
context
in
browser
.
reloadCellAtIndex
=
{
context
in
if
context
.
index
>=
self
.
arrPics
.
count
{
if
context
.
index
>=
self
.
arrPics
.
count
{
return
return
...
@@ -44,16 +44,16 @@ extension YHPictureReviewManager {
...
@@ -44,16 +44,16 @@ extension YHPictureReviewManager {
let
browserCell
=
context
.
cell
as?
JXPhotoBrowserImageCell
let
browserCell
=
context
.
cell
as?
JXPhotoBrowserImageCell
browserCell
?
.
index
=
context
.
index
browserCell
?
.
index
=
context
.
index
let
placeholder
=
UIImage
(
named
:
"global_default_image"
)
let
placeholder
=
UIImage
(
named
:
"global_default_image"
)
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
(
_
,
_
,
_
,
_
)
in
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
_
,
_
,
_
,
_
in
browserCell
?
.
setNeedsLayout
()
browserCell
?
.
setNeedsLayout
()
})
})
// 添加长按事件
// 添加长按事件
browserCell
?
.
longPressedAction
=
{
cell
,
_
in
browserCell
?
.
longPressedAction
=
{
cell
,
_
in
self
.
longPress
(
cell
:
cell
)
self
.
longPress
(
cell
:
cell
)
}
}
}
}
browser
.
getImgUrlBlock
=
{
[
weak
self
]
index
in
browser
.
getImgUrlBlock
=
{
[
weak
self
]
index
in
guard
let
self
=
self
else
{
return
""
}
guard
let
self
=
self
else
{
return
""
}
if
0
<=
index
,
index
<
self
.
arrPics
.
count
{
if
0
<=
index
,
index
<
self
.
arrPics
.
count
{
...
@@ -61,22 +61,245 @@ extension YHPictureReviewManager {
...
@@ -61,22 +61,245 @@ extension YHPictureReviewManager {
}
}
return
""
return
""
}
}
// 数字样式的页码指示器
// 数字样式的页码指示器
browser
.
pageIndicator
=
JXPhotoBrowserNumberPageIndicator
()
browser
.
pageIndicator
=
JXPhotoBrowserNumberPageIndicator
()
browser
.
pageIndex
=
self
.
curIndex
browser
.
pageIndex
=
self
.
curIndex
browser
.
show
()
browser
.
show
()
}
}
}
}
extension
YHPictureReviewManager
{
extension
YHPictureReviewManager
{
private
func
longPress
(
cell
:
JXPhotoBrowserImageCell
)
{
private
func
longPress
(
cell
:
JXPhotoBrowserImageCell
)
{
let
index
=
cell
.
index
let
index
=
cell
.
index
if
index
<
self
.
arrPics
.
count
,
index
>
-
1
{
if
index
<
arrPics
.
count
,
index
>
-
1
{
let
view
=
YHLongtapPictureSheetView
.
sheetView
()
let
view
=
YHLongtapPictureSheetView
.
sheetView
()
view
.
myUrl
=
self
.
arrPics
[
index
]
view
.
myUrl
=
arrPics
[
index
]
view
.
show
()
view
.
show
()
}
}
}
}
private
func
longPressMedia
(
cell
:
JXPhotoBrowserImageCell
)
{
let
index
=
cell
.
index
if
index
<
arrPreviewItems
.
count
,
index
>
-
1
{
let
view
=
YHLongtapPictureSheetView
.
sheetView
()
let
item
=
arrPreviewItems
[
index
]
if
item
.
type
==
.
image
{
switch
item
.
source
{
case
let
.
remoteURL
(
url
):
view
.
myUrl
=
url
view
.
show
()
case
.
localImage
:
break
case
.
localVideoURL
:
break
}
}
}
}
}
// MARK: - 新的混合媒体预览方法
extension
YHPictureReviewManager
{
/// 显示本地媒体预览(从 YHSelectMediaItem 数组)
/// - Parameters:
/// - curIndex: 当前索引
/// - selectMediaItems: 本地媒体项数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showLocalMedia
(
curIndex
:
Int
,
selectMediaItems
:
[
YHSelectMediaItem
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
let
previewItems
=
selectMediaItems
.
map
{
YHPreviewMediaItem
(
from
:
$0
)
}
showPreviewMedia
(
curIndex
:
curIndex
,
previewItems
:
previewItems
,
deleteCallback
:
deleteCallback
)
}
/// 显示远程媒体预览(从URL字符串数组,自动检测类型)
/// - Parameters:
/// - curIndex: 当前索引
/// - urlStrings: URL字符串数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showRemoteMedia
(
curIndex
:
Int
,
urlStrings
:
[
String
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
let
previewItems
=
urlStrings
.
map
{
urlString
->
YHPreviewMediaItem
in
let
type
=
detectMediaType
(
from
:
urlString
)
return
YHPreviewMediaItem
(
remoteURL
:
urlString
,
type
:
type
)
}
showPreviewMedia
(
curIndex
:
curIndex
,
previewItems
:
previewItems
,
deleteCallback
:
deleteCallback
)
}
/// 显示混合媒体预览(手动指定每个项目)
/// - Parameters:
/// - curIndex: 当前索引
/// - previewItems: 预览媒体项数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showPreviewMedia
(
curIndex
:
Int
,
previewItems
:
[
YHPreviewMediaItem
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
guard
curIndex
>
-
1
,
previewItems
.
count
>
0
else
{
return
}
self
.
curIndex
=
curIndex
arrPreviewItems
=
previewItems
let
browser
=
YHMediaBrowserViewController
()
browser
.
numberOfItems
=
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
0
}
return
self
.
arrPreviewItems
.
count
}
// 根据媒体类型返回对应的Cell类
browser
.
cellClassAtIndex
=
{
[
weak
self
]
index
in
guard
let
self
=
self
else
{
return
JXPhotoBrowserImageCell
.
self
}
guard
index
<
self
.
arrPreviewItems
.
count
else
{
return
JXPhotoBrowserImageCell
.
self
}
let
previewItem
=
self
.
arrPreviewItems
[
index
]
return
previewItem
.
type
==
.
video
?
YHVideoCell
.
self
:
JXPhotoBrowserImageCell
.
self
}
browser
.
reloadCellAtIndex
=
{
[
weak
self
]
context
in
guard
let
self
=
self
else
{
return
}
if
context
.
index
>=
self
.
arrPreviewItems
.
count
{
return
}
let
previewItem
=
self
.
arrPreviewItems
[
context
.
index
]
if
previewItem
.
type
==
.
video
{
// 视频Cell
let
browserCell
=
context
.
cell
as?
YHVideoCell
switch
previewItem
.
source
{
case
let
.
remoteURL
(
urlString
):
if
let
url
=
URL
(
string
:
urlString
)
{
browserCell
?
.
loadVideo
(
from
:
url
)
}
case
let
.
localVideoURL
(
url
):
browserCell
?
.
loadVideo
(
from
:
url
)
case
.
localImage
:
break
// 视频不应该是本地图片
}
}
else
{
// 图片Cell
let
browserCell
=
context
.
cell
as?
JXPhotoBrowserImageCell
browserCell
?
.
index
=
context
.
index
let
placeholder
=
UIImage
(
named
:
"global_default_image"
)
switch
previewItem
.
source
{
case
let
.
remoteURL
(
urlString
):
if
let
url
=
URL
(
string
:
urlString
)
{
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
_
,
_
,
_
,
_
in
browserCell
?
.
setNeedsLayout
()
})
}
case
let
.
localImage
(
image
):
browserCell
?
.
imageView
.
image
=
image
browserCell
?
.
setNeedsLayout
()
case
.
localVideoURL
:
break
// 图片不应该是视频URL
}
// 添加长按事件(仅图片)
browserCell
?
.
longPressedAction
=
{
[
weak
self
]
cell
,
_
in
guard
let
self
=
self
else
{
return
}
self
.
longPressMedia
(
cell
:
cell
)
}
}
}
// 视频播放控制
browser
.
cellWillAppear
=
{
[
weak
self
]
cell
,
index
in
guard
let
self
=
self
else
{
return
}
if
index
<
self
.
arrPreviewItems
.
count
{
let
previewItem
=
self
.
arrPreviewItems
[
index
]
if
previewItem
.
type
==
.
video
{
(
cell
as?
YHVideoCell
)?
.
playVideo
()
}
}
}
browser
.
cellWillDisappear
=
{
[
weak
self
]
cell
,
index
in
guard
let
self
=
self
else
{
return
}
if
index
<
self
.
arrPreviewItems
.
count
{
let
previewItem
=
self
.
arrPreviewItems
[
index
]
if
previewItem
.
type
==
.
video
{
(
cell
as?
YHVideoCell
)?
.
stopVideo
()
}
}
}
// 完善的删除逻辑
browser
.
deleteMediaBlock
=
{
[
weak
self
,
weak
browser
]
index
in
guard
let
self
=
self
else
{
return
}
// 删除数据
self
.
arrPreviewItems
.
remove
(
at
:
index
)
deleteCallback
?(
index
)
// 重新显示浏览器
if
!
self
.
arrPreviewItems
.
isEmpty
{
browser
?
.
dismiss
(
animated
:
false
)
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
let
newIndex
=
index
<
self
.
arrPreviewItems
.
count
?
index
:
self
.
arrPreviewItems
.
count
-
1
self
.
showPreviewMedia
(
curIndex
:
newIndex
,
previewItems
:
self
.
arrPreviewItems
,
deleteCallback
:
deleteCallback
)
}
}
else
{
browser
?
.
dismiss
(
animated
:
true
)
self
.
clearData
()
}
}
browser
.
dismissBlock
=
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
self
.
clearData
()
}
// 数字样式的页码指示器
browser
.
pageIndicator
=
JXPhotoBrowserNumberPageIndicator
()
browser
.
pageIndex
=
self
.
curIndex
browser
.
show
()
}
private
func
clearData
()
{
arrPreviewItems
.
removeAll
()
}
/// 根据URL检测媒体类型
/// - Parameter urlString: URL字符串
/// - Returns: 媒体类型
private
func
detectMediaType
(
from
urlString
:
String
)
->
YHMediaType
{
let
lowercasedUrl
=
urlString
.
lowercased
()
// 视频文件扩展名
let
videoExtensions
=
[
"mp4"
,
"mov"
,
"avi"
,
"mkv"
,
"wmv"
,
"flv"
,
"webm"
,
"m4v"
]
// 检查URL是否包含视频扩展名
for
ext
in
videoExtensions
{
if
lowercasedUrl
.
contains
(
".
\(
ext
)
"
)
{
return
.
video
}
}
// 默认为图片
return
.
image
}
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHPreviewMediaItem.swift
0 → 100644
View file @
5e650735
//
// YHPreviewMediaItem.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
// 预览媒体项 - 专门用于预览的数据模型
struct
YHPreviewMediaItem
{
let
type
:
YHMediaType
let
source
:
YHMediaSource
enum
YHMediaSource
{
case
remoteURL
(
String
)
// 远程URL
case
localImage
(
UIImage
)
// 本地图片
case
localVideoURL
(
URL
)
// 本地视频文件URL
var
displayURL
:
String
{
switch
self
{
case
.
remoteURL
(
let
url
):
return
url
case
.
localImage
:
return
""
// 本地图片不需要URL
case
.
localVideoURL
(
let
url
):
return
url
.
absoluteString
}
}
}
// 从远程URL创建
init
(
remoteURL
:
String
,
type
:
YHMediaType
=
.
image
)
{
self
.
type
=
type
self
.
source
=
.
remoteURL
(
remoteURL
)
}
// 从本地图片创建
init
(
localImage
:
UIImage
)
{
self
.
type
=
.
image
self
.
source
=
.
localImage
(
localImage
)
}
// 从本地视频URL创建
init
(
localVideoURL
:
URL
)
{
self
.
type
=
.
video
self
.
source
=
.
localVideoURL
(
localVideoURL
)
}
// 从 YHSelectMediaItem 创建
init
(
from
selectMediaItem
:
YHSelectMediaItem
)
{
self
.
type
=
selectMediaItem
.
type
switch
selectMediaItem
.
type
{
case
.
image
:
if
let
image
=
selectMediaItem
.
image
{
self
.
source
=
.
localImage
(
image
)
}
else
{
// 如果没有本地图片,使用占位符
self
.
source
=
.
localImage
(
UIImage
(
named
:
"global_default_image"
)
??
UIImage
())
}
case
.
video
:
if
let
videoURL
=
selectMediaItem
.
videoURL
{
self
.
source
=
.
localVideoURL
(
videoURL
)
}
else
{
// 异常情况,创建一个空的远程URL
self
.
source
=
.
remoteURL
(
""
)
}
}
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHSelectMediaItem.swift
0 → 100644
View file @
5e650735
//
// YHSelectMediaItem.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
class
YHSelectMediaItem
{
var
name
:
String
var
type
:
YHMediaType
var
image
:
UIImage
?
var
videoURL
:
URL
?
var
duration
:
TimeInterval
?
init
(
name
:
String
=
""
,
type
:
YHMediaType
=
.
image
,
image
:
UIImage
?
=
nil
,
videoURL
:
URL
?
=
nil
,
duration
:
TimeInterval
?
=
nil
)
{
self
.
name
=
name
self
.
type
=
type
self
.
image
=
image
self
.
videoURL
=
videoURL
self
.
duration
=
duration
}
}
enum
YHMediaType
{
case
image
case
video
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHVideoCell.swift
0 → 100644
View file @
5e650735
//
// YHVideoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
Foundation
import
UIKit
import
AVFoundation
import
JXPhotoBrowser
import
SnapKit
class
YHVideoCell
:
UIView
,
JXPhotoBrowserCell
{
weak
var
photoBrowser
:
JXPhotoBrowser
?
lazy
var
player
=
AVPlayer
()
lazy
var
playerLayer
=
AVPlayerLayer
(
player
:
player
)
// 播放状态图标
lazy
var
playImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
let
playImage
=
UIImage
(
named
:
"circle_play_icon"
)?
.
withRenderingMode
(
.
alwaysOriginal
)
imageView
.
image
=
playImage
return
imageView
}()
// 进度滑块
lazy
var
progressSlider
:
UISlider
=
{
let
slider
=
UISlider
()
slider
.
minimumTrackTintColor
=
.
white
slider
.
maximumTrackTintColor
=
UIColor
.
white
.
withAlphaComponent
(
0.3
)
slider
.
thumbTintColor
=
.
white
slider
.
addTarget
(
self
,
action
:
#selector(
sliderValueChanged
)
,
for
:
.
valueChanged
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchDown
)
,
for
:
.
touchDown
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchUp
)
,
for
:
.
touchUpInside
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchUp
)
,
for
:
.
touchUpOutside
)
return
slider
}()
// 时间标签
lazy
var
timeLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
12
)
label
.
textColor
=
.
white
label
.
text
=
"00:00 / 00:00"
label
.
textAlignment
=
.
right
return
label
}()
private
var
timeObserver
:
Any
?
private
var
isPlaying
=
false
private
var
isSeeking
=
false
static
func
generate
(
with
browser
:
JXPhotoBrowser
)
->
Self
{
let
instance
=
Self
.
init
(
frame
:
.
zero
)
instance
.
photoBrowser
=
browser
return
instance
}
required
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
.
zero
)
setupUI
()
setupGestures
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
deinit
{
removeTimeObserver
()
NotificationCenter
.
default
.
removeObserver
(
self
)
}
private
func
setupUI
()
{
backgroundColor
=
.
black
layer
.
addSublayer
(
playerLayer
)
addSubview
(
playImageView
)
addSubview
(
progressSlider
)
addSubview
(
timeLabel
)
setupConstraints
()
setupNotifications
()
}
private
func
setupConstraints
()
{
playImageView
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
48
)
}
progressSlider
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
16
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
60
)
make
.
height
.
equalTo
(
30
)
}
timeLabel
.
snp
.
makeConstraints
{
make
in
make
.
right
.
equalToSuperview
()
.
offset
(
-
16
)
make
.
top
.
equalTo
(
progressSlider
.
snp
.
bottom
)
.
offset
(
8
)
}
}
private
func
setupGestures
()
{
let
tap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleTap
)
)
addGestureRecognizer
(
tap
)
let
doubleTap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleDoubleTap
)
)
doubleTap
.
numberOfTapsRequired
=
2
addGestureRecognizer
(
doubleTap
)
tap
.
require
(
toFail
:
doubleTap
)
}
private
func
setupNotifications
()
{
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
playerDidFinishPlaying
)
,
name
:
.
AVPlayerItemDidPlayToEndTime
,
object
:
nil
)
}
override
func
layoutSubviews
()
{
super
.
layoutSubviews
()
playerLayer
.
frame
=
bounds
}
// MARK: - Actions
@objc
private
func
handleTap
()
{
// 点击整个页面播放或暂停
if
isPlaying
{
pauseVideo
()
}
else
{
playVideo
()
}
// 显示/隐藏控制器
toggleControlsVisibility
()
}
@objc
private
func
handleDoubleTap
()
{
photoBrowser
?
.
dismiss
()
}
@objc
private
func
sliderValueChanged
(
_
sender
:
UISlider
)
{
guard
let
item
=
player
.
currentItem
else
{
return
}
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
let
targetTime
=
Double
(
sender
.
value
)
*
duration
let
currentTimeString
=
formatTime
(
targetTime
)
let
durationString
=
formatTime
(
duration
)
timeLabel
.
text
=
"
\(
currentTimeString
)
/
\(
durationString
)
"
}
}
@objc
private
func
sliderTouchDown
()
{
isSeeking
=
true
}
@objc
private
func
sliderTouchUp
()
{
guard
let
item
=
player
.
currentItem
else
{
return
}
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
let
targetTime
=
Double
(
progressSlider
.
value
)
*
duration
let
seekTime
=
CMTime
(
seconds
:
targetTime
,
preferredTimescale
:
CMTimeScale
(
NSEC_PER_SEC
))
player
.
seek
(
to
:
seekTime
)
{
[
weak
self
]
_
in
self
?
.
isSeeking
=
false
}
}
else
{
isSeeking
=
false
}
}
@objc
private
func
playerDidFinishPlaying
()
{
isPlaying
=
false
playImageView
.
isHidden
=
false
player
.
seek
(
to
:
CMTime
.
zero
)
progressSlider
.
value
=
0
updatePlayButtonImage
()
}
// MARK: - Video Control
func
loadVideo
(
from
url
:
URL
)
{
let
playerItem
=
AVPlayerItem
(
url
:
url
)
player
.
replaceCurrentItem
(
with
:
playerItem
)
addTimeObserver
()
updatePlayButtonImage
()
}
func
loadVideo
(
from
urlString
:
String
)
{
guard
let
url
=
URL
(
string
:
urlString
)
else
{
return
}
loadVideo
(
from
:
url
)
}
func
playVideo
()
{
player
.
play
()
isPlaying
=
true
playImageView
.
isHidden
=
true
updatePlayButtonImage
()
}
private
func
pauseVideo
()
{
player
.
pause
()
isPlaying
=
false
playImageView
.
isHidden
=
false
updatePlayButtonImage
()
}
func
stopVideo
()
{
player
.
pause
()
player
.
seek
(
to
:
CMTime
.
zero
)
isPlaying
=
false
playImageView
.
isHidden
=
false
progressSlider
.
value
=
0
updatePlayButtonImage
()
}
private
func
updatePlayButtonImage
()
{
let
imageName
=
isPlaying
?
"circle_pause_icon"
:
"circle_play_icon"
let
image
=
UIImage
(
named
:
imageName
)?
.
withRenderingMode
(
.
alwaysOriginal
)
playImageView
.
image
=
image
}
private
func
toggleControlsVisibility
()
{
let
isHidden
=
playImageView
.
alpha
==
0
UIView
.
animate
(
withDuration
:
0.3
)
{
self
.
playImageView
.
alpha
=
isHidden
?
1.0
:
0.0
self
.
progressSlider
.
alpha
=
isHidden
?
1.0
:
0.0
self
.
timeLabel
.
alpha
=
isHidden
?
1.0
:
0.0
}
completion
:
{
_
in
self
.
playImageView
.
isHidden
=
!
isHidden
&&
self
.
isPlaying
self
.
progressSlider
.
isHidden
=
!
isHidden
self
.
timeLabel
.
isHidden
=
!
isHidden
}
}
// MARK: - Time Observer
private
func
addTimeObserver
()
{
let
timeInterval
=
CMTime
(
seconds
:
0.1
,
preferredTimescale
:
CMTimeScale
(
NSEC_PER_SEC
))
timeObserver
=
player
.
addPeriodicTimeObserver
(
forInterval
:
timeInterval
,
queue
:
.
main
)
{
[
weak
self
]
_
in
self
?
.
updateProgress
()
}
}
private
func
removeTimeObserver
()
{
if
let
timeObserver
=
timeObserver
{
player
.
removeTimeObserver
(
timeObserver
)
self
.
timeObserver
=
nil
}
}
private
func
updateProgress
()
{
guard
let
item
=
player
.
currentItem
,
!
isSeeking
else
{
return
}
let
currentTime
=
CMTimeGetSeconds
(
player
.
currentTime
())
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
progressSlider
.
value
=
Float
(
currentTime
/
duration
)
let
currentTimeString
=
formatTime
(
currentTime
)
let
durationString
=
formatTime
(
duration
)
timeLabel
.
text
=
"
\(
currentTimeString
)
/
\(
durationString
)
"
}
}
private
func
formatTime
(
_
time
:
Double
)
->
String
{
if
time
.
isInfinite
||
time
.
isNaN
{
return
"00:00"
}
let
minutes
=
Int
(
time
)
/
60
let
seconds
=
Int
(
time
)
%
60
return
String
(
format
:
"%02d:%02d"
,
minutes
,
seconds
)
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/Contents.json
0 → 100644
View file @
5e650735
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_pause_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_pause_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/circle_pause_icon@2x.png
0 → 100644
View file @
5e650735
1.39 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/circle_pause_icon@3x.png
0 → 100644
View file @
5e650735
1.99 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/Contents.json
0 → 100644
View file @
5e650735
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_play_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_play_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/circle_play_icon@2x.png
0 → 100644
View file @
5e650735
1.67 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/circle_play_icon@3x.png
0 → 100644
View file @
5e650735
2.3 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/Contents.json
0 → 100644
View file @
5e650735
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_plus_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_plus_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/circle_plus_icon@2x.png
0 → 100644
View file @
5e650735
412 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/circle_plus_icon@3x.png
0 → 100644
View file @
5e650735
418 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/Contents.json
0 → 100644
View file @
5e650735
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"media_brower_delete@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"media_brower_delete@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/media_brower_delete@2x.png
0 → 100644
View file @
5e650735
573 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/media_brower_delete@3x.png
0 → 100644
View file @
5e650735
729 Bytes
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment