From 44a8b818e4aa6a60c06fbd3f8d940766ca48dc12 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 21 Oct 2022 19:12:44 +0800 Subject: [PATCH] feat: [WIP] restore compose poll view --- .../Scene/Compose/ComposeViewController.swift | 102 +----------- .../Compose/Mention.imageset/Contents.json | 15 ++ .../Compose/Mention.imageset/Mention.pdf | 102 ++++++++++++ .../Scene/Compose/More.imageset/Contents.json | 15 ++ .../Scene/Compose/More.imageset/More.pdf | 83 ++++++++++ .../Compose/People.imageset/Contents.json | 15 ++ .../Scene/Compose/People.imageset/People.pdf | 140 ++++++++++++++++ .../chat.warning.fill.imageset/Contents.json | 15 ++ .../chat.warning.fill.pdf | 90 +++++++++++ .../Compose/emoji.fill.imageset/Contents.json | 15 ++ .../emoji.fill.imageset/emoji.fill.pdf | 93 +++++++++++ .../Compose/people.add.imageset/Contents.json | 15 ++ .../people.add.imageset/People Add.pdf | 150 ++++++++++++++++++ .../Compose/poll.fill.imageset/Contents.json | 15 ++ .../Compose/poll.fill.imageset/poll.fill.pdf | 89 +++++++++++ .../MastodonAsset/Generated/Assets.swift | 7 + .../Model/Poll/PollComposeItem.swift | 104 ++++++++++++ .../Model/Poll/PollComposeSection.swift | 12 ++ .../ComposeContentViewController.swift | 138 +++++++++++++++- .../ComposeContentViewModel.swift | 76 +++++++++ .../ComposeContent/Poll/PollOptionRow.swift | 35 ++++ .../Poll/PollOptionTextField.swift | 101 ++++++++++++ .../ComposeContentToolbarView+ViewModel.swift | 76 ++++++++- .../View/ComposeContentToolbarView.swift | 79 ++++++--- .../View/ComposeContentView.swift | 110 ++++++++++++- .../Vendor/ReorderableForEach.swift | 108 +++++++++++++ .../MastodonUI/Vendor/VectorImageView.swift | 44 +++++ .../DeleteBackwardResponseTextField.swift | 10 ++ 28 files changed, 1722 insertions(+), 132 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Mention.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/More.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/People.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/chat.warning.fill.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/emoji.fill.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/People Add.pdf create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/poll.fill.pdf create mode 100644 MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeItem.swift create mode 100644 MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeSection.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionTextField.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Vendor/ReorderableForEach.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Vendor/VectorImageView.swift diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index c94db4def..54f3903b6 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -106,30 +106,7 @@ final class ComposeViewController: UIViewController, NeedsDependency { // var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! // let composeToolbarBackgroundView = UIView() // -// static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration { -// var configuration = PHPickerConfiguration() -// configuration.filter = .any(of: [.images, .videos]) -// configuration.selectionLimit = selectionLimit -// return configuration -// } -// -// private(set) lazy var photoLibraryPicker: PHPickerViewController = { -// let imagePicker = PHPickerViewController(configuration: ComposeViewController.createPhotoLibraryPickerConfiguration()) -// imagePicker.delegate = self -// return imagePicker -// }() -// private(set) lazy var imagePickerController: UIImagePickerController = { -// let imagePickerController = UIImagePickerController() -// imagePickerController.sourceType = .camera -// imagePickerController.delegate = self -// return imagePickerController -// }() -// -// private(set) lazy var documentPickerController: UIDocumentPickerViewController = { -// let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image, .movie]) -// documentPickerController.delegate = self -// return documentPickerController -// }() + // // private(set) lazy var autoCompleteViewController: AutoCompleteViewController = { // let viewController = AutoCompleteViewController() @@ -814,29 +791,7 @@ extension ComposeViewController { // //// MARK: - ComposeToolbarViewDelegate //extension ComposeViewController: ComposeToolbarViewDelegate { -// -// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, mediaButtonDidPressed sender: Any, mediaSelectionType type: ComposeToolbarView.MediaSelectionType) { -// switch type { -// case .photoLibrary: -// present(photoLibraryPicker, animated: true, completion: nil) -// case .camera: -// present(imagePickerController, animated: true, completion: nil) -// case .browse: -// #if SNAPSHOT -// guard let image = UIImage(named: "Athens") else { return } -// -// let attachmentService = MastodonAttachmentService( -// context: context, -// image: image, -// initialAuthenticationBox: viewModel.authenticationBox -// ) -// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] -// #else -// present(documentPickerController, animated: true, completion: nil) -// #endif -// } -// } -// + // func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: Any) { // // toggle poll composing state // viewModel.isPollComposing.toggle() @@ -943,59 +898,6 @@ extension ComposeViewController: UIAdaptivePresentationControllerDelegate { } -//// MARK: - PHPickerViewControllerDelegate -//extension ComposeViewController: PHPickerViewControllerDelegate { -// func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { -// picker.dismiss(animated: true, completion: nil) -// -// let attachmentServices: [MastodonAttachmentService] = results.map { result in -// let service = MastodonAttachmentService( -// context: context, -// pickerResult: result, -// initialAuthenticationBox: viewModel.authenticationBox -// ) -// return service -// } -// viewModel.attachmentServices = viewModel.attachmentServices + attachmentServices -// } -//} -// -//// MARK: - UIImagePickerControllerDelegate -//extension ComposeViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate { -// -// func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { -// picker.dismiss(animated: true, completion: nil) -// -// guard let image = info[.originalImage] as? UIImage else { return } -// -// let attachmentService = MastodonAttachmentService( -// context: context, -// image: image, -// initialAuthenticationBox: viewModel.authenticationBox -// ) -// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] -// } -// -// func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { -// os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) -// picker.dismiss(animated: true, completion: nil) -// } -//} -// -//// MARK: - UIDocumentPickerDelegate -//extension ComposeViewController: UIDocumentPickerDelegate { -// func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { -// guard let url = urls.first else { return } -// -// let attachmentService = MastodonAttachmentService( -// context: context, -// documentURL: url, -// initialAuthenticationBox: viewModel.authenticationBox -// ) -// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] -// } -//} -// //// MARK: - ComposeStatusAttachmentTableViewCellDelegate //extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelegate { // diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Contents.json new file mode 100644 index 000000000..776af5644 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Mention.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Mention.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Mention.pdf new file mode 100644 index 000000000..e797d12c6 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/Mention.imageset/Mention.pdf @@ -0,0 +1,102 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 2.000000 cm +0.000000 0.000000 0.000000 scn +20.000000 10.000000 m +20.000000 8.250000 l +20.000000 6.178932 18.321068 4.500000 16.250000 4.500000 c +14.745829 4.500000 13.448502 5.385603 12.850986 6.663840 c +12.032894 5.644792 10.840015 5.000000 9.500000 5.000000 c +6.992370 5.000000 5.000000 7.258018 5.000000 10.000000 c +5.000000 12.741982 6.992370 15.000000 9.500000 15.000000 c +10.659005 15.000000 11.707939 14.517641 12.500963 13.728080 c +12.500000 14.250000 l +12.500000 14.664213 12.835787 15.000000 13.250000 15.000000 c +13.629696 15.000000 13.943491 14.717846 13.993154 14.351770 c +14.000000 14.250000 l +14.000000 8.250000 l +14.000000 7.007360 15.007360 6.000000 16.250000 6.000000 c +17.440865 6.000000 18.415646 6.925161 18.494810 8.095951 c +18.500000 8.250000 l +18.500000 10.000000 l +18.500000 14.694420 14.694420 18.500000 10.000000 18.500000 c +5.305580 18.500000 1.500000 14.694420 1.500000 10.000000 c +1.500000 5.305580 5.305580 1.500000 10.000000 1.500000 c +11.032966 1.500000 12.039467 1.683977 12.985156 2.038752 c +13.372977 2.184242 13.805312 1.987795 13.950803 1.599974 c +14.096293 1.212152 13.899846 0.779818 13.512025 0.634327 c +12.398500 0.216589 11.213587 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477152 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.429239 20.000000 19.847933 15.673328 19.996159 10.279904 c +20.000000 10.000000 l +20.000000 8.250000 l +20.000000 10.000000 l +h +9.500000 13.500000 m +7.865495 13.500000 6.500000 11.952439 6.500000 10.000000 c +6.500000 8.047561 7.865495 6.500000 9.500000 6.500000 c +11.134505 6.500000 12.500000 8.047561 12.500000 10.000000 c +12.500000 11.952439 11.134505 13.500000 9.500000 13.500000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1791 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001881 00000 n +0000001904 00000 n +0000002077 00000 n +0000002151 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2210 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/Contents.json new file mode 100644 index 000000000..990bf0cbd --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "More.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/More.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/More.pdf new file mode 100644 index 000000000..8ae9c73f2 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/More.imageset/More.pdf @@ -0,0 +1,83 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.250000 10.250000 cm +0.000000 0.000000 0.000000 scn +3.500000 1.750000 m +3.500000 0.783502 2.716498 0.000000 1.750000 0.000000 c +0.783502 0.000000 0.000000 0.783502 0.000000 1.750000 c +0.000000 2.716498 0.783502 3.500000 1.750000 3.500000 c +2.716498 3.500000 3.500000 2.716498 3.500000 1.750000 c +h +9.500000 1.750000 m +9.500000 0.783502 8.716498 0.000000 7.750000 0.000000 c +6.783502 0.000000 6.000000 0.783502 6.000000 1.750000 c +6.000000 2.716498 6.783502 3.500000 7.750000 3.500000 c +8.716498 3.500000 9.500000 2.716498 9.500000 1.750000 c +h +13.750000 0.000000 m +14.716498 0.000000 15.500000 0.783502 15.500000 1.750000 c +15.500000 2.716498 14.716498 3.500000 13.750000 3.500000 c +12.783502 3.500000 12.000000 2.716498 12.000000 1.750000 c +12.000000 0.783502 12.783502 0.000000 13.750000 0.000000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 877 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000967 00000 n +0000000989 00000 n +0000001162 00000 n +0000001236 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1295 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/Contents.json new file mode 100644 index 000000000..8f5a17a88 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "People.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/People.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/People.pdf new file mode 100644 index 000000000..c5345615f --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/People.imageset/People.pdf @@ -0,0 +1,140 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 2.000000 cm +0.000000 0.000000 0.000000 scn +2.000000 8.001000 m +11.000000 8.000000 l +12.053818 8.000000 12.918116 7.184515 12.994511 6.149316 c +13.000000 6.000000 l +13.000000 4.500000 l +12.999000 1.000000 9.284000 0.000000 6.500000 0.000000 c +3.777867 0.000000 0.164695 0.956049 0.005454 4.270353 c +0.000000 4.500000 l +0.000000 6.001000 l +0.000000 7.054819 0.816397 7.919116 1.850808 7.995511 c +2.000000 8.001000 l +h +13.220000 8.000000 m +18.000000 8.000000 l +19.053818 8.000000 19.918116 7.183603 19.994511 6.149192 c +20.000000 6.000000 l +20.000000 5.000000 l +19.999001 1.938000 17.142000 1.000000 15.000000 1.000000 c +14.320000 1.000000 13.568999 1.096001 12.860000 1.322001 c +13.196000 1.708000 13.467000 2.149000 13.662000 2.649000 c +14.205000 2.524000 14.715000 2.500000 15.000000 2.500000 c +15.266544 2.505959 l +16.251810 2.549091 18.352863 2.869398 18.492661 4.795017 c +18.500000 5.000000 l +18.500000 6.000000 l +18.500000 6.245334 18.322222 6.449580 18.089575 6.491940 c +18.000000 6.500000 l +13.949000 6.500000 l +13.865001 7.001375 13.655437 7.456812 13.354479 7.840185 c +13.220000 8.000000 l +18.000000 8.000000 l +13.220000 8.000000 l +h +2.000000 6.501000 m +1.899344 6.491000 l +1.774960 6.465720 1.690000 6.398199 1.646000 6.355000 c +1.602800 6.311000 1.535280 6.226681 1.510000 6.102040 c +1.500000 6.001000 l +1.500000 4.500000 l +1.500000 3.491000 1.950000 2.778000 2.917000 2.257999 c +3.743154 1.813076 4.919508 1.543680 6.182578 1.504868 c +6.500000 1.500000 l +6.817405 1.504868 l +8.080349 1.543680 9.255923 1.813076 10.083000 2.257999 c +10.988626 2.745499 11.441613 3.402630 11.494699 4.315081 c +11.500000 4.500999 l +11.500000 6.000000 l +11.500000 6.245334 11.322222 6.449580 11.089575 6.491940 c +11.000000 6.500000 l +2.000000 6.501000 l +h +6.500000 19.000000 m +8.985000 19.000000 11.000000 16.985001 11.000000 14.500000 c +11.000000 12.015000 8.985000 10.000000 6.500000 10.000000 c +4.015000 10.000000 2.000000 12.015000 2.000000 14.500000 c +2.000000 16.985001 4.015000 19.000000 6.500000 19.000000 c +h +15.500000 17.000000 m +17.433001 17.000000 19.000000 15.433001 19.000000 13.500000 c +19.000000 11.566999 17.433001 10.000000 15.500000 10.000000 c +13.567000 10.000000 12.000000 11.566999 12.000000 13.500000 c +12.000000 15.433001 13.567000 17.000000 15.500000 17.000000 c +h +6.500000 17.500000 m +4.846000 17.500000 3.500000 16.153999 3.500000 14.500000 c +3.500000 12.846000 4.846000 11.500000 6.500000 11.500000 c +8.154000 11.500000 9.500000 12.846000 9.500000 14.500000 c +9.500000 16.153999 8.154000 17.500000 6.500000 17.500000 c +h +15.500000 15.500000 m +14.397000 15.500000 13.500000 14.603001 13.500000 13.500000 c +13.500000 12.396999 14.397000 11.500000 15.500000 11.500000 c +16.603001 11.500000 17.500000 12.396999 17.500000 13.500000 c +17.500000 14.603001 16.603001 15.500000 15.500000 15.500000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 2893 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002983 00000 n +0000003006 00000 n +0000003179 00000 n +0000003253 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3312 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/Contents.json new file mode 100644 index 000000000..e4babf4c7 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "chat.warning.fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/chat.warning.fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/chat.warning.fill.pdf new file mode 100644 index 000000000..f6adcc07c --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/chat.warning.fill.imageset/chat.warning.fill.pdf @@ -0,0 +1,90 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 1.907806 cm +0.000000 0.000000 0.000000 scn +20.000000 10.093658 m +20.000000 15.616507 15.522848 20.093658 10.000000 20.093658 c +4.477152 20.093658 0.000000 15.616507 0.000000 10.093658 c +0.000000 8.450871 0.397199 6.864305 1.144898 5.443409 c +0.028547 1.155022 l +-0.008018 1.014606 -0.008014 0.867102 0.028576 0.726627 c +0.146904 0.272343 0.611098 -0.000004 1.065382 0.118324 c +5.355775 1.235390 l +6.775161 0.489723 8.359558 0.093658 10.000000 0.093658 c +15.522848 0.093658 20.000000 4.570810 20.000000 10.093658 c +h +10.000000 15.592194 m +10.414213 15.592194 10.750000 15.256407 10.750000 14.842194 c +10.750000 8.592194 l +10.750000 8.177980 10.414213 7.842194 10.000000 7.842194 c +9.585787 7.842194 9.250000 8.177980 9.250000 8.592194 c +9.250000 14.842194 l +9.250000 15.256407 9.585787 15.592194 10.000000 15.592194 c +h +11.000000 5.594330 m +11.000000 5.042046 10.552285 4.594330 10.000000 4.594330 c +9.447715 4.594330 9.000000 5.042046 9.000000 5.594330 c +9.000000 6.146615 9.447715 6.594330 10.000000 6.594330 c +10.552285 6.594330 11.000000 6.146615 11.000000 5.594330 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1155 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001245 00000 n +0000001268 00000 n +0000001441 00000 n +0000001515 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1574 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/Contents.json new file mode 100644 index 000000000..27b869ea8 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "emoji.fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/emoji.fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/emoji.fill.pdf new file mode 100644 index 000000000..b8c544137 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/emoji.fill.imageset/emoji.fill.pdf @@ -0,0 +1,93 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.998444 1.997925 cm +0.000000 0.000000 0.000000 scn +10.001551 20.003113 m +15.525254 20.003113 20.003101 15.525267 20.003101 10.001562 c +20.003101 4.477859 15.525254 0.000013 10.001551 0.000013 c +4.477847 0.000013 0.000000 4.477859 0.000000 10.001562 c +0.000000 15.525267 4.477847 20.003113 10.001551 20.003113 c +h +6.463291 7.218245 m +6.206941 7.543602 5.735374 7.599545 5.410017 7.343195 c +5.084659 7.086845 5.028717 6.615278 5.285066 6.289920 c +6.415668 4.854965 8.138899 3.999996 10.001535 3.999996 c +11.861678 3.999996 13.582860 4.852667 14.713623 6.284365 c +14.970354 6.609422 14.914966 7.081055 14.589909 7.337786 c +14.264852 7.594518 13.793221 7.539129 13.536489 7.214072 c +12.687231 6.138799 11.397759 5.499995 10.001535 5.499995 c +8.603443 5.499995 7.312427 6.140525 6.463291 7.218245 c +h +7.001998 13.250921 m +6.312035 13.250921 5.752709 12.691595 5.752709 12.001632 c +5.752709 11.311668 6.312035 10.752343 7.001998 10.752343 c +7.691962 10.752343 8.251287 11.311668 8.251287 12.001632 c +8.251287 12.691595 7.691962 13.250921 7.001998 13.250921 c +h +13.001999 13.250921 m +12.312036 13.250921 11.752709 12.691595 11.752709 12.001632 c +11.752709 11.311668 12.312036 10.752343 13.001999 10.752343 c +13.691962 10.752343 14.251287 11.311668 14.251287 12.001632 c +14.251287 12.691595 13.691962 13.250921 13.001999 13.250921 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1401 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001491 00000 n +0000001514 00000 n +0000001687 00000 n +0000001761 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1820 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/Contents.json new file mode 100644 index 000000000..b7086c903 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "People Add.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/People Add.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/People Add.pdf new file mode 100644 index 000000000..a25e5685a --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/people.add.imageset/People Add.pdf @@ -0,0 +1,150 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 1.000000 cm +0.000000 0.000000 0.000000 scn +15.500000 11.000000 m +18.537567 11.000000 21.000000 8.537566 21.000000 5.500000 c +21.000000 2.462433 18.537567 0.000000 15.500000 0.000000 c +12.462434 0.000000 10.000000 2.462433 10.000000 5.500000 c +10.000000 8.537566 12.462434 11.000000 15.500000 11.000000 c +h +2.000000 10.001000 m +10.809396 9.999816 l +10.383152 9.555614 10.019394 9.050992 9.732251 8.500078 c +2.000000 8.501000 l +1.899344 8.491000 l +1.774960 8.465720 1.690000 8.398199 1.646000 8.355000 c +1.602800 8.311000 1.535280 8.226681 1.510000 8.102040 c +1.500000 8.001000 l +1.500000 6.500000 l +1.500000 5.491000 1.950000 4.778000 2.917000 4.257999 c +3.743154 3.813076 4.919508 3.543680 6.182578 3.504868 c +6.500000 3.500000 l +6.817405 3.504868 l +7.681085 3.531410 8.503904 3.665789 9.202351 3.890396 c +9.326429 3.397497 9.508213 2.927378 9.739013 2.486986 c +8.688712 2.137030 7.530568 2.000000 6.500000 2.000000 c +3.777867 2.000000 0.164695 2.956049 0.005454 6.270353 c +0.000000 6.500000 l +0.000000 8.001000 l +0.000000 9.105000 0.896000 10.001000 2.000000 10.001000 c +h +15.500000 8.998419 m +15.410124 8.990363 l +15.206031 8.953320 15.045100 8.792387 15.008057 8.588294 c +15.000000 8.498419 l +15.000000 6.000420 l +12.500000 6.000000 l +12.410125 5.991943 l +12.206032 5.954900 12.045099 5.793969 12.008056 5.589876 c +12.000000 5.500000 l +12.008056 5.410124 l +12.045099 5.206031 12.206032 5.045100 12.410125 5.008057 c +12.500000 5.000000 l +15.000000 5.000420 l +15.000000 2.500000 l +15.008057 2.410124 l +15.045100 2.206030 15.206031 2.045101 15.410124 2.008057 c +15.500000 2.000000 l +15.589876 2.008057 l +15.793969 2.045101 15.954900 2.206030 15.991943 2.410124 c +16.000000 2.500000 l +16.000000 5.000420 l +18.500000 5.000000 l +18.589876 5.008057 l +18.793970 5.045100 18.954899 5.206031 18.991943 5.410124 c +19.000000 5.500000 l +18.991943 5.589876 l +18.954899 5.793969 18.793970 5.954900 18.589876 5.991943 c +18.500000 6.000000 l +16.000000 6.000420 l +16.000000 8.498419 l +15.991943 8.588294 l +15.954900 8.792387 15.793969 8.953320 15.589876 8.990363 c +15.500000 8.998419 l +h +6.500000 21.000000 m +8.985000 21.000000 11.000000 18.985001 11.000000 16.500000 c +11.000000 14.015000 8.985000 12.000000 6.500000 12.000000 c +4.015000 12.000000 2.000000 14.015000 2.000000 16.500000 c +2.000000 18.985001 4.015000 21.000000 6.500000 21.000000 c +h +15.500000 19.000000 m +17.433001 19.000000 19.000000 17.433001 19.000000 15.500000 c +19.000000 13.566999 17.433001 12.000000 15.500000 12.000000 c +13.567000 12.000000 12.000000 13.566999 12.000000 15.500000 c +12.000000 17.433001 13.567000 19.000000 15.500000 19.000000 c +h +6.500000 19.500000 m +4.846000 19.500000 3.500000 18.153999 3.500000 16.500000 c +3.500000 14.846000 4.846000 13.500000 6.500000 13.500000 c +8.154000 13.500000 9.500000 14.846000 9.500000 16.500000 c +9.500000 18.153999 8.154000 19.500000 6.500000 19.500000 c +h +15.500000 17.500000 m +14.397000 17.500000 13.500000 16.603001 13.500000 15.500000 c +13.500000 14.396999 14.397000 13.500000 15.500000 13.500000 c +16.603001 13.500000 17.500000 14.396999 17.500000 15.500000 c +17.500000 16.603001 16.603001 17.500000 15.500000 17.500000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 3220 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003310 00000 n +0000003333 00000 n +0000003506 00000 n +0000003580 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3639 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/Contents.json new file mode 100644 index 000000000..8b3f008b8 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "poll.fill.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/poll.fill.pdf b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/poll.fill.pdf new file mode 100644 index 000000000..bc645aaab --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Compose/poll.fill.imageset/poll.fill.pdf @@ -0,0 +1,89 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 1.998138 cm +0.000000 0.000000 0.000000 scn +9.751870 20.002289 m +11.271687 20.002289 12.503741 18.770235 12.503741 17.250418 c +12.503741 2.751883 l +12.503741 1.232067 11.271687 0.000013 9.751870 0.000013 c +8.232054 0.000013 7.000000 1.232067 7.000000 2.751883 c +7.000000 17.250418 l +7.000000 18.770235 8.232054 20.002289 9.751870 20.002289 c +h +16.751871 15.002289 m +18.271687 15.002289 19.503740 13.770235 19.503740 12.250419 c +19.503740 2.751883 l +19.503740 1.232067 18.271687 0.000013 16.751871 0.000013 c +15.232055 0.000013 14.000000 1.232067 14.000000 2.751883 c +14.000000 12.250419 l +14.000000 13.770235 15.232055 15.002289 16.751871 15.002289 c +h +2.751871 10.002289 m +4.271687 10.002289 5.503741 8.770235 5.503741 7.250419 c +5.503741 2.751883 l +5.503741 1.232067 4.271687 0.000013 2.751871 0.000013 c +1.232054 0.000013 0.000000 1.232067 0.000000 2.751883 c +0.000000 7.250419 l +0.000000 8.770235 1.232054 10.002289 2.751871 10.002289 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1024 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001114 00000 n +0000001137 00000 n +0000001310 00000 n +0000001384 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1443 +%%EOF \ No newline at end of file diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index efdc2164e..7fda6061f 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -131,10 +131,17 @@ public enum Asset { public enum Scene { public enum Compose { public static let buttonTint = ColorAsset(name: "Scene/Compose/button.tint") + public static let chatWarningFill = ImageAsset(name: "Scene/Compose/chat.warning.fill") public static let chatWarning = ImageAsset(name: "Scene/Compose/chat.warning") public static let earth = ImageAsset(name: "Scene/Compose/earth") + public static let emojiFill = ImageAsset(name: "Scene/Compose/emoji.fill") public static let emoji = ImageAsset(name: "Scene/Compose/emoji") public static let media = ImageAsset(name: "Scene/Compose/media") + public static let mention = ImageAsset(name: "Scene/Compose/mention") + public static let more = ImageAsset(name: "Scene/Compose/more") + public static let peopleAdd = ImageAsset(name: "Scene/Compose/people.add") + public static let people = ImageAsset(name: "Scene/Compose/people") + public static let pollFill = ImageAsset(name: "Scene/Compose/poll.fill") public static let poll = ImageAsset(name: "Scene/Compose/poll") } public enum Discovery { diff --git a/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeItem.swift b/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeItem.swift new file mode 100644 index 000000000..5673f600d --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeItem.swift @@ -0,0 +1,104 @@ +// +// PollComposeItem.swift +// +// +// Created by MainasuK on 2021-11-29. +// + +import UIKit +import Combine +import MastodonLocalization + +public enum PollComposeItem: Hashable { + case option(Option) + case expireConfiguration(ExpireConfiguration) + case multipleConfiguration(MultipleConfiguration) +} + +extension PollComposeItem { + public final class Option: NSObject, Identifiable, ObservableObject { + public let id = UUID() + + public weak var textField: UITextField? + + @Published public var text = "" + @Published public var shouldBecomeFirstResponder = false + + public override init() { + super.init() + } + } +} + +extension PollComposeItem { + public final class ExpireConfiguration: Identifiable, Hashable, ObservableObject { + public let id = UUID() + + @Published public var option: Option = .oneDay // Mastodon + + public init() { + // end init + } + + public static func == (lhs: ExpireConfiguration, rhs: ExpireConfiguration) -> Bool { + return lhs.id == rhs.id + && lhs.option == rhs.option + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + public enum Option: String, Hashable, CaseIterable { + case thirtyMinutes + case oneHour + case sixHours + case oneDay + case threeDays + case sevenDays + + public var title: String { + switch self { + case .thirtyMinutes: return L10n.Scene.Compose.Poll.thirtyMinutes + case .oneHour: return L10n.Scene.Compose.Poll.oneHour + case .sixHours: return L10n.Scene.Compose.Poll.sixHours + case .oneDay: return L10n.Scene.Compose.Poll.oneDay + case .threeDays: return L10n.Scene.Compose.Poll.threeDays + case .sevenDays: return L10n.Scene.Compose.Poll.sevenDays + } + } + + public var seconds: Int { + switch self { + case .thirtyMinutes: return 60 * 30 + case .oneHour: return 60 * 60 * 1 + case .sixHours: return 60 * 60 * 6 + case .oneDay: return 60 * 60 * 24 + case .threeDays: return 60 * 60 * 24 * 3 + case .sevenDays: return 60 * 60 * 24 * 7 + } + } + } + } +} + +extension PollComposeItem { + public final class MultipleConfiguration: Hashable, ObservableObject { + private let id = UUID() + + @Published public var isMultiple = false + + public init() { + // end init + } + + public static func == (lhs: MultipleConfiguration, rhs: MultipleConfiguration) -> Bool { + return lhs.id == rhs.id + && lhs.isMultiple == rhs.isMultiple + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeSection.swift b/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeSection.swift new file mode 100644 index 000000000..3279bc064 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/Model/Poll/PollComposeSection.swift @@ -0,0 +1,12 @@ +// +// PollComposeSection.swift +// +// +// Created by MainasuK on 2021-11-29. +// + +import Foundation + +public enum PollComposeSection: Hashable { + case main +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift index 51dfb3ace..09553d0f9 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewController.swift @@ -9,6 +9,7 @@ import os.log import UIKit import SwiftUI import Combine +import PhotosUI import MastodonCore public final class ComposeContentViewController: UIViewController { @@ -17,7 +18,7 @@ public final class ComposeContentViewController: UIViewController { var disposeBag = Set() public var viewModel: ComposeContentViewModel! - let composeContentToolbarViewModel = ComposeContentToolbarView.ViewModel() + private(set) lazy var composeContentToolbarViewModel = ComposeContentToolbarView.ViewModel(delegate: self) let tableView: ComposeTableView = { let tableView = ComposeTableView() @@ -31,6 +32,34 @@ public final class ComposeContentViewController: UIViewController { lazy var composeContentToolbarView = ComposeContentToolbarView(viewModel: composeContentToolbarViewModel) var composeContentToolbarViewBottomLayoutConstraint: NSLayoutConstraint! let composeContentToolbarBackgroundView = UIView() + + // media picker + + static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration { + var configuration = PHPickerConfiguration() + configuration.filter = .any(of: [.images, .videos]) + configuration.selectionLimit = selectionLimit + return configuration + } + + private(set) lazy var photoLibraryPicker: PHPickerViewController = { + let imagePicker = PHPickerViewController(configuration: ComposeContentViewController.createPhotoLibraryPickerConfiguration()) + imagePicker.delegate = self + return imagePicker + }() + + private(set) lazy var imagePickerController: UIImagePickerController = { + let imagePickerController = UIImagePickerController() + imagePickerController.sourceType = .camera + imagePickerController.delegate = self + return imagePickerController + }() + + private(set) lazy var documentPickerController: UIDocumentPickerViewController = { + let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image, .movie]) + documentPickerController.delegate = self + return documentPickerController + }() deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) @@ -188,6 +217,9 @@ extension ComposeContentViewController { } } .store(in: &disposeBag) + + // bind toolbar + bindToolbarViewModel() } public override func viewDidLayoutSubviews() { @@ -223,6 +255,12 @@ extension ComposeContentViewController { tableView.backgroundColor = backgroundColor composeContentToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor } + + private func bindToolbarViewModel() { + viewModel.$isPollActive.assign(to: &composeContentToolbarViewModel.$isPollActive) + viewModel.$isEmojiActive.assign(to: &composeContentToolbarViewModel.$isEmojiActive) + viewModel.$isContentWarningActive.assign(to: &composeContentToolbarViewModel.$isContentWarningActive) + } } // MARK: - UIScrollViewDelegate @@ -279,3 +317,101 @@ extension ComposeContentViewController { // MARK: - UITableViewDelegate extension ComposeContentViewController: UITableViewDelegate { } +// MARK: - PHPickerViewControllerDelegate +extension ComposeContentViewController: PHPickerViewControllerDelegate { + public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true, completion: nil) + + // TODO: +// let attachmentServices: [MastodonAttachmentService] = results.map { result in +// let service = MastodonAttachmentService( +// context: context, +// pickerResult: result, +// initialAuthenticationBox: viewModel.authenticationBox +// ) +// return service +// } +// viewModel.attachmentServices = viewModel.attachmentServices + attachmentServices + } +} + +// MARK: - UIImagePickerControllerDelegate +extension ComposeContentViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate { + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + picker.dismiss(animated: true, completion: nil) + + guard let image = info[.originalImage] as? UIImage else { return } + +// let attachmentService = MastodonAttachmentService( +// context: context, +// image: image, +// initialAuthenticationBox: viewModel.authenticationBox +// ) +// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] + } + + public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + picker.dismiss(animated: true, completion: nil) + } +} + +// MARK: - UIDocumentPickerDelegate +extension ComposeContentViewController: UIDocumentPickerDelegate { + public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let url = urls.first else { return } + +// let attachmentService = MastodonAttachmentService( +// context: context, +// documentURL: url, +// initialAuthenticationBox: viewModel.authenticationBox +// ) +// viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] + } +} + +// MARK: - ComposeContentToolbarViewDelegate +extension ComposeContentViewController: ComposeContentToolbarViewDelegate { + func composeContentToolbarView( + _ viewModel: ComposeContentToolbarView.ViewModel, + toolbarItemDidPressed action: ComposeContentToolbarView.ViewModel.Action + ) { + switch action { + case .attachment: + assertionFailure() + case .poll: + self.viewModel.isPollActive.toggle() + case .emoji: + self.viewModel.isEmojiActive.toggle() + case .contentWarning: + self.viewModel.isContentWarningActive.toggle() + case .visibility: + assertionFailure() + } + } + + func composeContentToolbarView( + _ viewModel: ComposeContentToolbarView.ViewModel, + attachmentMenuDidPressed action: ComposeContentToolbarView.ViewModel.AttachmentAction + ) { + switch action { + case .photoLibrary: + present(photoLibraryPicker, animated: true, completion: nil) + case .camera: + present(imagePickerController, animated: true, completion: nil) + case .browse: + #if SNAPSHOT + guard let image = UIImage(named: "Athens") else { return } + + let attachmentService = MastodonAttachmentService( + context: context, + image: image, + initialAuthenticationBox: viewModel.authenticationBox + ) + viewModel.attachmentServices = viewModel.attachmentServices + [attachmentService] + #else + present(documentPickerController, animated: true, completion: nil) + #endif + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index 24736cc5e..ca78649e1 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -30,6 +30,20 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { @Published var viewLayoutFrame = ViewLayoutFrame() @Published var authContext: AuthContext + // poll + @Published var isPollActive = false + @Published public var pollOptions: [PollComposeItem.Option] = { + // initial with 2 options + var options: [PollComposeItem.Option] = [] + options.append(PollComposeItem.Option()) + options.append(PollComposeItem.Option()) + return options + }() + @Published public var maxPollOptionLimit = 4 + + @Published var isEmojiActive = false + @Published var isContentWarningActive = false + // output // content @@ -94,3 +108,65 @@ extension ComposeContentViewModel { } } +extension ComposeContentViewModel { + func createNewPollOptionIfCould() { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + guard pollOptions.count < maxPollOptionLimit else { return } + let option = PollComposeItem.Option() + option.shouldBecomeFirstResponder = true + pollOptions.append(option) + } +} + +// MARK: - DeleteBackwardResponseTextFieldRelayDelegate +extension ComposeContentViewModel: DeleteBackwardResponseTextFieldRelayDelegate { + + func deleteBackwardResponseTextFieldDidReturn(_ textField: DeleteBackwardResponseTextField) { + let index = textField.tag + if index + 1 == pollOptions.count { + createNewPollOptionIfCould() + } else if index < pollOptions.count { + pollOptions[index + 1].textField?.becomeFirstResponder() + } + } + + func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?) { + guard (textBeforeDelete ?? "").isEmpty else { + // do nothing when not empty + return + } + + let index = textField.tag + guard index > 0 else { + // do nothing at first row + return + } + + func optionBeforeRemoved() -> PollComposeItem.Option? { + guard index > 0 else { return nil } + let indexBeforeRemoved = pollOptions.index(before: index) + let itemBeforeRemoved = pollOptions[indexBeforeRemoved] + return itemBeforeRemoved + + } + + func optionAfterRemoved() -> PollComposeItem.Option? { + guard index < pollOptions.count - 1 else { return nil } + let indexAfterRemoved = pollOptions.index(after: index) + let itemAfterRemoved = pollOptions[indexAfterRemoved] + return itemAfterRemoved + } + + // move first responder + let _option = optionBeforeRemoved() ?? optionAfterRemoved() + _option?.textField?.becomeFirstResponder() + + guard pollOptions.count > 2 else { + // remove item when more then 2 options + return + } + pollOptions.remove(at: index) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift new file mode 100644 index 000000000..89482ddef --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift @@ -0,0 +1,35 @@ +// +// PollOptionRow.swift +// +// +// Created by MainasuK on 2022-5-31. +// + +import SwiftUI +import MastodonCore + +public struct PollOptionRow: View { + + @ObservedObject var viewModel: PollComposeItem.Option + + let index: Int? + let deleteBackwardResponseTextFieldRelayDelegate: DeleteBackwardResponseTextFieldRelayDelegate? + let configurationHandler: (DeleteBackwardResponseTextField) -> Void + + public var body: some View { + PollOptionTextField( + text: $viewModel.text, + index: index ?? -1, + delegate: deleteBackwardResponseTextFieldRelayDelegate + ) { textField in + viewModel.textField = textField + configurationHandler(textField) + } + .onReceive(viewModel.$shouldBecomeFirstResponder) { shouldBecomeFirstResponder in + guard shouldBecomeFirstResponder else { return } + viewModel.shouldBecomeFirstResponder = false + viewModel.textField?.becomeFirstResponder() + } + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionTextField.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionTextField.swift new file mode 100644 index 000000000..0b2065008 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionTextField.swift @@ -0,0 +1,101 @@ +// +// PollOptionTextField.swift +// +// +// Created by MainasuK on 2022-5-27. +// + +import os.log +import UIKit +import SwiftUI +import Combine +import MastodonCore +import MastodonLocalization + +public struct PollOptionTextField: UIViewRepresentable { + + let textField = DeleteBackwardResponseTextField() + + @Binding var text: String + + let index: Int + let delegate: DeleteBackwardResponseTextFieldRelayDelegate? + let configurationHandler: (DeleteBackwardResponseTextField) -> Void + + public func makeUIView(context: Context) -> DeleteBackwardResponseTextField { + textField.setContentHuggingPriority(.defaultHigh, for: .vertical) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.textInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + textField.borderStyle = .roundedRect + textField.returnKeyType = .next + return textField + } + + public func updateUIView(_ textField: DeleteBackwardResponseTextField, context: Context) { + textField.tag = index + textField.text = text + textField.placeholder = { + if index >= 0 { + return L10n.Scene.Compose.Poll.optionNumber(index) + } else { + assertionFailure() + return "" + } + }() + textField.delegate = context.coordinator + textField.deleteBackwardDelegate = context.coordinator + context.coordinator.delegate = delegate + configurationHandler(textField) + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self) + } + +} + +protocol DeleteBackwardResponseTextFieldRelayDelegate: AnyObject { + func deleteBackwardResponseTextFieldDidReturn(_ textField: DeleteBackwardResponseTextField) + func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?) +} + +extension PollOptionTextField { + public class Coordinator: NSObject { + let logger = Logger(subsystem: "DeleteBackwardResponseTextFieldRepresentable.Coordinator", category: "Coordinator") + + var disposeBag = Set() + weak var delegate: DeleteBackwardResponseTextFieldRelayDelegate? + + let view: PollOptionTextField + + init(_ view: PollOptionTextField) { + self.view = view + super.init() + + NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: view.textField) + .sink { [weak self] _ in + guard let self = self else { return } + self.view.text = view.textField.text ?? "" + } + .store(in: &disposeBag) + } + } +} + +// MARK: - UITextFieldDelegate +extension PollOptionTextField.Coordinator: UITextFieldDelegate { + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + guard let textField = textField as? DeleteBackwardResponseTextField else { + return true + } + delegate?.deleteBackwardResponseTextFieldDidReturn(textField) + return true + } +} + +extension PollOptionTextField.Coordinator: DeleteBackwardResponseTextFieldDelegate { + public func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?) { + delegate?.deleteBackwardResponseTextField(textField, textBeforeDelete: textBeforeDelete) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView+ViewModel.swift index ed412fbea..e04648c09 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView+ViewModel.swift @@ -8,11 +8,14 @@ import SwiftUI import MastodonCore import MastodonAsset +import MastodonLocalization import MastodonSDK extension ComposeContentToolbarView { class ViewModel: ObservableObject { + weak var delegate: ComposeContentToolbarViewDelegate? + // input @Published var backgroundColor = ThemeService.shared.currentTheme.value.composeToolbarBackgroundColor @Published var visibility: Mastodon.Entity.Status.Visibility = .public @@ -20,9 +23,16 @@ extension ComposeContentToolbarView { return [.public, .private, .direct] } + @Published var isPollActive = false + @Published var isEmojiActive = false + @Published var isContentWarningActive = false + // output - init() { + init(delegate: ComposeContentToolbarViewDelegate) { + self.delegate = delegate + // end init + ThemeService.shared.currentTheme .map { $0.composeToolbarBackgroundColor } .assign(to: &$backgroundColor) @@ -39,19 +49,71 @@ extension ComposeContentToolbarView.ViewModel { case contentWarning case visibility - var image: UIImage { + var activeImage: UIImage { switch self { case .attachment: - return Asset.Scene.Compose.media.image + return Asset.Scene.Compose.media.image.withRenderingMode(.alwaysTemplate) case .poll: - return Asset.Scene.Compose.poll.image + return Asset.Scene.Compose.pollFill.image.withRenderingMode(.alwaysTemplate) case .emoji: - return Asset.Scene.Compose.emoji.image + return Asset.Scene.Compose.emojiFill.image.withRenderingMode(.alwaysTemplate) case .contentWarning: - return Asset.Scene.Compose.chatWarning.image + return Asset.Scene.Compose.chatWarningFill.image.withRenderingMode(.alwaysTemplate) case .visibility: - return Asset.Scene.Compose.earth.image + return Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate) + } + } + + var inactiveImage: UIImage { + switch self { + case .attachment: + return Asset.Scene.Compose.media.image.withRenderingMode(.alwaysTemplate) + case .poll: + return Asset.Scene.Compose.poll.image.withRenderingMode(.alwaysTemplate) + case .emoji: + return Asset.Scene.Compose.emoji.image.withRenderingMode(.alwaysTemplate) + case .contentWarning: + return Asset.Scene.Compose.chatWarning.image.withRenderingMode(.alwaysTemplate) + case .visibility: + return Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate) + } + } + } + + enum AttachmentAction: CaseIterable { + case photoLibrary + case camera + case browse + + var title: String { + switch self { + case .photoLibrary: return L10n.Scene.Compose.MediaSelection.photoLibrary + case .camera: return L10n.Scene.Compose.MediaSelection.camera + case .browse: return L10n.Scene.Compose.MediaSelection.browse + } + } + + var image: UIImage { + switch self { + case .photoLibrary: return UIImage(systemName: "photo.on.rectangle")! + case .camera: return UIImage(systemName: "camera")! + case .browse: return UIImage(systemName: "ellipsis")! } } } } + +extension ComposeContentToolbarView.ViewModel { + func image(for action: Action) -> UIImage { + switch action { + case .poll: + return isPollActive ? action.activeImage : action.inactiveImage + case .emoji: + return isEmojiActive ? action.activeImage : action.inactiveImage + case .contentWarning: + return isContentWarningActive ? action.activeImage : action.inactiveImage + default: + return action.inactiveImage + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView.swift index 775a6f4f0..679df22c0 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentToolbarView.swift @@ -5,11 +5,21 @@ // Created by MainasuK on 22/10/18. // +import os.log import SwiftUI import MastodonAsset +import MastodonLocalization +import MastodonSDK + +protocol ComposeContentToolbarViewDelegate: AnyObject { + func composeContentToolbarView(_ viewModel: ComposeContentToolbarView.ViewModel, toolbarItemDidPressed action: ComposeContentToolbarView.ViewModel.Action) + func composeContentToolbarView(_ viewModel: ComposeContentToolbarView.ViewModel, attachmentMenuDidPressed action: ComposeContentToolbarView.ViewModel.AttachmentAction) +} struct ComposeContentToolbarView: View { + let logger = Logger(subsystem: "ComposeContentToolbarView", category: "View") + static var toolbarHeight: CGFloat { 48 } @ObservedObject var viewModel: ViewModel @@ -20,20 +30,17 @@ struct ComposeContentToolbarView: View { switch action { case .attachment: Menu { - Button { - - } label: { - Label("Photo Library", systemImage: "photo.on.rectangle") - } - Button { - - } label: { - Label("Camera", systemImage: "camera") - } - Button { - - } label: { - Label("Browse", systemImage: "ellipsis") + ForEach(ComposeContentToolbarView.ViewModel.AttachmentAction.allCases, id: \.self) { attachmentAction in + Button { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public), \(attachmentAction.title)") + viewModel.delegate?.composeContentToolbarView(viewModel, attachmentMenuDidPressed: attachmentAction) + } label: { + Label { + Text(attachmentAction.title) + } icon: { + Image(uiImage: attachmentAction.image) + } + } } } label: { label(for: action) @@ -43,18 +50,23 @@ struct ComposeContentToolbarView: View { Menu { Picker(selection: $viewModel.visibility) { ForEach(viewModel.allVisibilities, id: \.self) { visibility in - Label(visibility.rawValue, systemImage: "photo.on.rectangle") + Label { + Text(visibility.title) + } icon: { + Image(uiImage: visibility.image) + } } } label: { - Text("Select Visibility") + Text(viewModel.visibility.title) } } label: { - label(for: action) + label(for: viewModel.visibility.image) } .frame(width: 48, height: 48) default: Button { - + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(String(describing: action))") + viewModel.delegate?.composeContentToolbarView(viewModel, toolbarItemDidPressed: action) } label: { label(for: action) } @@ -72,11 +84,38 @@ struct ComposeContentToolbarView: View { } - extension ComposeContentToolbarView { func label(for action: ComposeContentToolbarView.ViewModel.Action) -> some View { - Image(uiImage: action.image.withRenderingMode(.alwaysTemplate)) + Image(uiImage: viewModel.image(for: action)) + .foregroundColor(Color(Asset.Scene.Compose.buttonTint.color)) + .frame(width: 24, height: 24, alignment: .center) + } + + func label(for image: UIImage) -> some View { + Image(uiImage: image) .foregroundColor(Color(Asset.Scene.Compose.buttonTint.color)) .frame(width: 24, height: 24, alignment: .center) } } + +extension Mastodon.Entity.Status.Visibility { + fileprivate var title: String { + switch self { + case .public: return L10n.Scene.Compose.Visibility.public + case .unlisted: return L10n.Scene.Compose.Visibility.unlisted + case .private: return L10n.Scene.Compose.Visibility.private + case .direct: return L10n.Scene.Compose.Visibility.direct + case ._other(let value): return value + } + } + + fileprivate var image: UIImage { + switch self { + case .public: return Asset.Scene.Compose.earth.image.withRenderingMode(.alwaysTemplate) + case .unlisted: return Asset.Scene.Compose.people.image.withRenderingMode(.alwaysTemplate) + case .private: return Asset.Scene.Compose.peopleAdd.image.withRenderingMode(.alwaysTemplate) + case .direct: return Asset.Scene.Compose.mention.image.withRenderingMode(.alwaysTemplate) + case ._other: return Asset.Scene.Compose.more.image.withRenderingMode(.alwaysTemplate) + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift index 2b9f79321..91f3923aa 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift @@ -7,6 +7,7 @@ import os.log import SwiftUI +import MastodonCore import MastodonLocalization public struct ComposeContentView: View { @@ -21,8 +22,10 @@ public struct ComposeContentView: View { public var body: some View { VStack(spacing: .zero) { Group { + // author authorView .padding(.top, 14) + // content editor MetaTextViewRepresentable( string: $viewModel.content, width: viewModel.viewLayoutFrame.layoutFrame.width - ComposeContentView.margin * 2, @@ -44,15 +47,17 @@ public struct ComposeContentView: View { ) .frame(minHeight: 100) .fixedSize(horizontal: false, vertical: true) + // poll + pollView } .background( GeometryReader { proxy in Color.clear.preference(key: ViewFramePreferenceKey.self, value: proxy.frame(in: .local)) } - .onPreferenceChange(ViewFramePreferenceKey.self) { frame in - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): content frame: \(frame.debugDescription)") - viewModel.contentCellFrame = frame - } + .onPreferenceChange(ViewFramePreferenceKey.self) { frame in + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): content frame: \(frame.debugDescription)") + viewModel.contentCellFrame = frame + } ) Spacer() } // end VStack @@ -84,6 +89,81 @@ extension ComposeContentView { } } +extension ComposeContentView { + // MARK: - poll + var pollView: some View { + VStack { + if viewModel.isPollActive { + // poll option TextField + ReorderableForEach( + items: $viewModel.pollOptions + ) { $pollOption in + let _index = viewModel.pollOptions.firstIndex(of: pollOption) + PollOptionRow( + viewModel: pollOption, + index: _index, + deleteBackwardResponseTextFieldRelayDelegate: viewModel + ) { textField in + // viewModel.customEmojiPickerInputViewModel.configure(textInput: textField) + } + } + } + VStack(spacing: .zero) { + // expire configuration + Menu { + ForEach(PollComposeItem.ExpireConfiguration.Option.allCases, id: \.self) { option in + Button { + // viewModel.pollExpireConfiguration.option = option + // viewModel.pollExpireConfiguration = viewModel.pollExpireConfiguration + } label: { + Text(option.title) + } + } + } label: { + HStack { +// VectorImageView( +// image: Asset.ObjectTools.clock.image.withRenderingMode(.alwaysTemplate), +// tintColor: .secondaryLabel +// ) +// .frame(width: 24, height: 24) +// .padding(.vertical, 12) +// let text = viewModel.pollExpireConfigurationFormatter.string(from: TimeInterval(viewModel.pollExpireConfiguration.option.seconds)) ?? "-" +// Text(text) +// .font(.callout) +// .foregroundColor(.primary) +// Spacer() +// VectorImageView( +// image: Asset.Arrows.tablerChevronDown.image.withRenderingMode(.alwaysTemplate), +// tintColor: .secondaryLabel +// ) +// .frame(width: 24, height: 24) +// .padding(.vertical, 12) + } + } + // multi-selection configuration +// Button { +// viewModel.pollMultipleConfiguration.isMultiple.toggle() +// viewModel.pollMultipleConfiguration = viewModel.pollMultipleConfiguration +// } label: { +// HStack { +// let selectionImage = viewModel.pollMultipleConfiguration.isMultiple ? Asset.Indices.checkmarkSquare.image.withRenderingMode(.alwaysTemplate) : Asset.Indices.square.image.withRenderingMode(.alwaysTemplate) +// VectorImageView( +// image: selectionImage, +// tintColor: .secondaryLabel +// ) +// .frame(width: 24, height: 24) +// .padding(.vertical, 12) +// Text(L10n.Scene.Compose.Vote.multiple) +// .font(.callout) +// .foregroundColor(.primary) +// Spacer() +// } +// } + } + } // end VStack + } +} + //private struct ScrollOffsetPreferenceKey: PreferenceKey { // static var defaultValue: CGPoint = .zero // @@ -95,3 +175,25 @@ private struct ViewFramePreferenceKey: PreferenceKey { static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } } + +// MARK: - TypeIdentifiedItemProvider +extension PollComposeItem.Option: TypeIdentifiedItemProvider { + public static var typeIdentifier: String { + return Bundle(for: PollComposeItem.Option.self).bundleIdentifier! + String(describing: type(of: PollComposeItem.Option.self)) + } +} + +// MARK: - NSItemProviderWriting +extension PollComposeItem.Option: NSItemProviderWriting { + public func loadData( + withTypeIdentifier typeIdentifier: String, + forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void + ) -> Progress? { + completionHandler(nil, nil) + return nil + } + + public static var writableTypeIdentifiersForItemProvider: [String] { + return [Self.typeIdentifier] + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Vendor/ReorderableForEach.swift b/MastodonSDK/Sources/MastodonUI/Vendor/ReorderableForEach.swift new file mode 100644 index 000000000..9cbb5bcab --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Vendor/ReorderableForEach.swift @@ -0,0 +1,108 @@ +// +// ReorderableForEach.swift +// +// +// Created by MainasuK on 2022-5-23. +// + +import SwiftUI +import UniformTypeIdentifiers + +// Ref +// https://stackoverflow.com/a/68963988/3797903 + +struct ReorderableForEach: View { + + @State var currentReorderItem: Item? = nil + @State var isCurrentReorderItemOutside: Bool = false + + @Binding var items: [Item] + @ViewBuilder let content: (Binding) -> Content + + var body: some View { + ForEach($items) { $item in + content($item) + .zIndex(currentReorderItem == item ? 1 : 0) + .onDrop( + of: [Item.typeIdentifier], + delegate: DropRelocateDelegate( + item: item, + items: $items, + current: $currentReorderItem, + isOutside: $isCurrentReorderItemOutside + ) + ) + .onDrag { + currentReorderItem = item + isCurrentReorderItemOutside = false + return NSItemProvider(object: item) + } + } + .onDrop( + of: [Item.typeIdentifier], + delegate: DropOutsideDelegate( + current: $currentReorderItem, + isOutside: $isCurrentReorderItemOutside + ) + ) + } +} + +struct DropRelocateDelegate: DropDelegate { + let item: Item + @Binding var items: [Item] + + @Binding var current: Item? + @Binding var isOutside: Bool + + func dropEntered(info: DropInfo) { + guard item != current, let current = current else { return } + guard let from = items.firstIndex(of: current), let to = items.firstIndex(of: item) else { return } + + if items[to] != current { + withAnimation { + items.move( + fromOffsets: IndexSet(integer: from), + toOffset: to > from ? to + 1 : to + ) + } + } + } + + func dropUpdated(info: DropInfo) -> DropProposal? { + DropProposal(operation: .move) + } + + func performDrop(info: DropInfo) -> Bool { + current = nil + isOutside = false + return true + } +} + +struct DropOutsideDelegate: DropDelegate { + @Binding var current: Item? + @Binding var isOutside: Bool + + func dropEntered(info: DropInfo) { + isOutside = false + } + + func dropExited(info: DropInfo) { + isOutside = true + } + + func dropUpdated(info: DropInfo) -> DropProposal? { + return DropProposal(operation: .cancel) + } + + func performDrop(info: DropInfo) -> Bool { + current = nil + isOutside = false + return false + } +} + +public protocol TypeIdentifiedItemProvider { + static var typeIdentifier: String { get } +} diff --git a/MastodonSDK/Sources/MastodonUI/Vendor/VectorImageView.swift b/MastodonSDK/Sources/MastodonUI/Vendor/VectorImageView.swift new file mode 100644 index 000000000..901e6dcdc --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Vendor/VectorImageView.swift @@ -0,0 +1,44 @@ +// +// VectorImageView.swift +// +// +// Created by MainasuK on 2022-4-29. +// + +import UIKit +import SwiftUI + +// workaround SwiftUI vector image scale problem +// https://stackoverflow.com/a/61178828/3797903 +public struct VectorImageView: UIViewRepresentable { + + public var image: UIImage + public var contentMode: UIView.ContentMode = .scaleAspectFit + public var tintColor: UIColor = .black + + public init( + image: UIImage, + contentMode: UIView.ContentMode = .scaleAspectFit, + tintColor: UIColor = .black + ) { + self.image = image + self.contentMode = contentMode + self.tintColor = tintColor + } + + public func makeUIView(context: Context) -> UIImageView { + let imageView = UIImageView() + imageView.setContentCompressionResistancePriority( + .fittingSizeLevel, + for: .vertical + ) + return imageView + } + + public func updateUIView(_ imageView: UIImageView, context: Context) { + imageView.contentMode = contentMode + imageView.tintColor = tintColor + imageView.image = image + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/View/TextField/DeleteBackwardResponseTextField.swift b/MastodonSDK/Sources/MastodonUI/View/TextField/DeleteBackwardResponseTextField.swift index 6fd760430..a6e1bf02c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TextField/DeleteBackwardResponseTextField.swift +++ b/MastodonSDK/Sources/MastodonUI/View/TextField/DeleteBackwardResponseTextField.swift @@ -15,10 +15,20 @@ public final class DeleteBackwardResponseTextField: UITextField { public weak var deleteBackwardDelegate: DeleteBackwardResponseTextFieldDelegate? + public var textInset: UIEdgeInsets = .zero + public override func deleteBackward() { let text = self.text super.deleteBackward() deleteBackwardDelegate?.deleteBackwardResponseTextField(self, textBeforeDelete: text) } + public override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: textInset) + } + + public override func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: textInset) + } + }