chore: drop Nuke
This commit is contained in:
parent
9680a665d2
commit
72a225bbc3
|
@ -193,7 +193,9 @@
|
||||||
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
|
DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; };
|
||||||
DB0C946B26A700AB0088FB11 /* MastodonUser+Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */; };
|
DB0C946B26A700AB0088FB11 /* MastodonUser+Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */; };
|
||||||
DB0C946C26A700CE0088FB11 /* MastodonUser+Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */; };
|
DB0C946C26A700CE0088FB11 /* MastodonUser+Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */; };
|
||||||
DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */; };
|
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */; };
|
||||||
|
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947126A7D2D70088FB11 /* AvatarButton.swift */; };
|
||||||
|
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; };
|
||||||
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; };
|
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; };
|
||||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
||||||
|
@ -275,10 +277,8 @@
|
||||||
DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */; };
|
DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */; };
|
||||||
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */; };
|
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */; };
|
||||||
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */; };
|
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */; };
|
||||||
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; };
|
|
||||||
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; };
|
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; };
|
||||||
DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; };
|
DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; };
|
||||||
DB52D33A26839DD800D43133 /* ImageTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB52D33926839DD800D43133 /* ImageTask.swift */; };
|
|
||||||
DB564BD0269F2F83001E39A7 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB564BCE269F2F83001E39A7 /* Localizable.stringsdict */; };
|
DB564BD0269F2F83001E39A7 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB564BCE269F2F83001E39A7 /* Localizable.stringsdict */; };
|
||||||
DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */; };
|
DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */; };
|
||||||
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; };
|
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; };
|
||||||
|
@ -443,9 +443,10 @@
|
||||||
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
||||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||||
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; };
|
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; };
|
||||||
DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = DBAEDE5E267A0B1500D25FF5 /* Nuke */; };
|
|
||||||
DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */; };
|
DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */; };
|
||||||
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
|
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
|
||||||
|
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
|
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; };
|
||||||
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
||||||
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
|
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
|
||||||
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
|
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
|
||||||
|
@ -892,6 +893,9 @@
|
||||||
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
|
||||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
||||||
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = "<group>"; };
|
DB0C946A26A700AB0088FB11 /* MastodonUser+Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonUser+Property.swift"; sourceTree = "<group>"; };
|
||||||
|
DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = "<group>"; };
|
||||||
|
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarButton.swift; sourceTree = "<group>"; };
|
||||||
|
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = "<group>"; };
|
||||||
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
DB0F814D264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -980,10 +984,8 @@
|
||||||
DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryFetchedResultController.swift; sourceTree = "<group>"; };
|
DB4F097E26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryFetchedResultController.swift; sourceTree = "<group>"; };
|
||||||
DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchToSearchDetailViewControllerAnimatedTransitioning.swift; sourceTree = "<group>"; };
|
DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchToSearchDetailViewControllerAnimatedTransitioning.swift; sourceTree = "<group>"; };
|
||||||
DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchTransitionController.swift; sourceTree = "<group>"; };
|
DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchTransitionController.swift; sourceTree = "<group>"; };
|
||||||
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = "<group>"; };
|
|
||||||
DB51D170262832380062B7A1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
DB51D170262832380062B7A1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||||
DB51D171262832380062B7A1 /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
|
DB51D171262832380062B7A1 /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = "<group>"; };
|
||||||
DB52D33926839DD800D43133 /* ImageTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTask.swift; sourceTree = "<group>"; };
|
|
||||||
DB564BCF269F2F83001E39A7 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
DB564BCF269F2F83001E39A7 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
DB564BD1269F2F8A001E39A7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
DB564BD1269F2F8A001E39A7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFilterService.swift; sourceTree = "<group>"; };
|
DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFilterService.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1143,6 +1145,7 @@
|
||||||
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = "<group>"; };
|
||||||
DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = "<group>"; };
|
DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = "<group>"; };
|
||||||
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
|
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
|
||||||
|
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = "<group>"; };
|
||||||
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
||||||
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
||||||
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
|
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1253,7 +1256,6 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */,
|
|
||||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
||||||
DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */,
|
DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */,
|
||||||
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
||||||
|
@ -1266,7 +1268,6 @@
|
||||||
DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */,
|
DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */,
|
||||||
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
||||||
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
||||||
DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */,
|
|
||||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
||||||
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */,
|
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */,
|
||||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
||||||
|
@ -1544,7 +1545,8 @@
|
||||||
2D42FF8325C82245004A627A /* Button */ = {
|
2D42FF8325C82245004A627A /* Button */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */,
|
DB0C947126A7D2D70088FB11 /* AvatarButton.swift */,
|
||||||
|
DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */,
|
||||||
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */,
|
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */,
|
||||||
0FAA101125E105390017CCDE /* PrimaryActionButton.swift */,
|
0FAA101125E105390017CCDE /* PrimaryActionButton.swift */,
|
||||||
);
|
);
|
||||||
|
@ -1695,6 +1697,7 @@
|
||||||
DB9D6C1325E4F97A0051B173 /* Container */,
|
DB9D6C1325E4F97A0051B173 /* Container */,
|
||||||
DBA9B90325F1D4420012E7B6 /* Control */,
|
DBA9B90325F1D4420012E7B6 /* Control */,
|
||||||
2D152A8A25C295B8009AA50C /* Content */,
|
2D152A8A25C295B8009AA50C /* Content */,
|
||||||
|
DB0C947026A7D2AB0088FB11 /* ImageView */,
|
||||||
DB87D45C2609DE6600D12C0D /* TextField */,
|
DB87D45C2609DE6600D12C0D /* TextField */,
|
||||||
DB1D187125EF5BBD003F1F23 /* TableView */,
|
DB1D187125EF5BBD003F1F23 /* TableView */,
|
||||||
2D7631A625C1533800929FB9 /* TableviewCell */,
|
2D7631A625C1533800929FB9 /* TableviewCell */,
|
||||||
|
@ -1910,6 +1913,22 @@
|
||||||
path = CoreDataStack;
|
path = CoreDataStack;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB0C947026A7D2AB0088FB11 /* ImageView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DB0C946E26A7D2A80088FB11 /* AvatarImageView.swift */,
|
||||||
|
);
|
||||||
|
path = ImageView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
DB0C947826A7FE950088FB11 /* Button */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */,
|
||||||
|
);
|
||||||
|
path = Button;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB1D187125EF5BBD003F1F23 /* TableView */ = {
|
DB1D187125EF5BBD003F1F23 /* TableView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2509,7 +2528,7 @@
|
||||||
DBCC3B35261440BA0045B23D /* UINavigationController.swift */,
|
DBCC3B35261440BA0045B23D /* UINavigationController.swift */,
|
||||||
DB97131E2666078B00BD1E90 /* Date.swift */,
|
DB97131E2666078B00BD1E90 /* Date.swift */,
|
||||||
DBAC6489267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift */,
|
DBAC6489267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift */,
|
||||||
DB52D33926839DD800D43133 /* ImageTask.swift */,
|
DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */,
|
||||||
);
|
);
|
||||||
path = Extension;
|
path = Extension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2568,13 +2587,14 @@
|
||||||
DB9D6BFD25E4F57B0051B173 /* Notification */ = {
|
DB9D6BFD25E4F57B0051B173 /* Notification */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
DB0C947826A7FE950088FB11 /* Button */,
|
||||||
|
2D35237F26256F470031AF25 /* TableViewCell */,
|
||||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
|
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
|
||||||
DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */,
|
DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */,
|
||||||
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
|
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
|
||||||
2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */,
|
2D084B8C26258EA3003AA3AF /* NotificationViewModel+Diffable.swift */,
|
||||||
2D084B9226259545003AA3AF /* NotificationViewModel+LoadLatestState.swift */,
|
2D084B9226259545003AA3AF /* NotificationViewModel+LoadLatestState.swift */,
|
||||||
2D24E12C2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift */,
|
2D24E12C2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift */,
|
||||||
2D35237F26256F470031AF25 /* TableViewCell */,
|
|
||||||
);
|
);
|
||||||
path = Notification;
|
path = Notification;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2639,7 +2659,6 @@
|
||||||
DBA9B90325F1D4420012E7B6 /* Control */ = {
|
DBA9B90325F1D4420012E7B6 /* Control */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */,
|
|
||||||
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */,
|
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */,
|
||||||
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */,
|
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */,
|
||||||
);
|
);
|
||||||
|
@ -2976,12 +2995,10 @@
|
||||||
2D939AC725EE14620076FA61 /* CropViewController */,
|
2D939AC725EE14620076FA61 /* CropViewController */,
|
||||||
DB9A487D2603456B008B817C /* UITextView+Placeholder */,
|
DB9A487D2603456B008B817C /* UITextView+Placeholder */,
|
||||||
DBB525072611EAC0002F1F29 /* Tabman */,
|
DBB525072611EAC0002F1F29 /* Tabman */,
|
||||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */,
|
|
||||||
DBAC6482267D0B21007FE9FD /* DifferenceKit */,
|
DBAC6482267D0B21007FE9FD /* DifferenceKit */,
|
||||||
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
||||||
DBAC64A0267E6D02007FE9FD /* Fuzi */,
|
DBAC64A0267E6D02007FE9FD /* Fuzi */,
|
||||||
DBF7A0FB26830C33004176A2 /* FPSIndicator */,
|
DBF7A0FB26830C33004176A2 /* FPSIndicator */,
|
||||||
DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */,
|
|
||||||
DB03F7EA268976B5007B274C /* MastodonMeta */,
|
DB03F7EA268976B5007B274C /* MastodonMeta */,
|
||||||
DB03F7EC268976B5007B274C /* MetaTextView */,
|
DB03F7EC268976B5007B274C /* MetaTextView */,
|
||||||
DBC6462A26A1738900B0E31B /* MastodonUI */,
|
DBC6462A26A1738900B0E31B /* MastodonUI */,
|
||||||
|
@ -3204,7 +3221,6 @@
|
||||||
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */,
|
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */,
|
||||||
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||||
DB6F5E30264E7410009108F4 /* XCRemoteSwiftPackageReference "TwitterTextEditor" */,
|
DB6F5E30264E7410009108F4 /* XCRemoteSwiftPackageReference "TwitterTextEditor" */,
|
||||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */,
|
|
||||||
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
||||||
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
||||||
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */,
|
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */,
|
||||||
|
@ -3540,6 +3556,7 @@
|
||||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
||||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */,
|
||||||
DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */,
|
DBAEDE61267B342D00D25FF5 /* StatusContentCacheService.swift in Sources */,
|
||||||
|
DB0C946F26A7D2A80088FB11 /* AvatarImageView.swift in Sources */,
|
||||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
||||||
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */,
|
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */,
|
||||||
|
@ -3569,6 +3586,7 @@
|
||||||
2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */,
|
2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */,
|
||||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
||||||
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
|
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
|
||||||
|
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||||
2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */,
|
2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */,
|
||||||
DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */,
|
DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */,
|
||||||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
||||||
|
@ -3607,6 +3625,7 @@
|
||||||
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
|
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
|
||||||
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
|
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
|
||||||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
|
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
|
||||||
|
DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */,
|
||||||
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
|
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
|
||||||
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
|
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
|
||||||
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
||||||
|
@ -3664,7 +3683,6 @@
|
||||||
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */,
|
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */,
|
||||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
|
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
|
||||||
2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */,
|
2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */,
|
||||||
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */,
|
|
||||||
5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */,
|
5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */,
|
||||||
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
|
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
|
||||||
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */,
|
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
|
@ -3676,7 +3694,6 @@
|
||||||
DBCBCC012680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift in Sources */,
|
DBCBCC012680AF2A000F5B51 /* AsyncHomeTimelineViewModel+Diffable.swift in Sources */,
|
||||||
DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */,
|
DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */,
|
||||||
DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */,
|
DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */,
|
||||||
DB52D33A26839DD800D43133 /* ImageTask.swift in Sources */,
|
|
||||||
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */,
|
2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */,
|
||||||
DB49A61F25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift in Sources */,
|
DB49A61F25FF32AA00B98345 /* EmojiService+CustomEmojiViewModel.swift in Sources */,
|
||||||
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
|
||||||
|
@ -3821,6 +3838,7 @@
|
||||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||||
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
||||||
|
DB0C947226A7D2D70088FB11 /* AvatarButton.swift in Sources */,
|
||||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
||||||
DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */,
|
DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */,
|
||||||
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
||||||
|
@ -4011,6 +4029,7 @@
|
||||||
DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */,
|
DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */,
|
||||||
DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */,
|
DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */,
|
||||||
DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */,
|
||||||
|
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||||
DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */,
|
DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */,
|
||||||
DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */,
|
DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */,
|
||||||
DBBC24C626A5456000398BB9 /* Theme.swift in Sources */,
|
DBBC24C626A5456000398BB9 /* Theme.swift in Sources */,
|
||||||
|
@ -5402,14 +5421,6 @@
|
||||||
minimumVersion = 3.1.3;
|
minimumVersion = 3.1.3;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/kean/Nuke.git";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 10.3.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */ = {
|
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/uias/Tabman";
|
repositoryURL = "https://github.com/uias/Tabman";
|
||||||
|
@ -5478,11 +5489,6 @@
|
||||||
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
||||||
productName = AlamofireImage;
|
productName = AlamofireImage;
|
||||||
};
|
};
|
||||||
DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = DB0E2D2C26833FF600865C3C /* XCRemoteSwiftPackageReference "Nuke-FLAnimatedImage-Plugin" */;
|
|
||||||
productName = NukeFLAnimatedImagePlugin;
|
|
||||||
};
|
|
||||||
DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = {
|
DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
||||||
|
@ -5528,11 +5534,6 @@
|
||||||
package = DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */;
|
package = DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */;
|
||||||
productName = Fuzi;
|
productName = Fuzi;
|
||||||
};
|
};
|
||||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */;
|
|
||||||
productName = Nuke;
|
|
||||||
};
|
|
||||||
DBB525072611EAC0002F1F29 /* Tabman */ = {
|
DBB525072611EAC0002F1F29 /* Tabman */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */;
|
package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>29</integer>
|
<integer>21</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -37,12 +37,12 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>27</integer>
|
<integer>22</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>28</integer>
|
<integer>23</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
|
"revision": "4d19ad82f80cc71ff829b941ded114c56f4f604c",
|
||||||
"version": "5.4.3"
|
"version": "5.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Nuke
|
|
||||||
|
|
||||||
enum CustomEmojiPickerSection: Equatable, Hashable {
|
enum CustomEmojiPickerSection: Equatable, Hashable {
|
||||||
case emoji(name: String)
|
case emoji(name: String)
|
||||||
|
@ -24,14 +23,9 @@ extension CustomEmojiPickerSection {
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CustomEmojiPickerItemCollectionViewCell.self), for: indexPath) as! CustomEmojiPickerItemCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CustomEmojiPickerItemCollectionViewCell.self), for: indexPath) as! CustomEmojiPickerItemCollectionViewCell
|
||||||
let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
let placeholder = UIImage.placeholder(size: CustomEmojiPickerItemCollectionViewCell.itemSize, color: .systemFill)
|
||||||
.af.imageRounded(withCornerRadius: 4)
|
.af.imageRounded(withCornerRadius: 4)
|
||||||
cell.imageTask = Nuke.loadImage(
|
|
||||||
with: attribute.emoji.url,
|
let url = URL(string: attribute.emoji.url)
|
||||||
options: .init(
|
cell.emojiImageView.setImage(url: url, placeholder: placeholder, scaleToSize: CustomEmojiPickerItemCollectionViewCell.itemSize)
|
||||||
placeholder: placeholder,
|
|
||||||
transition: .fadeIn(duration: 0.2)
|
|
||||||
),
|
|
||||||
into: cell.emojiImageView
|
|
||||||
)
|
|
||||||
cell.accessibilityLabel = attribute.emoji.shortcode
|
cell.accessibilityLabel = attribute.emoji.shortcode
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import CoreDataStack
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
import Nuke
|
|
||||||
|
|
||||||
enum NotificationSection: Equatable, Hashable {
|
enum NotificationSection: Equatable, Hashable {
|
||||||
case main
|
case main
|
||||||
|
@ -56,17 +55,16 @@ extension NotificationSection {
|
||||||
.af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
.af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.actionImageView.image = createActionImage()
|
cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color
|
||||||
|
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||||
cell.traitCollectionDidChange
|
cell.traitCollectionDidChange
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak cell] in
|
.sink { [weak cell] in
|
||||||
guard let cell = cell else { return }
|
guard let cell = cell else { return }
|
||||||
cell.actionImageView.image = createActionImage()
|
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
cell.actionImageView.backgroundColor = notification.notificationType.color
|
|
||||||
|
|
||||||
// configure author name, notification description, timestamp
|
// configure author name, notification description, timestamp
|
||||||
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
|
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
|
||||||
let createAt = notification.createAt
|
let createAt = notification.createAt
|
||||||
|
|
|
@ -688,12 +688,12 @@ extension StatusSection {
|
||||||
cell.statusView.usernameLabel.text = "@" + author.acct
|
cell.statusView.usernameLabel.text = "@" + author.acct
|
||||||
// avatar
|
// avatar
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
cell.statusView.avatarImageView.isHidden = true
|
cell.statusView.avatarButton.isHidden = true
|
||||||
cell.statusView.avatarStackedContainerButton.isHidden = false
|
cell.statusView.avatarStackedContainerButton.isHidden = false
|
||||||
cell.statusView.avatarStackedContainerButton.topLeadingAvatarStackedImageView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: reblog.author.avatarImageURL()))
|
cell.statusView.avatarStackedContainerButton.topLeadingAvatarStackedImageView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: reblog.author.avatarImageURL()))
|
||||||
cell.statusView.avatarStackedContainerButton.bottomTrailingAvatarStackedImageView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
cell.statusView.avatarStackedContainerButton.bottomTrailingAvatarStackedImageView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
||||||
} else {
|
} else {
|
||||||
cell.statusView.avatarImageView.isHidden = false
|
cell.statusView.avatarButton.isHidden = false
|
||||||
cell.statusView.avatarStackedContainerButton.isHidden = true
|
cell.statusView.avatarStackedContainerButton.isHidden = true
|
||||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
//
|
||||||
|
// FLAnimatedImageView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import Alamofire
|
||||||
|
import AlamofireImage
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
|
private enum FLAnimatedImageViewAssociatedKeys {
|
||||||
|
static var activeAvatarRequestURL = "FLAnimatedImageViewAssociatedKeys.activeAvatarRequestURL"
|
||||||
|
static var avatarRequestCancellable = "FLAnimatedImageViewAssociatedKeys.avatarRequestCancellable"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FLAnimatedImageView {
|
||||||
|
|
||||||
|
var activeAvatarRequestURL: URL? {
|
||||||
|
get {
|
||||||
|
objc_getAssociatedObject(self, &FLAnimatedImageViewAssociatedKeys.activeAvatarRequestURL) as? URL
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &FLAnimatedImageViewAssociatedKeys.activeAvatarRequestURL, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var avatarRequestCancellable: AnyCancellable? {
|
||||||
|
get {
|
||||||
|
objc_getAssociatedObject(self, &FLAnimatedImageViewAssociatedKeys.avatarRequestCancellable) as? AnyCancellable
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &FLAnimatedImageViewAssociatedKeys.avatarRequestCancellable, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setImage(url: URL?, placeholder: UIImage?, scaleToSize: CGSize?) {
|
||||||
|
// cancel task
|
||||||
|
activeAvatarRequestURL = nil
|
||||||
|
avatarRequestCancellable?.cancel()
|
||||||
|
|
||||||
|
// set placeholder
|
||||||
|
image = placeholder
|
||||||
|
|
||||||
|
// set image
|
||||||
|
guard let url = url else { return }
|
||||||
|
activeAvatarRequestURL = url
|
||||||
|
let avatarRequest = AF.request(url).publishData()
|
||||||
|
avatarRequestCancellable = avatarRequest
|
||||||
|
.sink { response in
|
||||||
|
switch response.result {
|
||||||
|
case .success(let data):
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
let image: UIImage? = {
|
||||||
|
if let scaleToSize = scaleToSize {
|
||||||
|
return UIImage(data: data)?.af.imageScaled(to: scaleToSize, scale: UIScreen.main.scale)
|
||||||
|
} else {
|
||||||
|
return UIImage(data: data)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
let animatedImage = FLAnimatedImage(animatedGIFData: data)
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if self.activeAvatarRequestURL == url {
|
||||||
|
if let animatedImage = animatedImage {
|
||||||
|
self.animatedImage = animatedImage
|
||||||
|
} else {
|
||||||
|
self.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// ImageTask.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-6-24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Nuke
|
|
||||||
|
|
||||||
extension ImageTask {
|
|
||||||
func store(in set: inout Set<ImageTask?>) {
|
|
||||||
set.insert(self)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,16 +5,16 @@
|
||||||
// Created by Cirno MainasuK on 2021-2-4.
|
// Created by Cirno MainasuK on 2021-2-4.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import Nuke
|
|
||||||
|
|
||||||
protocol AvatarConfigurableView {
|
protocol AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { get }
|
static var configurableAvatarImageSize: CGSize { get }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { get }
|
static var configurableAvatarImageCornerRadius: CGFloat { get }
|
||||||
var configurableAvatarImageView: UIImageView? { get }
|
var configurableAvatarImageView: FLAnimatedImageView? { get }
|
||||||
var configurableAvatarButton: UIButton? { get }
|
|
||||||
func configure(with configuration: AvatarConfigurableViewConfiguration)
|
func configure(with configuration: AvatarConfigurableViewConfiguration)
|
||||||
func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration)
|
func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration)
|
||||||
}
|
}
|
||||||
|
@ -43,69 +43,31 @@ extension AvatarConfigurableView {
|
||||||
}
|
}
|
||||||
return placeholderImage
|
return placeholderImage
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// reset layer attributes
|
|
||||||
configurableAvatarImageView?.layer.masksToBounds = false
|
|
||||||
configurableAvatarImageView?.layer.cornerRadius = 0
|
|
||||||
configurableAvatarImageView?.layer.cornerCurve = .circular
|
|
||||||
|
|
||||||
configurableAvatarButton?.layer.masksToBounds = false
|
|
||||||
configurableAvatarButton?.layer.cornerRadius = 0
|
|
||||||
configurableAvatarButton?.layer.cornerCurve = .circular
|
|
||||||
|
|
||||||
// accessibility
|
// accessibility
|
||||||
configurableAvatarImageView?.accessibilityIgnoresInvertColors = true
|
configurableAvatarImageView?.accessibilityIgnoresInvertColors = true
|
||||||
configurableAvatarButton?.accessibilityIgnoresInvertColors = true
|
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
avatarConfigurableView(self, didFinishConfiguration: configuration)
|
avatarConfigurableView(self, didFinishConfiguration: configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let imageDisplayingView: ImageDisplayingView = configurableAvatarImageView ?? configurableAvatarButton?.imageView else {
|
guard let configurableAvatarImageView = configurableAvatarImageView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// set corner radius (due to GIF won't crop)
|
// set corner radius (due to GIF won't crop)
|
||||||
imageDisplayingView.layer.masksToBounds = true
|
configurableAvatarImageView.layer.masksToBounds = true
|
||||||
imageDisplayingView.layer.cornerRadius = Self.configurableAvatarImageCornerRadius
|
configurableAvatarImageView.layer.cornerRadius = Self.configurableAvatarImageCornerRadius
|
||||||
imageDisplayingView.layer.cornerCurve = Self.configurableAvatarImageCornerRadius < Self.configurableAvatarImageSize.width * 0.5 ? .continuous :.circular
|
configurableAvatarImageView.layer.cornerCurve = Self.configurableAvatarImageCornerRadius < Self.configurableAvatarImageSize.width * 0.5 ? .continuous :.circular
|
||||||
|
|
||||||
// set border
|
// set border
|
||||||
configureLayerBorder(view: imageDisplayingView, configuration: configuration)
|
configureLayerBorder(view: configurableAvatarImageView, configuration: configuration)
|
||||||
|
|
||||||
|
configurableAvatarImageView.setImage(
|
||||||
// set image
|
url: configuration.avatarImageURL,
|
||||||
let url = configuration.avatarImageURL
|
|
||||||
let processors: [ImageProcessing] = [
|
|
||||||
ImageProcessors.Resize(
|
|
||||||
size: Self.configurableAvatarImageSize,
|
|
||||||
unit: .points,
|
|
||||||
contentMode: .aspectFill,
|
|
||||||
crop: false
|
|
||||||
),
|
|
||||||
ImageProcessors.RoundedCorners(
|
|
||||||
radius: Self.configurableAvatarImageCornerRadius
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
let request = ImageRequest(url: url, processors: processors)
|
|
||||||
let options = ImageLoadingOptions(
|
|
||||||
placeholder: placeholderImage,
|
placeholder: placeholderImage,
|
||||||
transition: .fadeIn(duration: 0.2)
|
scaleToSize: Self.configurableAvatarImageSize
|
||||||
)
|
)
|
||||||
|
|
||||||
Nuke.loadImage(
|
|
||||||
with: request,
|
|
||||||
options: options,
|
|
||||||
into: imageDisplayingView
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .failure:
|
|
||||||
break
|
|
||||||
case .success:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureLayerBorder(view: UIView, configuration: AvatarConfigurableViewConfiguration) {
|
func configureLayerBorder(view: UIView, configuration: AvatarConfigurableViewConfiguration) {
|
||||||
|
|
|
@ -168,9 +168,9 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.attachment(of: status, index: i)
|
self.attachment(of: status, index: i)
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
.compactMap { attachment -> AnyPublisher<UIImage, Error>? in
|
.compactMap { attachment -> AnyPublisher<Void, Error>? in
|
||||||
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
||||||
return self.context.photoLibraryService.saveImage(url: url)
|
return self.context.photoLibraryService.save(imageSource: .url(url))
|
||||||
}
|
}
|
||||||
.switchToLatest()
|
.switchToLatest()
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
|
@ -197,9 +197,9 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.attachment(of: status, index: i)
|
self.attachment(of: status, index: i)
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
.compactMap { attachment -> AnyPublisher<UIImage, Error>? in
|
.compactMap { attachment -> AnyPublisher<Void, Error>? in
|
||||||
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
||||||
return self.context.photoLibraryService.copyImage(url: url)
|
return self.context.photoLibraryService.copy(imageSource: .url(url))
|
||||||
}
|
}
|
||||||
.switchToLatest()
|
.switchToLatest()
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
final class AutoCompleteTableViewCell: UITableViewCell {
|
final class AutoCompleteTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ final class AutoCompleteTableViewCell: UITableViewCell {
|
||||||
return stackView
|
return stackView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let avatarImageView = UIImageView()
|
let avatarImageView = FLAnimatedImageView()
|
||||||
|
|
||||||
let titleLabel: UILabel = {
|
let titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
@ -129,8 +130,7 @@ extension AutoCompleteTableViewCell {
|
||||||
extension AutoCompleteTableViewCell: AvatarConfigurableView {
|
extension AutoCompleteTableViewCell: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { avatarImageSize }
|
static var configurableAvatarImageSize: CGSize { avatarImageSize }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { avatarImageCornerRadius }
|
static var configurableAvatarImageCornerRadius: CGFloat { avatarImageCornerRadius }
|
||||||
var configurableAvatarImageView: UIImageView? { avatarImageView }
|
var configurableAvatarImageView: FLAnimatedImageView? { avatarImageView }
|
||||||
var configurableAvatarButton: UIButton? { nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(SwiftUI) && DEBUG
|
#if canImport(SwiftUI) && DEBUG
|
||||||
|
|
|
@ -6,16 +6,14 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Nuke
|
import FLAnimatedImage
|
||||||
|
|
||||||
final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell {
|
final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
static let itemSize = CGSize(width: 44, height: 44)
|
static let itemSize = CGSize(width: 44, height: 44)
|
||||||
|
|
||||||
var imageTask: ImageTask?
|
let emojiImageView: FLAnimatedImageView = {
|
||||||
|
let imageView = FLAnimatedImageView()
|
||||||
let emojiImageView: UIImageView = {
|
|
||||||
let imageView = UIImageView()
|
|
||||||
imageView.contentMode = .scaleAspectFit
|
imageView.contentMode = .scaleAspectFit
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
return imageView
|
return imageView
|
||||||
|
@ -29,8 +27,6 @@ final class CustomEmojiPickerItemCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
imageTask?.cancel()
|
|
||||||
imageTask = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import MastodonSDK
|
||||||
import MetaTextView
|
import MetaTextView
|
||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
import Meta
|
import Meta
|
||||||
import Nuke
|
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
|
||||||
final class ComposeViewController: UIViewController, NeedsDependency {
|
final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
|
|
|
@ -66,7 +66,7 @@ final class ReplicaStatusView: UIView {
|
||||||
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
|
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
let avatarImageView: UIImageView = FLAnimatedImageView()
|
let avatarImageView = FLAnimatedImageView()
|
||||||
|
|
||||||
let nameLabel: ActiveLabel = {
|
let nameLabel: ActiveLabel = {
|
||||||
let label = ActiveLabel(style: .statusName)
|
let label = ActiveLabel(style: .statusName)
|
||||||
|
@ -250,6 +250,5 @@ extension ReplicaStatusView {
|
||||||
extension ReplicaStatusView: AvatarConfigurableView {
|
extension ReplicaStatusView: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
||||||
var configurableAvatarImageView: UIImageView? { avatarImageView }
|
var configurableAvatarImageView: FLAnimatedImageView? { avatarImageView }
|
||||||
var configurableAvatarButton: UIButton? { nil }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,45 +205,51 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
|
||||||
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, contextMenuActionPerform action: MediaPreviewImageViewController.ContextMenuAction) {
|
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, contextMenuActionPerform action: MediaPreviewImageViewController.ContextMenuAction) {
|
||||||
switch action {
|
switch action {
|
||||||
case .savePhoto:
|
case .savePhoto:
|
||||||
switch viewController.viewModel.item {
|
let savePublisher: AnyPublisher<Void, Error> = {
|
||||||
case .status(let meta):
|
switch viewController.viewModel.item {
|
||||||
context.photoLibraryService.saveImage(url: meta.url)
|
case .status(let meta):
|
||||||
.sink { [weak self] completion in
|
return context.photoLibraryService.save(imageSource: .url(meta.url))
|
||||||
guard let self = self else { return }
|
case .local(let meta):
|
||||||
switch completion {
|
return context.photoLibraryService.save(imageSource: .image(meta.image))
|
||||||
case .failure(let error):
|
}
|
||||||
guard let error = error as? PhotoLibraryService.PhotoLibraryError,
|
}()
|
||||||
case .noPermission = error else { return }
|
savePublisher
|
||||||
let alertController = SettingService.openSettingsAlertController(title: L10n.Common.Alerts.SavePhotoFailure.title, message: L10n.Common.Alerts.SavePhotoFailure.message)
|
.sink { [weak self] completion in
|
||||||
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
guard let self = self else { return }
|
||||||
case .finished:
|
switch completion {
|
||||||
break
|
case .failure(let error):
|
||||||
}
|
guard let error = error as? PhotoLibraryService.PhotoLibraryError,
|
||||||
} receiveValue: { _ in
|
case .noPermission = error else { return }
|
||||||
// do nothing
|
let alertController = SettingService.openSettingsAlertController(title: L10n.Common.Alerts.SavePhotoFailure.title, message: L10n.Common.Alerts.SavePhotoFailure.message)
|
||||||
|
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
.store(in: &context.disposeBag)
|
} receiveValue: { _ in
|
||||||
case .local(let meta):
|
// do nothing
|
||||||
context.photoLibraryService.save(image: meta.image, withNotificationFeedback: true)
|
}
|
||||||
}
|
.store(in: &context.disposeBag)
|
||||||
case .copyPhoto:
|
case .copyPhoto:
|
||||||
switch viewController.viewModel.item {
|
let copyPublisher: AnyPublisher<Void, Error> = {
|
||||||
case .status(let meta):
|
switch viewController.viewModel.item {
|
||||||
context.photoLibraryService.copyImage(url: meta.url)
|
case .status(let meta):
|
||||||
.sink { completion in
|
return context.photoLibraryService.copy(imageSource: .url(meta.url))
|
||||||
switch completion {
|
case .local(let meta):
|
||||||
case .failure(let error):
|
return context.photoLibraryService.copy(imageSource: .image(meta.image))
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
}
|
||||||
case .finished:
|
}()
|
||||||
break
|
copyPublisher
|
||||||
}
|
.sink { completion in
|
||||||
} receiveValue: { _ in
|
switch completion {
|
||||||
// do nothing
|
case .failure(let error):
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
.store(in: &context.disposeBag)
|
} receiveValue: { _ in
|
||||||
case .local(let meta):
|
// do nothing
|
||||||
context.photoLibraryService.copy(image: meta.image, withNotificationFeedback: true)
|
}
|
||||||
}
|
.store(in: &context.disposeBag)
|
||||||
case .share:
|
case .share:
|
||||||
let applicationActivities: [UIActivity] = [
|
let applicationActivities: [UIActivity] = [
|
||||||
SafariActivity(sceneCoordinator: self.coordinator)
|
SafariActivity(sceneCoordinator: self.coordinator)
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Nuke
|
|
||||||
|
|
||||||
protocol MediaPreviewImageViewControllerDelegate: AnyObject {
|
protocol MediaPreviewImageViewControllerDelegate: AnyObject {
|
||||||
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer)
|
func mediaPreviewImageViewController(_ viewController: MediaPreviewImageViewController, tapGestureRecognizerDidTrigger tapGestureRecognizer: UITapGestureRecognizer)
|
||||||
|
@ -91,11 +90,14 @@ extension MediaPreviewImageViewController {
|
||||||
// }
|
// }
|
||||||
viewModel.image
|
viewModel.image
|
||||||
.receive(on: RunLoop.main) // use RunLoop prevent set image during zooming (TODO: handle transitioning state)
|
.receive(on: RunLoop.main) // use RunLoop prevent set image during zooming (TODO: handle transitioning state)
|
||||||
.sink { [weak self] image in
|
.sink { [weak self] image, animatedImage in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let image = image else { return }
|
guard let image = image else { return }
|
||||||
self.previewImageView.imageView.image = image
|
self.previewImageView.imageView.image = image
|
||||||
self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true)
|
self.previewImageView.setup(image: image, container: self.previewImageView, forceUpdate: true)
|
||||||
|
if let animatedImage = animatedImage {
|
||||||
|
self.previewImageView.imageView.animatedImage = animatedImage
|
||||||
|
}
|
||||||
self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
|
self.previewImageView.imageView.accessibilityLabel = self.viewModel.altText
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Nuke
|
import Alamofire
|
||||||
|
import AlamofireImage
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
class MediaPreviewImageViewModel {
|
class MediaPreviewImageViewModel {
|
||||||
|
|
||||||
|
@ -18,34 +20,35 @@ class MediaPreviewImageViewModel {
|
||||||
let item: ImagePreviewItem
|
let item: ImagePreviewItem
|
||||||
|
|
||||||
// output
|
// output
|
||||||
let image: CurrentValueSubject<UIImage?, Never>
|
let image: CurrentValueSubject<(UIImage?, FLAnimatedImage?), Never>
|
||||||
let altText: String?
|
let altText: String?
|
||||||
|
|
||||||
init(meta: RemoteImagePreviewMeta) {
|
init(meta: RemoteImagePreviewMeta) {
|
||||||
self.item = .status(meta)
|
self.item = .status(meta)
|
||||||
self.image = CurrentValueSubject(meta.thumbnail)
|
self.image = CurrentValueSubject((meta.thumbnail, nil))
|
||||||
self.altText = meta.altText
|
self.altText = meta.altText
|
||||||
|
|
||||||
let url = meta.url
|
let url = meta.url
|
||||||
|
AF.request(url).publishData()
|
||||||
ImagePipeline.shared.imagePublisher(with: url)
|
.map { response in
|
||||||
.sink { completion in
|
switch response.result {
|
||||||
switch completion {
|
case .success(let data):
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
||||||
|
let image = UIImage(data: data, scale: UIScreen.main.scale)
|
||||||
|
let animatedImage = FLAnimatedImage(animatedGIFData: data)
|
||||||
|
return (image, animatedImage)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
||||||
case .finished:
|
return (nil, nil)
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
|
||||||
}
|
}
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.image.value = response.image
|
|
||||||
}
|
}
|
||||||
|
.assign(to: \.value, on: image)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(meta: LocalImagePreviewMeta) {
|
init(meta: LocalImagePreviewMeta) {
|
||||||
self.item = .local(meta)
|
self.item = .local(meta)
|
||||||
self.image = CurrentValueSubject(meta.image)
|
self.image = CurrentValueSubject((meta.image, nil))
|
||||||
self.altText = nil
|
self.altText = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// NotificationAvatarButton.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
|
final class NotificationAvatarButton: AvatarButton {
|
||||||
|
|
||||||
|
// Size fixed
|
||||||
|
static let containerSize = CGSize(width: 35, height: 35)
|
||||||
|
static let badgeImageViewSize = CGSize(width: 24, height: 24)
|
||||||
|
static let badgeImageMaskSize = CGSize(width: badgeImageViewSize.width + 4, height: badgeImageViewSize.height + 4)
|
||||||
|
|
||||||
|
let badgeImageView: UIImageView = {
|
||||||
|
let imageView = RoundedImageView()
|
||||||
|
imageView.contentMode = .center
|
||||||
|
imageView.isOpaque = true
|
||||||
|
imageView.layer.shouldRasterize = true
|
||||||
|
imageView.layer.rasterizationScale = UIScreen.main.scale
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override func _init() {
|
||||||
|
super._init()
|
||||||
|
|
||||||
|
avatarImageSize = CGSize(width: 35, height: 35)
|
||||||
|
|
||||||
|
let path: CGPath = {
|
||||||
|
let path = CGMutablePath()
|
||||||
|
path.addRect(CGRect(origin: .zero, size: NotificationAvatarButton.containerSize))
|
||||||
|
let x: CGFloat = {
|
||||||
|
if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft {
|
||||||
|
return -0.5 * NotificationAvatarButton.badgeImageMaskSize.width
|
||||||
|
} else {
|
||||||
|
return NotificationAvatarButton.containerSize.width - 0.5 * NotificationAvatarButton.badgeImageMaskSize.width
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
path.addPath(UIBezierPath(
|
||||||
|
ovalIn: CGRect(
|
||||||
|
x: x,
|
||||||
|
y: NotificationAvatarButton.containerSize.height - 0.5 * NotificationAvatarButton.badgeImageMaskSize.width,
|
||||||
|
width: NotificationAvatarButton.badgeImageMaskSize.width,
|
||||||
|
height: NotificationAvatarButton.badgeImageMaskSize.height
|
||||||
|
)
|
||||||
|
).cgPath)
|
||||||
|
return path
|
||||||
|
}()
|
||||||
|
|
||||||
|
let maskShapeLayer = CAShapeLayer()
|
||||||
|
maskShapeLayer.backgroundColor = UIColor.black.cgColor
|
||||||
|
maskShapeLayer.fillRule = .evenOdd
|
||||||
|
maskShapeLayer.path = path
|
||||||
|
avatarImageView.layer.mask = maskShapeLayer
|
||||||
|
|
||||||
|
badgeImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(badgeImageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
badgeImageView.centerXAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
badgeImageView.centerYAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
badgeImageView.widthAnchor.constraint(equalToConstant: NotificationAvatarButton.badgeImageViewSize.width).priority(.required - 1),
|
||||||
|
badgeImageView.heightAnchor.constraint(equalToConstant: NotificationAvatarButton.badgeImageViewSize.height).priority(.required - 1),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateAppearance() {
|
||||||
|
super.updateAppearance()
|
||||||
|
badgeImageView.alpha = primaryActionState.contains(.highlighted) ? 0.6 : 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RoundedImageView: UIImageView {
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
layer.masksToBounds = true
|
||||||
|
layer.cornerRadius = bounds.width / 2
|
||||||
|
layer.cornerCurve = .circular
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,6 @@ import ActiveLabel
|
||||||
import MetaTextView
|
import MetaTextView
|
||||||
import Meta
|
import Meta
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import Nuke
|
|
||||||
|
|
||||||
protocol NotificationTableViewCellDelegate: AnyObject {
|
protocol NotificationTableViewCellDelegate: AnyObject {
|
||||||
var context: AppContext! { get }
|
var context: AppContext! { get }
|
||||||
|
@ -46,31 +45,8 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
var containerStackViewBottomLayoutConstraint: NSLayoutConstraint!
|
var containerStackViewBottomLayoutConstraint: NSLayoutConstraint!
|
||||||
let containerStackView = UIStackView()
|
let containerStackView = UIStackView()
|
||||||
|
|
||||||
let avatarImageView: UIImageView = {
|
let avatarButton = NotificationAvatarButton()
|
||||||
let imageView = FLAnimatedImageView()
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
|
||||||
let traitCollectionDidChange = PassthroughSubject<Void, Never>()
|
let traitCollectionDidChange = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
let actionImageView: UIImageView = {
|
|
||||||
let imageView = UIImageView()
|
|
||||||
imageView.contentMode = .center
|
|
||||||
imageView.isOpaque = true
|
|
||||||
imageView.layer.masksToBounds = true
|
|
||||||
imageView.layer.cornerRadius = NotificationStatusTableViewCell.actionImageViewSize.width * 0.5
|
|
||||||
imageView.layer.cornerCurve = .circular
|
|
||||||
imageView.layer.borderWidth = NotificationStatusTableViewCell.actionImageBorderWidth
|
|
||||||
imageView.layer.shouldRasterize = true
|
|
||||||
imageView.layer.rasterizationScale = UIScreen.main.scale
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
|
|
||||||
let avatarContainer: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
let contentStackView = UIStackView()
|
let contentStackView = UIStackView()
|
||||||
|
|
||||||
|
@ -181,25 +157,11 @@ extension NotificationStatusTableViewCell {
|
||||||
containerStackViewBottomLayoutConstraint.priority(.required - 1),
|
containerStackViewBottomLayoutConstraint.priority(.required - 1),
|
||||||
])
|
])
|
||||||
|
|
||||||
containerStackView.addArrangedSubview(avatarContainer)
|
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
containerStackView.addArrangedSubview(avatarButton)
|
||||||
avatarContainer.addSubview(avatarImageView)
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
|
avatarButton.heightAnchor.constraint(equalToConstant: NotificationAvatarButton.containerSize.width).priority(.required - 1),
|
||||||
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor),
|
avatarButton.widthAnchor.constraint(equalToConstant: NotificationAvatarButton.containerSize.height).priority(.required - 1),
|
||||||
avatarImageView.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor),
|
|
||||||
avatarImageView.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
|
|
||||||
avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
|
|
||||||
avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
|
|
||||||
])
|
|
||||||
|
|
||||||
actionImageView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
avatarContainer.addSubview(actionImageView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
actionImageView.centerYAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
|
|
||||||
actionImageView.centerXAnchor.constraint(equalTo: avatarContainer.trailingAnchor),
|
|
||||||
actionImageView.widthAnchor.constraint(equalToConstant: NotificationStatusTableViewCell.actionImageViewSize.width).priority(.required - 1),
|
|
||||||
actionImageView.heightAnchor.constraint(equalTo: actionImageView.widthAnchor, multiplier: 1.0),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
containerStackView.addArrangedSubview(contentStackView)
|
containerStackView.addArrangedSubview(contentStackView)
|
||||||
|
@ -274,10 +236,8 @@ extension NotificationStatusTableViewCell {
|
||||||
filteredLabel.isHidden = true
|
filteredLabel.isHidden = true
|
||||||
|
|
||||||
statusView.delegate = self
|
statusView.delegate = self
|
||||||
|
|
||||||
let avatarImageViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
avatarButton.addTarget(self, action: #selector(NotificationStatusTableViewCell.avatarButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
avatarImageViewTapGestureRecognizer.addTarget(self, action: #selector(NotificationStatusTableViewCell.avatarImageViewTapGestureRecognizerHandler(_:)))
|
|
||||||
avatarImageView.addGestureRecognizer(avatarImageViewTapGestureRecognizer)
|
|
||||||
let authorNameLabelTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
let authorNameLabelTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
authorNameLabelTapGestureRecognizer.addTarget(self, action: #selector(NotificationStatusTableViewCell.authorNameLabelTapGestureRecognizerHandler(_:)))
|
authorNameLabelTapGestureRecognizer.addTarget(self, action: #selector(NotificationStatusTableViewCell.authorNameLabelTapGestureRecognizerHandler(_:)))
|
||||||
nameLabel.addGestureRecognizer(authorNameLabelTapGestureRecognizer)
|
nameLabel.addGestureRecognizer(authorNameLabelTapGestureRecognizer)
|
||||||
|
@ -312,8 +272,8 @@ extension NotificationStatusTableViewCell {
|
||||||
extension NotificationStatusTableViewCell {
|
extension NotificationStatusTableViewCell {
|
||||||
|
|
||||||
private func setupBackgroundColor(theme: Theme) {
|
private func setupBackgroundColor(theme: Theme) {
|
||||||
actionImageView.layer.borderColor = theme.systemBackgroundColor.cgColor
|
// actionImageView.layer.borderColor = theme.systemBackgroundColor.cgColor
|
||||||
avatarImageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor
|
// avatarImageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor
|
||||||
statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor
|
statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor
|
||||||
statusContainerView.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
statusContainerView.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||||
return traitCollection.userInterfaceStyle == .light ? theme.systemBackgroundColor : theme.tertiarySystemGroupedBackgroundColor
|
return traitCollection.userInterfaceStyle == .light ? theme.systemBackgroundColor : theme.tertiarySystemGroupedBackgroundColor
|
||||||
|
@ -323,9 +283,9 @@ extension NotificationStatusTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationStatusTableViewCell {
|
extension NotificationStatusTableViewCell {
|
||||||
@objc private func avatarImageViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
@objc private func avatarButtonDidPressed(_ sender: UIButton) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
delegate?.notificationStatusTableViewCell(self, avatarImageViewDidPressed: avatarImageView)
|
delegate?.notificationStatusTableViewCell(self, avatarImageViewDidPressed: avatarButton.avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func authorNameLabelTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
@objc private func authorNameLabelTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||||
|
@ -408,6 +368,5 @@ extension NotificationStatusTableViewCell {
|
||||||
extension NotificationStatusTableViewCell: AvatarConfigurableView {
|
extension NotificationStatusTableViewCell: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { CGSize(width: 35, height: 35) }
|
static var configurableAvatarImageSize: CGSize { CGSize(width: 35, height: 35) }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
||||||
var configurableAvatarImageView: UIImageView? { avatarImageView }
|
var configurableAvatarImageView: FLAnimatedImageView? { avatarButton.avatarImageView }
|
||||||
var configurableAvatarButton: UIButton? { nil }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ final class ProfileHeaderView: UIView {
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let avatarImageView: UIImageView = {
|
let avatarImageView: FLAnimatedImageView = {
|
||||||
let imageView = FLAnimatedImageView()
|
let imageView = FLAnimatedImageView()
|
||||||
let placeholderImage = UIImage
|
let placeholderImage = UIImage
|
||||||
.placeholder(size: ProfileHeaderView.avatarImageViewSize, color: Asset.Theme.Mastodon.systemGroupedBackground.color)
|
.placeholder(size: ProfileHeaderView.avatarImageViewSize, color: Asset.Theme.Mastodon.systemGroupedBackground.color)
|
||||||
|
@ -559,8 +559,7 @@ extension ProfileHeaderView: ProfileStatusDashboardViewDelegate {
|
||||||
extension ProfileHeaderView: AvatarConfigurableView {
|
extension ProfileHeaderView: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { avatarImageViewSize }
|
static var configurableAvatarImageSize: CGSize { avatarImageViewSize }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { avatarImageViewCornerRadius }
|
static var configurableAvatarImageCornerRadius: CGFloat { avatarImageViewCornerRadius }
|
||||||
var configurableAvatarImageView: UIImageView? { return avatarImageView }
|
var configurableAvatarImageView: FLAnimatedImageView? { return avatarImageView }
|
||||||
var configurableAvatarButton: UIButton? { return nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,11 @@ import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
import Nuke
|
|
||||||
|
|
||||||
final class SearchResultTableViewCell: UITableViewCell {
|
final class SearchResultTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
let _imageView: UIImageView = {
|
let _imageView: AvatarImageView = {
|
||||||
let imageView = FLAnimatedImageView()
|
let imageView = AvatarImageView()
|
||||||
imageView.tintColor = Asset.Colors.Label.primary.color
|
imageView.tintColor = Asset.Colors.Label.primary.color
|
||||||
imageView.layer.cornerRadius = 4
|
imageView.layer.cornerRadius = 4
|
||||||
imageView.clipsToBounds = true
|
imageView.clipsToBounds = true
|
||||||
|
@ -48,7 +47,7 @@ final class SearchResultTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
Nuke.cancelRequest(for: _imageView)
|
_imageView.af.cancelImageRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
@ -155,32 +154,19 @@ extension SearchResultTableViewCell {
|
||||||
extension SearchResultTableViewCell {
|
extension SearchResultTableViewCell {
|
||||||
|
|
||||||
func config(with account: Mastodon.Entity.Account) {
|
func config(with account: Mastodon.Entity.Account) {
|
||||||
Nuke.loadImage(
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
||||||
with: account.avatarImageURL(),
|
|
||||||
options: ImageLoadingOptions(
|
|
||||||
placeholder: UIImage.placeholder(color: .systemFill),
|
|
||||||
transition: .fadeIn(duration: 0.2)
|
|
||||||
),
|
|
||||||
into: _imageView
|
|
||||||
)
|
|
||||||
_titleLabel.text = account.displayName.isEmpty ? account.username : account.displayName
|
_titleLabel.text = account.displayName.isEmpty ? account.username : account.displayName
|
||||||
_subTitleLabel.text = account.acct
|
_subTitleLabel.text = account.acct
|
||||||
}
|
}
|
||||||
|
|
||||||
func config(with account: MastodonUser) {
|
func config(with account: MastodonUser) {
|
||||||
Nuke.loadImage(
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: account.avatarImageURL()))
|
||||||
with: account.avatarImageURL(),
|
|
||||||
options: ImageLoadingOptions(
|
|
||||||
placeholder: UIImage.placeholder(color: .systemFill),
|
|
||||||
transition: .fadeIn(duration: 0.2)
|
|
||||||
),
|
|
||||||
into: _imageView
|
|
||||||
)
|
|
||||||
_titleLabel.text = account.displayNameWithFallback
|
_titleLabel.text = account.displayNameWithFallback
|
||||||
_subTitleLabel.text = account.acct
|
_subTitleLabel.text = account.acct
|
||||||
}
|
}
|
||||||
|
|
||||||
func config(with tag: Mastodon.Entity.Tag) {
|
func config(with tag: Mastodon.Entity.Tag) {
|
||||||
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: nil))
|
||||||
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
||||||
_imageView.image = image
|
_imageView.image = image
|
||||||
_titleLabel.text = "#" + tag.name
|
_titleLabel.text = "#" + tag.name
|
||||||
|
@ -195,6 +181,7 @@ extension SearchResultTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func config(with tag: Tag) {
|
func config(with tag: Tag) {
|
||||||
|
configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: nil))
|
||||||
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
let image = UIImage(systemName: "number.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 34, weight: .regular))!.withRenderingMode(.alwaysTemplate)
|
||||||
_imageView.image = image
|
_imageView.image = image
|
||||||
_titleLabel.text = "# " + tag.name
|
_titleLabel.text = "# " + tag.name
|
||||||
|
@ -211,6 +198,13 @@ extension SearchResultTableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - AvatarStackedImageView
|
||||||
|
extension SearchResultTableViewCell: AvatarConfigurableView {
|
||||||
|
static var configurableAvatarImageSize: CGSize { CGSize(width: 42, height: 42) }
|
||||||
|
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
||||||
|
var configurableAvatarImageView: FLAnimatedImageView? { _imageView }
|
||||||
|
}
|
||||||
|
|
||||||
#if canImport(SwiftUI) && DEBUG
|
#if canImport(SwiftUI) && DEBUG
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
//
|
|
||||||
// AvatarBarButtonItem.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021-2-4.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
final class AvatarBarButtonItem: UIBarButtonItem {
|
|
||||||
|
|
||||||
static let avatarButtonSize = CGSize(width: 32, height: 32)
|
|
||||||
|
|
||||||
let avatarButton: UIButton = {
|
|
||||||
let button = UIButton(type: .custom)
|
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
button.widthAnchor.constraint(equalToConstant: avatarButtonSize.width).priority(.defaultHigh),
|
|
||||||
button.heightAnchor.constraint(equalToConstant: avatarButtonSize.height).priority(.defaultHigh),
|
|
||||||
])
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
_init()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarBarButtonItem {
|
|
||||||
|
|
||||||
private func _init() {
|
|
||||||
customView = avatarButton
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarBarButtonItem: AvatarConfigurableView {
|
|
||||||
static var configurableAvatarImageSize: CGSize { return avatarButtonSize }
|
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
|
||||||
var configurableAvatarImageView: UIImageView? { return nil }
|
|
||||||
var configurableAvatarButton: UIButton? { return avatarButton }
|
|
||||||
var configurableVerifiedBadgeImageView: UIImageView? { return nil }
|
|
||||||
}
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
//
|
||||||
|
// AvatarButton.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class AvatarButton: UIControl {
|
||||||
|
|
||||||
|
// UIControl.Event - Application: 0x0F000000
|
||||||
|
static let primaryAction = UIControl.Event(rawValue: 1 << 25) // 0x01000000
|
||||||
|
var primaryActionState: UIControl.State = .normal
|
||||||
|
|
||||||
|
var avatarImageSize = CGSize(width: 42, height: 42)
|
||||||
|
let avatarImageView = AvatarImageView()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func _init() {
|
||||||
|
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(avatarImageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarImageView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAppearance() {
|
||||||
|
avatarImageView.alpha = primaryActionState.contains(.highlighted) ? 0.6 : 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AvatarButton {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return avatarImageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
defer { updateAppearance() }
|
||||||
|
|
||||||
|
updateState(touch: touch, event: event)
|
||||||
|
return super.beginTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
defer { updateAppearance() }
|
||||||
|
|
||||||
|
updateState(touch: touch, event: event)
|
||||||
|
return super.continueTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
defer { updateAppearance() }
|
||||||
|
resetState()
|
||||||
|
|
||||||
|
if let touch = touch {
|
||||||
|
if AvatarButton.isTouching(touch, view: self, event: event) {
|
||||||
|
sendActions(for: AvatarButton.primaryAction)
|
||||||
|
} else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.endTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func cancelTracking(with event: UIEvent?) {
|
||||||
|
defer { updateAppearance() }
|
||||||
|
|
||||||
|
resetState()
|
||||||
|
super.cancelTracking(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AvatarButton {
|
||||||
|
|
||||||
|
private static func isTouching(_ touch: UITouch, view: UIView, event: UIEvent?) -> Bool {
|
||||||
|
let location = touch.location(in: view)
|
||||||
|
return view.point(inside: location, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetState() {
|
||||||
|
primaryActionState = .normal
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateState(touch: UITouch, event: UIEvent?) {
|
||||||
|
primaryActionState = AvatarButton.isTouching(touch, view: self, event: event) ? .highlighted : .normal
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if canImport(SwiftUI) && DEBUG
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AvatarButton_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
UIViewPreview(width: 42) {
|
||||||
|
let avatarButton = AvatarButton()
|
||||||
|
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarButton.widthAnchor.constraint(equalToConstant: 42),
|
||||||
|
avatarButton.heightAnchor.constraint(equalToConstant: 42),
|
||||||
|
])
|
||||||
|
return avatarButton
|
||||||
|
}
|
||||||
|
.previewLayout(.fixed(width: 42, height: 42))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -9,19 +9,20 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import FLAnimatedImage
|
import FLAnimatedImage
|
||||||
|
|
||||||
final class AvatarStackedImageView: FLAnimatedImageView { }
|
final class AvatarStackedImageView: AvatarImageView { }
|
||||||
|
|
||||||
// MARK: - AvatarConfigurableView
|
// MARK: - AvatarConfigurableView
|
||||||
extension AvatarStackedImageView: AvatarConfigurableView {
|
extension AvatarStackedImageView: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { CGSize(width: 28, height: 28) }
|
static var configurableAvatarImageSize: CGSize { CGSize(width: 28, height: 28) }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
static var configurableAvatarImageCornerRadius: CGFloat { 4 }
|
||||||
var configurableAvatarImageView: UIImageView? { self }
|
var configurableAvatarImageView: FLAnimatedImageView? { self }
|
||||||
var configurableAvatarButton: UIButton? { nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AvatarStackContainerButton: UIControl {
|
final class AvatarStackContainerButton: UIControl {
|
||||||
|
|
||||||
static let containerSize = CGSize(width: 42, height: 42)
|
static let containerSize = CGSize(width: 42, height: 42)
|
||||||
|
static let avatarImageViewSize = CGSize(width: 28, height: 28)
|
||||||
|
static let avatarImageViewCornerRadius: CGFloat = 4
|
||||||
static let maskOffset: CGFloat = 2
|
static let maskOffset: CGFloat = 2
|
||||||
|
|
||||||
// UIControl.Event - Application: 0x0F000000
|
// UIControl.Event - Application: 0x0F000000
|
||||||
|
@ -46,13 +47,6 @@ final class AvatarStackContainerButton: UIControl {
|
||||||
extension AvatarStackContainerButton {
|
extension AvatarStackContainerButton {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
// GIF get worse when enable rasterize
|
|
||||||
// topLeadingAvatarStackedImageView.layer.shouldRasterize = true
|
|
||||||
// topLeadingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
|
|
||||||
//
|
|
||||||
// bottomTrailingAvatarStackedImageView.layer.shouldRasterize = true
|
|
||||||
// bottomTrailingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
|
|
||||||
|
|
||||||
topLeadingAvatarStackedImageView.translatesAutoresizingMaskIntoConstraints = false
|
topLeadingAvatarStackedImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
addSubview(topLeadingAvatarStackedImageView)
|
addSubview(topLeadingAvatarStackedImageView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -75,16 +69,16 @@ extension AvatarStackContainerButton {
|
||||||
let offset: CGFloat = 2
|
let offset: CGFloat = 2
|
||||||
let path: CGPath = {
|
let path: CGPath = {
|
||||||
let path = CGMutablePath()
|
let path = CGMutablePath()
|
||||||
path.addRect(CGRect(origin: .zero, size: AvatarStackedImageView.configurableAvatarImageSize))
|
path.addRect(CGRect(origin: .zero, size: AvatarStackContainerButton.avatarImageViewSize))
|
||||||
let mirrorScale: CGFloat = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? -1 : 1
|
let mirrorScale: CGFloat = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? -1 : 1
|
||||||
path.addPath(UIBezierPath(
|
path.addPath(UIBezierPath(
|
||||||
roundedRect: CGRect(
|
roundedRect: CGRect(
|
||||||
x: mirrorScale * (AvatarStackContainerButton.containerSize.width - AvatarStackedImageView.configurableAvatarImageSize.width - offset),
|
x: mirrorScale * (AvatarStackContainerButton.containerSize.width - AvatarStackContainerButton.avatarImageViewSize.width - offset),
|
||||||
y: AvatarStackContainerButton.containerSize.height - AvatarStackedImageView.configurableAvatarImageSize.height - offset,
|
y: AvatarStackContainerButton.containerSize.height - AvatarStackContainerButton.avatarImageViewSize.height - offset,
|
||||||
width: AvatarStackedImageView.configurableAvatarImageSize.width,
|
width: AvatarStackContainerButton.avatarImageViewSize.width,
|
||||||
height: AvatarStackedImageView.configurableAvatarImageSize.height
|
height: AvatarStackContainerButton.avatarImageViewSize.height
|
||||||
),
|
),
|
||||||
cornerRadius: AvatarStackedImageView.configurableAvatarImageCornerRadius
|
cornerRadius: AvatarStackedImageView.configurableAvatarImageCornerRadius + 1 // 1pt overshoot
|
||||||
).cgPath)
|
).cgPath)
|
||||||
return path
|
return path
|
||||||
}()
|
}()
|
||||||
|
@ -93,9 +87,6 @@ extension AvatarStackContainerButton {
|
||||||
maskShapeLayer.fillRule = .evenOdd
|
maskShapeLayer.fillRule = .evenOdd
|
||||||
maskShapeLayer.path = path
|
maskShapeLayer.path = path
|
||||||
topLeadingAvatarStackedImageView.layer.mask = maskShapeLayer
|
topLeadingAvatarStackedImageView.layer.mask = maskShapeLayer
|
||||||
|
|
||||||
topLeadingAvatarStackedImageView.image = UIImage.placeholder(color: .systemFill)
|
|
||||||
bottomTrailingAvatarStackedImageView.image = UIImage.placeholder(color: .systemFill)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
|
@ -95,10 +95,7 @@ final class StatusView: UIView {
|
||||||
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
|
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
let avatarImageView: FLAnimatedImageView = {
|
let avatarButton = AvatarButton()
|
||||||
let imageView = FLAnimatedImageView()
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
||||||
|
|
||||||
let nameLabel: ActiveLabel = {
|
let nameLabel: ActiveLabel = {
|
||||||
|
@ -317,13 +314,13 @@ extension StatusView {
|
||||||
avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1),
|
avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1),
|
||||||
avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1),
|
avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1),
|
||||||
])
|
])
|
||||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
avatarButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
avatarView.addSubview(avatarImageView)
|
avatarView.addSubview(avatarButton)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
avatarImageView.topAnchor.constraint(equalTo: avatarView.topAnchor),
|
avatarButton.topAnchor.constraint(equalTo: avatarView.topAnchor),
|
||||||
avatarImageView.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor),
|
avatarButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor),
|
||||||
avatarImageView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor),
|
avatarButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor),
|
||||||
avatarImageView.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor),
|
avatarButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor),
|
||||||
])
|
])
|
||||||
avatarStackedContainerButton.translatesAutoresizingMaskIntoConstraints = false
|
avatarStackedContainerButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
avatarView.addSubview(avatarStackedContainerButton)
|
avatarView.addSubview(avatarStackedContainerButton)
|
||||||
|
@ -473,11 +470,7 @@ extension StatusView {
|
||||||
headerInfoLabel.isUserInteractionEnabled = true
|
headerInfoLabel.isUserInteractionEnabled = true
|
||||||
headerInfoLabel.addGestureRecognizer(headerInfoLabelTapGestureRecognizer)
|
headerInfoLabel.addGestureRecognizer(headerInfoLabelTapGestureRecognizer)
|
||||||
|
|
||||||
let avatarImageViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
avatarButton.addTarget(self, action: #selector(StatusView.avatarButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
avatarImageViewTapGestureRecognizer.addTarget(self, action: #selector(StatusView.avatarImageViewDidPressed(_:)))
|
|
||||||
avatarImageView.addGestureRecognizer(avatarImageViewTapGestureRecognizer)
|
|
||||||
avatarImageView.isUserInteractionEnabled = true
|
|
||||||
|
|
||||||
avatarStackedContainerButton.addTarget(self, action: #selector(StatusView.avatarStackedContainerButtonDidPressed(_:)), for: .touchUpInside)
|
avatarStackedContainerButton.addTarget(self, action: #selector(StatusView.avatarStackedContainerButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
revealContentWarningButton.addTarget(self, action: #selector(StatusView.revealContentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
revealContentWarningButton.addTarget(self, action: #selector(StatusView.revealContentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
pollVoteButton.addTarget(self, action: #selector(StatusView.pollVoteButtonPressed(_:)), for: .touchUpInside)
|
pollVoteButton.addTarget(self, action: #selector(StatusView.pollVoteButtonPressed(_:)), for: .touchUpInside)
|
||||||
|
@ -544,9 +537,9 @@ extension StatusView {
|
||||||
delegate?.statusView(self, headerInfoLabelDidPressed: headerInfoLabel)
|
delegate?.statusView(self, headerInfoLabelDidPressed: headerInfoLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func avatarImageViewDidPressed(_ sender: UITapGestureRecognizer) {
|
@objc private func avatarButtonDidPressed(_ sender: UIButton) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
delegate?.statusView(self, avatarImageViewDidPressed: avatarImageView)
|
delegate?.statusView(self, avatarImageViewDidPressed: avatarButton.avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func avatarStackedContainerButtonDidPressed(_ sender: UIButton) {
|
@objc private func avatarStackedContainerButtonDidPressed(_ sender: UIButton) {
|
||||||
|
@ -633,8 +626,7 @@ extension StatusView: PlayerContainerViewDelegate {
|
||||||
extension StatusView: AvatarConfigurableView {
|
extension StatusView: AvatarConfigurableView {
|
||||||
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize }
|
||||||
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
static var configurableAvatarImageCornerRadius: CGFloat { return 4 }
|
||||||
var configurableAvatarImageView: UIImageView? { avatarImageView }
|
var configurableAvatarImageView: FLAnimatedImageView? { avatarButton.avatarImageView }
|
||||||
var configurableAvatarButton: UIButton? { nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(SwiftUI) && DEBUG
|
#if canImport(SwiftUI) && DEBUG
|
||||||
|
@ -662,7 +654,7 @@ struct StatusView_Previews: PreviewProvider {
|
||||||
UIViewPreview(width: 375) {
|
UIViewPreview(width: 375) {
|
||||||
let statusView = StatusView()
|
let statusView = StatusView()
|
||||||
statusView.headerContainerView.isHidden = false
|
statusView.headerContainerView.isHidden = false
|
||||||
statusView.avatarImageView.isHidden = true
|
statusView.avatarButton.isHidden = true
|
||||||
statusView.avatarStackedContainerButton.isHidden = false
|
statusView.avatarStackedContainerButton.isHidden = false
|
||||||
statusView.avatarStackedContainerButton.topLeadingAvatarStackedImageView.configure(
|
statusView.avatarStackedContainerButton.topLeadingAvatarStackedImageView.configure(
|
||||||
with: AvatarConfigurableViewConfiguration(
|
with: AvatarConfigurableViewConfiguration(
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
//
|
||||||
|
// AvatarImageView.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
|
class AvatarImageView: FLAnimatedImageView { }
|
|
@ -13,7 +13,6 @@ import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
import AlamofireNetworkActivityIndicator
|
import AlamofireNetworkActivityIndicator
|
||||||
import Nuke
|
|
||||||
|
|
||||||
final class APIService {
|
final class APIService {
|
||||||
|
|
||||||
|
@ -34,10 +33,6 @@ final class APIService {
|
||||||
|
|
||||||
// setup cache. 10MB RAM + 50MB Disk
|
// setup cache. 10MB RAM + 50MB Disk
|
||||||
URLCache.shared = URLCache(memoryCapacity: 10 * 1024 * 1024, diskCapacity: 50 * 1024 * 1024, diskPath: nil)
|
URLCache.shared = URLCache(memoryCapacity: 10 * 1024 * 1024, diskCapacity: 50 * 1024 * 1024, diskPath: nil)
|
||||||
|
|
||||||
// setup Nuke cache
|
|
||||||
// using LRU disk cache
|
|
||||||
ImagePipeline.shared = ImagePipeline(configuration: .withDataCache)
|
|
||||||
|
|
||||||
// enable network activity manager for AlamofireImage
|
// enable network activity manager for AlamofireImage
|
||||||
NetworkActivityIndicatorManager.shared.isEnabled = true
|
NetworkActivityIndicatorManager.shared.isEnabled = true
|
||||||
|
|
|
@ -9,7 +9,9 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Photos
|
import Photos
|
||||||
|
import Alamofire
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
|
import FLAnimatedImage
|
||||||
|
|
||||||
final class PhotoLibraryService: NSObject {
|
final class PhotoLibraryService: NSObject {
|
||||||
|
|
||||||
|
@ -19,88 +21,150 @@ extension PhotoLibraryService {
|
||||||
|
|
||||||
enum PhotoLibraryError: Error {
|
enum PhotoLibraryError: Error {
|
||||||
case noPermission
|
case noPermission
|
||||||
|
case badPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ImageSource {
|
||||||
|
case url(URL)
|
||||||
|
case image(UIImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PhotoLibraryService {
|
extension PhotoLibraryService {
|
||||||
|
|
||||||
func saveImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
func save(imageSource source: ImageSource) -> AnyPublisher<Void, Error> {
|
||||||
|
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
|
|
||||||
|
let imageDataPublisher: AnyPublisher<Data, Error> = {
|
||||||
|
switch source {
|
||||||
|
case .url(let url):
|
||||||
|
return PhotoLibraryService.fetchImageData(url: url)
|
||||||
|
case .image(let image):
|
||||||
|
return PhotoLibraryService.fetchImageData(image: image)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return imageDataPublisher
|
||||||
|
.flatMap { data in
|
||||||
|
PhotoLibraryService.save(imageData: data)
|
||||||
|
}
|
||||||
|
.handleEvents(receiveSubscription: { _ in
|
||||||
|
impactFeedbackGenerator.impactOccurred()
|
||||||
|
}, receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure:
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||||
|
case .finished:
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PhotoLibraryService {
|
||||||
|
|
||||||
|
func copy(imageSource source: ImageSource) -> AnyPublisher<Void, Error> {
|
||||||
|
|
||||||
|
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
|
let imageDataPublisher: AnyPublisher<Data, Error> = {
|
||||||
|
switch source {
|
||||||
|
case .url(let url):
|
||||||
|
return PhotoLibraryService.fetchImageData(url: url)
|
||||||
|
case .image(let image):
|
||||||
|
return PhotoLibraryService.fetchImageData(image: image)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return imageDataPublisher
|
||||||
|
.flatMap { data in
|
||||||
|
PhotoLibraryService.copy(imageData: data)
|
||||||
|
}
|
||||||
|
.handleEvents(receiveSubscription: { _ in
|
||||||
|
impactFeedbackGenerator.impactOccurred()
|
||||||
|
}, receiveCompletion: { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure:
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||||
|
case .finished:
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PhotoLibraryService {
|
||||||
|
|
||||||
|
static func fetchImageData(url: URL) -> AnyPublisher<Data, Error> {
|
||||||
|
AF.request(url).publishData()
|
||||||
|
.tryMap { response in
|
||||||
|
switch response.result {
|
||||||
|
case .success(let data):
|
||||||
|
return data
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func fetchImageData(image: UIImage) -> AnyPublisher<Data, Error> {
|
||||||
|
return Future<Data, Error> { promise in
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
let imageData = image.pngData()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let imageData = imageData {
|
||||||
|
promise(.success(imageData))
|
||||||
|
} else {
|
||||||
|
promise(.failure(PhotoLibraryError.badPayload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func save(imageData: Data) -> AnyPublisher<Void, Error> {
|
||||||
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
||||||
return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher()
|
return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return processImage(url: url)
|
return Future<Void, Error> { promise in
|
||||||
.handleEvents(receiveOutput: { image in
|
PHPhotoLibrary.shared().performChanges {
|
||||||
self.save(image: image)
|
PHAssetCreationRequest.forAsset().addResource(with: .photo, data: imageData, options: nil)
|
||||||
})
|
} completionHandler: { isSuccess, error in
|
||||||
.eraseToAnyPublisher()
|
if let error = error {
|
||||||
}
|
|
||||||
|
|
||||||
func copyImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
|
||||||
return processImage(url: url)
|
|
||||||
.handleEvents(receiveOutput: { image in
|
|
||||||
UIPasteboard.general.image = image
|
|
||||||
})
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
func processImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
|
||||||
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
|
||||||
|
|
||||||
return Future<UIImage, Error> { promise in
|
|
||||||
ImageDownloader.default.download(URLRequest(url: url), completion: { response in
|
|
||||||
switch response.result {
|
|
||||||
case .failure(let error):
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
|
||||||
promise(.failure(error))
|
promise(.failure(error))
|
||||||
case .success(let image):
|
} else {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
promise(.success(Void()))
|
||||||
promise(.success(image))
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
.handleEvents(receiveSubscription: { _ in
|
|
||||||
impactFeedbackGenerator.impactOccurred()
|
|
||||||
}, receiveCompletion: { completion in
|
|
||||||
switch completion {
|
|
||||||
case .failure:
|
|
||||||
notificationFeedbackGenerator.notificationOccurred(.error)
|
|
||||||
case .finished:
|
|
||||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(image: UIImage, withNotificationFeedback: Bool = false) {
|
static func copy(imageData: Data) -> AnyPublisher<Void, Error> {
|
||||||
UIImageWriteToSavedPhotosAlbum(
|
Future<Void, Error> { promise in
|
||||||
image,
|
DispatchQueue.global().async {
|
||||||
self,
|
let image = UIImage(data: imageData, scale: UIScreen.main.scale)
|
||||||
#selector(PhotoLibraryService.image(_:didFinishSavingWithError:contextInfo:)),
|
DispatchQueue.main.async {
|
||||||
nil
|
if let image = image {
|
||||||
)
|
UIPasteboard.general.image = image
|
||||||
|
promise(.success(Void()))
|
||||||
// assert no error
|
} else {
|
||||||
if withNotificationFeedback {
|
promise(.failure(PhotoLibraryError.badPayload))
|
||||||
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
}
|
||||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(image: UIImage, withNotificationFeedback: Bool = false) {
|
|
||||||
UIPasteboard.general.image = image
|
|
||||||
|
|
||||||
// assert no error
|
|
||||||
if withNotificationFeedback {
|
|
||||||
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
|
||||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
|
|
||||||
// TODO: notify banner
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue