diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 66627d459b..97af02b765 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -74,7 +74,6 @@ 7B2A116122C20F5F00AD029C /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2A116022C20F5F00AD029C /* PlayerView.swift */; }; 7B2A116322C2269800AD029C /* EmergencyContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2A116222C2269800AD029C /* EmergencyContactViewController.swift */; }; 7B2A116722C2497000AD029C /* ViewEmergencyContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2A116622C2497000AD029C /* ViewEmergencyContactViewController.swift */; }; - 7B2AFEE220FE022C00C747BB /* MXMFastURLDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B2AFEE120FE022C00C747BB /* MXMFastURLDetector.m */; }; 7B2C4B112436FF5E00D2EB99 /* CircleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7B2C4B102436FF5E00D2EB99 /* CircleCell.xib */; }; 7B2C8B2D2524D26900347AC3 /* ClipSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2C8B2C2524D26900347AC3 /* ClipSwitcherViewController.swift */; }; 7B2C8B312524D6C100347AC3 /* ClipThumbnailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2C8B302524D6C100347AC3 /* ClipThumbnailCell.swift */; }; @@ -212,7 +211,6 @@ 7B71F5262191CC1500F93B98 /* WalletHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B71F5252191CC1500F93B98 /* WalletHeaderView.swift */; }; 7B7264F0209C56E500F234BE /* TextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7264EF209C56E500F234BE /* TextLabel.swift */; }; 7B7264F5209C5B1A00F234BE /* CoreTextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7264F4209C5B1A00F234BE /* CoreTextLabel.swift */; }; - 7B72651C209C5F9300F234BE /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B72651B209C5F9300F234BE /* Link.swift */; }; 7B75E6471FE0E20000CD66F1 /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B75E6461FE0E20000CD66F1 /* InsetLabel.swift */; }; 7B78B2461FF2170400371632 /* InviteLinkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B78B2451FF2170400371632 /* InviteLinkViewController.swift */; }; 7B7A243B236A9B86003F1458 /* RoundedInsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7A243A236A9B86003F1458 /* RoundedInsetLabel.swift */; }; @@ -604,6 +602,9 @@ 7C66F1BF268B3514006D8462 /* HomeAppsManager+FolderInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C66F1BE268B3514006D8462 /* HomeAppsManager+FolderInteraction.swift */; }; 7C66F1C1268C0250006D8462 /* HomeAppsFolderInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C66F1C0268C0250006D8462 /* HomeAppsFolderInteraction.swift */; }; 7C695D56285B25AB0042177C /* SpotlightManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C695D55285B25AB0042177C /* SpotlightManager.swift */; }; + 7C6967F82864488E0042177C /* SharedMediaLinkTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6967F72864488E0042177C /* SharedMediaLinkTableViewController.swift */; }; + 7C6967FB286450DB0042177C /* SharedMediaLinkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6967F9286450DB0042177C /* SharedMediaLinkCell.swift */; }; + 7C6967FC286450DB0042177C /* SharedMediaLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7C6967FA286450DB0042177C /* SharedMediaLinkCell.xib */; }; 7C6AD1F526A6794F00E6C41D /* StickersStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6AD1F426A6794F00E6C41D /* StickersStoreViewController.swift */; }; 7C7635B826A13461006101DB /* HomeAppsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7635B726A13461006101DB /* HomeAppsConstants.swift */; }; 7C8CC5A4280D347A00F7CBDF /* PreviewWallpaperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */; }; @@ -1094,8 +1095,6 @@ 7B2A116022C20F5F00AD029C /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; 7B2A116222C2269800AD029C /* EmergencyContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmergencyContactViewController.swift; sourceTree = ""; }; 7B2A116622C2497000AD029C /* ViewEmergencyContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewEmergencyContactViewController.swift; sourceTree = ""; }; - 7B2AFEE020FE022C00C747BB /* MXMFastURLDetector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXMFastURLDetector.h; sourceTree = ""; }; - 7B2AFEE120FE022C00C747BB /* MXMFastURLDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXMFastURLDetector.m; sourceTree = ""; }; 7B2C4B102436FF5E00D2EB99 /* CircleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CircleCell.xib; sourceTree = ""; }; 7B2C8B2C2524D26900347AC3 /* ClipSwitcherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipSwitcherViewController.swift; sourceTree = ""; }; 7B2C8B302524D6C100347AC3 /* ClipThumbnailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipThumbnailCell.swift; sourceTree = ""; }; @@ -1231,7 +1230,6 @@ 7B71F5252191CC1500F93B98 /* WalletHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletHeaderView.swift; sourceTree = ""; }; 7B7264EF209C56E500F234BE /* TextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLabel.swift; sourceTree = ""; }; 7B7264F4209C5B1A00F234BE /* CoreTextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTextLabel.swift; sourceTree = ""; }; - 7B72651B209C5F9300F234BE /* Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; 7B75E6461FE0E20000CD66F1 /* InsetLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLabel.swift; sourceTree = ""; }; 7B78B2451FF2170400371632 /* InviteLinkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteLinkViewController.swift; sourceTree = ""; }; 7B7A243A236A9B86003F1458 /* RoundedInsetLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedInsetLabel.swift; sourceTree = ""; }; @@ -1624,6 +1622,9 @@ 7C66F1BE268B3514006D8462 /* HomeAppsManager+FolderInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeAppsManager+FolderInteraction.swift"; sourceTree = ""; }; 7C66F1C0268C0250006D8462 /* HomeAppsFolderInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsFolderInteraction.swift; sourceTree = ""; }; 7C695D55285B25AB0042177C /* SpotlightManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightManager.swift; sourceTree = ""; }; + 7C6967F72864488E0042177C /* SharedMediaLinkTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMediaLinkTableViewController.swift; sourceTree = ""; }; + 7C6967F9286450DB0042177C /* SharedMediaLinkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMediaLinkCell.swift; sourceTree = ""; }; + 7C6967FA286450DB0042177C /* SharedMediaLinkCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SharedMediaLinkCell.xib; sourceTree = ""; }; 7C6AD1F426A6794F00E6C41D /* StickersStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersStoreViewController.swift; sourceTree = ""; }; 7C7635B726A13461006101DB /* HomeAppsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsConstants.swift; sourceTree = ""; }; 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWallpaperViewController.swift; sourceTree = ""; }; @@ -2382,9 +2383,6 @@ 7B72651A209C5F8300F234BE /* Models */ = { isa = PBXGroup; children = ( - 7B72651B209C5F9300F234BE /* Link.swift */, - 7B2AFEE020FE022C00C747BB /* MXMFastURLDetector.h */, - 7B2AFEE120FE022C00C747BB /* MXMFastURLDetector.m */, 7B61B9C52538E0CE001F94A3 /* ScreenHeight.swift */, 7B7ABBDF2539799900070B71 /* ScreenWidth.swift */, 7BEE5352222E21D7008D3911 /* KeyboardHeight.swift */, @@ -2883,6 +2881,7 @@ 7B8BB585234EE9A200991ACB /* SharedMediaAudioTableViewController.swift */, 7B8A6A6C2456C39F00ADC9EB /* SharedMediaPostTableViewController.swift */, 7BCEAA752350A20600F07635 /* SharedMediaDataTableViewController.swift */, + 7C6967F72864488E0042177C /* SharedMediaLinkTableViewController.swift */, 7B3FD4EB241965AA00B58006 /* LocationViewController.swift */, 7B36919E233A1962007321A7 /* LocationPickerViewController.swift */, 7BB5250224160F3D0060DAE1 /* LocationPreviewViewController.swift */, @@ -3604,6 +3603,8 @@ 7B8BB591234F37D100991ACB /* SharedMediaAudioCell.swift */, 7BCEAA772350A29000F07635 /* SharedMediaDataCell.xib */, 7BCEAA792350A3A200F07635 /* SharedMediaDataCell.swift */, + 7C6967FA286450DB0042177C /* SharedMediaLinkCell.xib */, + 7C6967F9286450DB0042177C /* SharedMediaLinkCell.swift */, 7B8A6A732456E0EC00ADC9EB /* SharedMediaPostCell.xib */, 7B8A6A722456E0EC00ADC9EB /* SharedMediaPostCell.swift */, 7BFCB7752419FC5B00E7BC43 /* LocationCell.xib */, @@ -4078,6 +4079,7 @@ 7BFE47DD228432A600FC4379 /* PeerInfoView.xib in Resources */, 7B8A6A752456E0EC00ADC9EB /* SharedMediaPostCell.xib in Resources */, 7B8BB58E234F34FE00991ACB /* SharedMediaAudioCell.xib in Resources */, + 7C6967FC286450DB0042177C /* SharedMediaLinkCell.xib in Resources */, 7BCA2E302452CBF6005BD2E4 /* AboutTableHeaderView.xib in Resources */, DF8CECE71FC317BF00E40064 /* PayWindow.xib in Resources */, 7C6132B827953B4F002777EE /* DeleteAccountAbortWindow.xib in Resources */, @@ -4427,6 +4429,7 @@ 7BF49DD320C3DBAC00A8510E /* CaptchaManager.swift in Sources */, DF8CECE11FC3054700E40064 /* TransferTypeCell.swift in Sources */, 7BCB8C8422BB56B8002A13CC /* DataAndStorageSettingsViewController.swift in Sources */, + 7C6967F82864488E0042177C /* SharedMediaLinkTableViewController.swift in Sources */, 9BB351671FB19ECB00EDDD2C /* ConversationDateHeaderView.swift in Sources */, DF2819752014669E001EE5FA /* RefreshAccountJob.swift in Sources */, DF768563200647CC0055F3FF /* BouncingButton.swift in Sources */, @@ -4601,6 +4604,8 @@ 7C2475C727795BC100112A30 /* DeleteAccountTableHeaderView.swift in Sources */, 7BACFED32459C47C0033A3CD /* BubblePath.swift in Sources */, ABE865BE23F2455800C0167D /* AuthorizationScope.swift in Sources */, + 7C6967FB286450DB0042177C /* SharedMediaLinkCell.swift in Sources */, + ABE865BE23F2455800C0167D /* Scope.swift in Sources */, DF121F351FA1C809000F701D /* CornerImageView.swift in Sources */, 9438252725EE697300709B7D /* CacheableAssetFileManager.swift in Sources */, 7BFD345C2285AB4200524EA0 /* AddMemberViewController.swift in Sources */, @@ -4996,7 +5001,6 @@ 7B88655B20AC3417002D15E4 /* PhotoRepresentableMessageCell.swift in Sources */, 7BF42E4122DDF0D9005066E6 /* GalleryAnimatable.swift in Sources */, E09C5EC123703FEE0095F729 /* ProfileShortcutView.swift in Sources */, - 7B2AFEE220FE022C00C747BB /* MXMFastURLDetector.m in Sources */, DF1F277C21A53585009A74C6 /* BackupViewController.swift in Sources */, ABCB87DD23F18A3D00780E60 /* PermissionsViewController.swift in Sources */, 7C66F1BF268B3514006D8462 /* HomeAppsManager+FolderInteraction.swift in Sources */, @@ -5053,7 +5057,6 @@ 7B91056222CF4A58002E3608 /* LiveMessageCell.swift in Sources */, 7BEED0D9244D752700F3A2B9 /* SettingsRow.swift in Sources */, 94C6AA09280D35990011AB02 /* AttachmentDiagnosticViewController.swift in Sources */, - 7B72651C209C5F9300F234BE /* Link.swift in Sources */, 7BD344072334CD5F005C26E3 /* UserHandleViewController.swift in Sources */, 7B28FA18201196F80023B28D /* DataMessageCell.swift in Sources */, DF121F311FA1C767000F701D /* ConversationCell.swift in Sources */, diff --git a/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/Contents.json b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/Contents.json new file mode 100644 index 0000000000..93f16d1c1c --- /dev/null +++ b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_shared_link@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_shared_link@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@2x.png b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@2x.png new file mode 100644 index 0000000000..d8c466bd5b Binary files /dev/null and b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@2x.png differ diff --git a/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@3x.png b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@3x.png new file mode 100644 index 0000000000..912aa02057 Binary files /dev/null and b/Mixin/Assets.xcassets/Empty Indicator/ic_shared_link.imageset/ic_shared_link@3x.png differ diff --git a/Mixin/Extension/CoreTextExtension.swift b/Mixin/Extension/CoreTextExtension.swift index 3e050561ed..fb0cf7fd4d 100644 --- a/Mixin/Extension/CoreTextExtension.swift +++ b/Mixin/Extension/CoreTextExtension.swift @@ -1,4 +1,5 @@ import CoreText +import Foundation extension CTLine { diff --git a/Mixin/Mixin-Bridging-Header.h b/Mixin/Mixin-Bridging-Header.h index cb13a8af76..fa18261a48 100644 --- a/Mixin/Mixin-Bridging-Header.h +++ b/Mixin/Mixin-Bridging-Header.h @@ -2,7 +2,6 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "MXMFastURLDetector.h" #import "opusfile.h" #import "opusenc.h" #import "opusenc_set_bitrate.h" diff --git a/Mixin/Resources/en.lproj/Localizable.strings b/Mixin/Resources/en.lproj/Localizable.strings index a902465beb..e45931afe1 100644 --- a/Mixin/Resources/en.lproj/Localizable.strings +++ b/Mixin/Resources/en.lproj/Localizable.strings @@ -465,6 +465,7 @@ "line_busy_remote" = "Line busy"; "link_recognition_failure" = "Link recognition failure"; "link_shareable_false" = "The link has been set to not allow sharing"; +"links" = "Links"; "live" = "Live"; "live_shareable_false" = "The live has been set to not allow sharing"; "live_stream" = "live stream"; @@ -541,6 +542,7 @@ "no_files" = "NO FILES"; "no_hidden_assets" = "No hidden assets"; "no_items_selected" = "No Items Selected"; +"no_links" = "NO LINKS"; "no_logs" = "No logs"; "no_media" = "NO MEDIA"; "no_memo" = "No Memo"; diff --git a/Mixin/Resources/ja.lproj/Localizable.strings b/Mixin/Resources/ja.lproj/Localizable.strings index 48793a3ec9..750fbadc65 100644 --- a/Mixin/Resources/ja.lproj/Localizable.strings +++ b/Mixin/Resources/ja.lproj/Localizable.strings @@ -465,6 +465,7 @@ "line_busy_remote" = "お話中です"; "link_recognition_failure" = "リンクの認識に失敗しました"; "link_shareable_false" = "このリンクを共有することはできません"; +"links" = "リンク"; "live" = "配信"; "live_shareable_false" = "この配信を共有することはできません"; "live_stream" = "配信"; @@ -541,6 +542,7 @@ "no_files" = "ファイルがありません"; "no_hidden_assets" = "非表示資産はありません"; "no_items_selected" = "選択してください"; +"no_links" = "リンクがありません"; "no_logs" = "ログがありません"; "no_media" = "メディアがありません"; "no_memo" = "メモなし"; diff --git a/Mixin/Resources/ru.lproj/Localizable.strings b/Mixin/Resources/ru.lproj/Localizable.strings index 85ffbde091..05ede5a148 100644 --- a/Mixin/Resources/ru.lproj/Localizable.strings +++ b/Mixin/Resources/ru.lproj/Localizable.strings @@ -465,6 +465,7 @@ "line_busy_remote" = "Линия занята"; "link_recognition_failure" = "Сбой распознавания ссылки"; "link_shareable_false" = "Ссылка была настроена так, чтобы не разрешать совместное использование"; +"links" = "Ссылки"; "live" = "Вживую"; "live_shareable_false" = "Прямая трансляция была настроена так, чтобы не разрешать делиться"; "live_stream" = "прямой эфир"; @@ -541,6 +542,7 @@ "no_files" = "ФАЙЛОВ НЕТ"; "no_hidden_assets" = "Нет скрытых активов"; "no_items_selected" = "Элементы не выбраны"; +"no_links" = "НЕТ ССЫЛОК"; "no_logs" = "Нет журналов"; "no_media" = "НЕТ МЕДИА"; "no_memo" = "Нет памятки"; diff --git a/Mixin/Resources/zh-Hans.lproj/Localizable.strings b/Mixin/Resources/zh-Hans.lproj/Localizable.strings index e6c0e32e28..6acc551212 100644 --- a/Mixin/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hans.lproj/Localizable.strings @@ -465,6 +465,7 @@ "line_busy_remote" = "对方忙"; "link_recognition_failure" = "链接识别失败"; "link_shareable_false" = "该链接已被设置为不允许分享"; +"links" = "链接"; "live" = "直播"; "live_shareable_false" = "该直播已被设置为不允许转发"; "live_stream" = "直播卡片"; @@ -541,6 +542,7 @@ "no_files" = "没有文件"; "no_hidden_assets" = "没有隐藏的资产"; "no_items_selected" = "未选定项目"; +"no_links" = "没有链接"; "no_logs" = "没有日志"; "no_media" = "没有媒体"; "no_memo" = "没有 Memo(备注)"; diff --git a/Mixin/Resources/zh-Hant.lproj/Localizable.strings b/Mixin/Resources/zh-Hant.lproj/Localizable.strings index 6345ce33db..f8a95f4d32 100644 --- a/Mixin/Resources/zh-Hant.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hant.lproj/Localizable.strings @@ -465,6 +465,7 @@ "line_busy_remote" = "對方忙"; "link_recognition_failure" = "連結識別失敗"; "link_shareable_false" = "該連結已被設定為不允許分享"; +"links" = "連結"; "live" = "直播"; "live_shareable_false" = "該直播已被設定為不允許轉發"; "live_stream" = "直播卡片"; @@ -541,6 +542,7 @@ "no_files" = "沒有檔案"; "no_hidden_assets" = "沒有隱藏的資產"; "no_items_selected" = "未選定專案"; +"no_links" = "沒有連結"; "no_logs" = "沒有日誌"; "no_media" = "沒有媒體"; "no_memo" = "沒有 Memo(備註)"; diff --git a/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.swift b/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.swift new file mode 100644 index 0000000000..20307559df --- /dev/null +++ b/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.swift @@ -0,0 +1,7 @@ +import UIKit + +class SharedMediaLinkCell: ModernSelectedBackgroundCell { + + @IBOutlet weak var linkLabel: UILabel! + +} diff --git a/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.xib b/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.xib new file mode 100644 index 0000000000..df69d95b45 --- /dev/null +++ b/Mixin/UserInterface/Controllers/Chat/Cells/SharedMediaLinkCell.xib @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mixin/UserInterface/Controllers/Chat/Model/DecryptionFailedMessageViewModel.swift b/Mixin/UserInterface/Controllers/Chat/Model/DecryptionFailedMessageViewModel.swift index 2f775d809a..ee9ee6ac2b 100644 --- a/Mixin/UserInterface/Controllers/Chat/Model/DecryptionFailedMessageViewModel.swift +++ b/Mixin/UserInterface/Controllers/Chat/Model/DecryptionFailedMessageViewModel.swift @@ -1,4 +1,5 @@ import UIKit +import MixinServices class DecryptionFailedMessageViewModel: TextMessageViewModel { diff --git a/Mixin/UserInterface/Controllers/Chat/SharedMediaLinkTableViewController.swift b/Mixin/UserInterface/Controllers/Chat/SharedMediaLinkTableViewController.swift new file mode 100644 index 0000000000..8fddd0a0be --- /dev/null +++ b/Mixin/UserInterface/Controllers/Chat/SharedMediaLinkTableViewController.swift @@ -0,0 +1,89 @@ +import UIKit +import MixinServices + +class SharedMediaLinkTableViewController: SharedMediaTableViewController { + + private var dates = [String]() + private var items = [String: [HyperlinkItem]]() + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(R.nib.sharedMediaLinkCell) + tableView.dataSource = self + tableView.delegate = self + reloadData() + } + +} + +extension SharedMediaLinkTableViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + dates.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + items[dates[section]]?.count ?? 0 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.shared_media_link, for: indexPath)! + cell.linkLabel.text = items[dates[indexPath.section]]?[indexPath.row].link + return cell + } + +} + +extension SharedMediaLinkTableViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerReuseId) as! SharedMediaTableHeaderView + view.label.text = dates[section] + return view + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if let item = items[dates[indexPath.section]]?[indexPath.row], let url = URL(string: item.link), let container = parent?.parent { + let context = MixinWebViewController.Context(conversationId: conversationId, initialUrl: url) + MixinWebViewController.presentInstance(with: context, asChildOf: container) + } + } + +} + +extension SharedMediaLinkTableViewController { + + private func reloadData() { + guard let conversationId = self.conversationId else { + return + } + DispatchQueue.global().async { [weak self] in + var dates = [String]() + var items = [String: [HyperlinkItem]]() + let linkItems = HyperlinkDAO.shared.hyperlinks(conversationId: conversationId) + for item in linkItems { + let date = item.createdAt.toUTCDate() + let title = DateFormatter.dateSimple.string(from: date) + if items[title] != nil { + items[title]!.append(item) + } else { + dates.append(title) + items[title] = [item] + } + } + DispatchQueue.main.async { + guard let self = self else { + return + } + self.dates = dates + self.items = items + self.tableView.reloadData() + self.tableView.checkEmpty(dataCount: items.count, + text: R.string.localizable.no_links(), + photo: R.image.emptyIndicator.ic_shared_link()!) + } + } + } + +} diff --git a/Mixin/UserInterface/Controllers/Chat/SharedMediaViewController.swift b/Mixin/UserInterface/Controllers/Chat/SharedMediaViewController.swift index a18bd3f68e..93d01086e9 100644 --- a/Mixin/UserInterface/Controllers/Chat/SharedMediaViewController.swift +++ b/Mixin/UserInterface/Controllers/Chat/SharedMediaViewController.swift @@ -15,6 +15,7 @@ class SharedMediaViewController: UIViewController { private lazy var audioViewController = SharedMediaAudioTableViewController() private lazy var dataViewController = SharedMediaDataTableViewController() private lazy var postViewController = SharedMediaPostTableViewController() + private lazy var linkViewController = SharedMediaLinkTableViewController() private var contentViewController: UIViewController? @@ -24,6 +25,7 @@ class SharedMediaViewController: UIViewController { R.string.localizable.media(), R.string.localizable.audio(), R.string.localizable.post(), + R.string.localizable.links(), R.string.localizable.file() ] load(child: mediaViewController) @@ -37,6 +39,8 @@ class SharedMediaViewController: UIViewController { load(child: audioViewController) case 2: load(child: postViewController) + case 3: + load(child: linkViewController) default: load(child: dataViewController) } diff --git a/Mixin/UserInterface/Controllers/Common/Views/TextLabel.swift b/Mixin/UserInterface/Controllers/Common/Views/TextLabel.swift index 86d7974134..21f58566d0 100644 --- a/Mixin/UserInterface/Controllers/Common/Views/TextLabel.swift +++ b/Mixin/UserInterface/Controllers/Common/Views/TextLabel.swift @@ -1,4 +1,5 @@ import UIKit +import MixinServices class TextLabel: CoreTextLabel { diff --git a/Mixin/UserInterface/Models/Link.swift b/Mixin/UserInterface/Models/Link.swift deleted file mode 100644 index 792b712d5f..0000000000 --- a/Mixin/UserInterface/Models/Link.swift +++ /dev/null @@ -1,22 +0,0 @@ -import UIKit - -class Link { - - struct Range { - let range: NSRange - let url: URL - } - - static let detector = FastURLDetector() - - let hitFrame: CGRect - let backgroundPath: UIBezierPath - let url: URL - - init(hitFrame: CGRect, backgroundPath: UIBezierPath, url: URL) { - self.hitFrame = hitFrame - self.backgroundPath = backgroundPath - self.url = url - } - -} diff --git a/Mixin/UserInterface/Views/CoreTextLabel.swift b/Mixin/UserInterface/Views/CoreTextLabel.swift index 0bc4fcb6dd..e8eb1cfbec 100644 --- a/Mixin/UserInterface/Views/CoreTextLabel.swift +++ b/Mixin/UserInterface/Views/CoreTextLabel.swift @@ -1,4 +1,5 @@ import UIKit +import MixinServices class CoreTextLabel: UIView { diff --git a/MixinServices/MixinServices/Database/User/DAO/HyperlinkDAO.swift b/MixinServices/MixinServices/Database/User/DAO/HyperlinkDAO.swift new file mode 100644 index 0000000000..8201e81494 --- /dev/null +++ b/MixinServices/MixinServices/Database/User/DAO/HyperlinkDAO.swift @@ -0,0 +1,22 @@ +import GRDB + +public final class HyperlinkDAO: UserDatabaseDAO { + + public static let shared = HyperlinkDAO() + + public func insert(_ hyperlink: Hyperlink, database: GRDB.Database) throws { + try hyperlink.save(database) + } + + public func hyperlinks(conversationId: String) -> [HyperlinkItem] { + let sql = """ + SELECT h.hyperlink, h.site_description, h.site_image, h.site_name, h.site_title, m.created_at + FROM hyperlinks h + INNER JOIN messages m ON h.hyperlink = m.hyperlink + WHERE m.conversation_id = ? AND m.category IN ('SIGNAL_TEXT', 'PLAIN_TEXT', 'ENCRYPTED_TEXT') + ORDER BY m.created_at DESC + """ + return db.select(with: sql, arguments: [conversationId]) + } + +} diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift index fabe62a089..c4c8c21498 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift @@ -604,7 +604,13 @@ public final class MessageDAO: UserDatabaseDAO { quotedMessage = nil } + if message.category.hasSuffix("_TEXT"), let content = message.content, let link = Link.detector.lastMatch(in: content)?.url?.absoluteString { + message.hyperlink = link + } db.write { (db) in + if let link = message.hyperlink { + try HyperlinkDAO.shared.insert(Hyperlink(link: link), database: db) + } if let mention = MessageMention(message: message, quotedMessage: quotedMessage) { try mention.save(db) } diff --git a/MixinServices/MixinServices/Database/User/Model/Hyperlink.swift b/MixinServices/MixinServices/Database/User/Model/Hyperlink.swift new file mode 100644 index 0000000000..19d105974e --- /dev/null +++ b/MixinServices/MixinServices/Database/User/Model/Hyperlink.swift @@ -0,0 +1,38 @@ +import Foundation +import GRDB + +public struct Hyperlink { + + public let link: String + public let siteName: String + public let siteTitle: String + public let siteDescription: String? + public let siteImage: String? + + public init(link: String) { + self.link = link + self.siteName = "" + self.siteTitle = "" + self.siteDescription = nil + self.siteImage = nil + } + +} + +extension Hyperlink: Codable, DatabaseColumnConvertible, MixinFetchableRecord, MixinEncodableRecord { + + public enum CodingKeys: String, CodingKey { + case link = "hyperlink" + case siteName = "site_name" + case siteTitle = "site_title" + case siteDescription = "site_description" + case siteImage = "site_image" + } + +} + +extension Hyperlink: TableRecord, PersistableRecord { + + public static let databaseTableName = "hyperlinks" + +} diff --git a/MixinServices/MixinServices/Database/User/Model/HyperlinkItem.swift b/MixinServices/MixinServices/Database/User/Model/HyperlinkItem.swift new file mode 100644 index 0000000000..08c7821980 --- /dev/null +++ b/MixinServices/MixinServices/Database/User/Model/HyperlinkItem.swift @@ -0,0 +1,26 @@ +import Foundation +import GRDB + +public struct HyperlinkItem { + + public let link: String + public let siteName: String + public let siteTitle: String + public let siteDescription: String? + public let siteImage: String? + public let createdAt: String + +} + +extension HyperlinkItem: Codable, MixinFetchableRecord { + + public enum CodingKeys: String, CodingKey { + case link = "hyperlink" + case siteName = "site_name" + case siteTitle = "site_title" + case siteDescription = "site_description" + case siteImage = "site_image" + case createdAt = "created_at" + } + +} diff --git a/MixinServices/MixinServices/Database/User/Model/Message.swift b/MixinServices/MixinServices/Database/User/Model/Message.swift index bdfec58fea..6732697a0e 100644 --- a/MixinServices/MixinServices/Database/User/Model/Message.swift +++ b/MixinServices/MixinServices/Database/User/Model/Message.swift @@ -37,6 +37,7 @@ public struct Message { public var createdAt: String public var albumId: String? + public var hyperlink: String? } extension Message: Codable, DatabaseColumnConvertible, MixinFetchableRecord, MixinEncodableRecord { @@ -72,6 +73,7 @@ extension Message: Codable, DatabaseColumnConvertible, MixinFetchableRecord, Mix case quoteContent = "quote_content" case createdAt = "created_at" case albumId = "album_id" + case hyperlink } } diff --git a/MixinServices/MixinServices/Database/User/UserDatabase.swift b/MixinServices/MixinServices/Database/User/UserDatabase.swift index b921085103..6d3551f264 100644 --- a/MixinServices/MixinServices/Database/User/UserDatabase.swift +++ b/MixinServices/MixinServices/Database/User/UserDatabase.swift @@ -171,6 +171,7 @@ public final class UserDatabase: Database { .init(key: .quoteContent, constraints: "BLOB"), .init(key: .createdAt, constraints: "TEXT NOT NULL"), .init(key: .albumId, constraints: "TEXT"), + .init(key: .hyperlink, constraints: "TEXT") ]), ColumnMigratableTableDefinition(constraints: nil, columns: [ .init(key: .messageId, constraints: "TEXT PRIMARY KEY"), @@ -517,6 +518,25 @@ public final class UserDatabase: Database { } } + migrator.registerMigration("hyperlinks") { db in + let sql = """ + CREATE TABLE IF NOT EXISTS hyperlinks( + hyperlink TEXT NOT NULL, + site_name TEXT NOT NULL, + site_title TEXT NOT NULL, + site_description TEXT, + site_image TEXT, + PRIMARY KEY (hyperlink) + ) + """ + try db.execute(sql: sql) + + let messageInfos = try TableInfo.fetchAll(db, sql: "PRAGMA table_info(messages)") + if !messageInfos.map(\.name).contains("hyperlink") { + try db.execute(sql: "ALTER TABLE messages ADD COLUMN hyperlink TEXT") + } + } + return migrator } diff --git a/MixinServices/MixinServices/Foundation/URLDetector/Link.swift b/MixinServices/MixinServices/Foundation/URLDetector/Link.swift new file mode 100644 index 0000000000..144af9777f --- /dev/null +++ b/MixinServices/MixinServices/Foundation/URLDetector/Link.swift @@ -0,0 +1,27 @@ +import UIKit + +public class Link { + + public struct Range { + public let range: NSRange + public let url: URL + + public init(range: NSRange, url: URL) { + self.range = range + self.url = url + } + } + + public static let detector = FastURLDetector() + + public let hitFrame: CGRect + public let backgroundPath: UIBezierPath + public let url: URL + + public init(hitFrame: CGRect, backgroundPath: UIBezierPath, url: URL) { + self.hitFrame = hitFrame + self.backgroundPath = backgroundPath + self.url = url + } + +} diff --git a/Mixin/UserInterface/Models/MXMFastURLDetector.h b/MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.h similarity index 68% rename from Mixin/UserInterface/Models/MXMFastURLDetector.h rename to MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.h index 7b6b0a37a0..3a84718096 100644 --- a/Mixin/UserInterface/Models/MXMFastURLDetector.h +++ b/MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.h @@ -3,9 +3,10 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(FastURLDetector) -@interface MXMFastURLDetector : NSObject +@interface MXSFastURLDetector : NSObject - (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options usingBlock:(void (NS_NOESCAPE ^)(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL *stop))block; +- (nullable NSTextCheckingResult *)lastMatchInString:(NSString *)string options:(NSMatchingOptions)options; @end diff --git a/Mixin/UserInterface/Models/MXMFastURLDetector.m b/MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.m similarity index 70% rename from Mixin/UserInterface/Models/MXMFastURLDetector.m rename to MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.m index 003d025b11..744422d42a 100644 --- a/Mixin/UserInterface/Models/MXMFastURLDetector.m +++ b/MixinServices/MixinServices/Foundation/URLDetector/MXSFastURLDetector.m @@ -1,6 +1,8 @@ -#import "MXMFastURLDetector.h" +#import "MXSFastURLDetector.h" -@implementation MXMFastURLDetector +@implementation MXSFastURLDetector + +NS_INLINE BOOL MaybeContainsURL(NSString *string); + (NSDataDetector *)detector { static NSDataDetector *detector; @@ -12,8 +14,24 @@ + (NSDataDetector *)detector { } - (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options usingBlock:(void (NS_NOESCAPE ^)(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL *stop))block { + if (MaybeContainsURL(string)) { + NSRange range = NSMakeRange(0, string.length); + [[MXSFastURLDetector detector] enumerateMatchesInString:string options:options range:range usingBlock:block]; + } +} + +- (nullable NSTextCheckingResult *)lastMatchInString:(NSString *)string options:(NSMatchingOptions)options { + if (MaybeContainsURL(string)) { + NSRange range = NSMakeRange(0, string.length); + return [[MXSFastURLDetector detector] matchesInString:string options:options range:range].lastObject; + } else { + return nil; + } +} + +NS_INLINE BOOL MaybeContainsURL(NSString *string) { if (string.length < 3) { - return; + return NO; } BOOL maybeContainsURL = NO; int dotSequence = 0; @@ -39,10 +57,7 @@ - (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)o } lastChar = c; } - if (maybeContainsURL) { - NSRange range = NSMakeRange(0, string.length); - [[MXMFastURLDetector detector] enumerateMatchesInString:string options:options range:range usingBlock:block]; - } + return maybeContainsURL; } @end