feat: [WIP] restore compose poll view

This commit is contained in:
CMK 2022-10-21 19:12:44 +08:00
parent f1b5c52815
commit 44a8b818e4
28 changed files with 1722 additions and 132 deletions

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Mention.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "More.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "People.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "chat.warning.fill.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "emoji.fill.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "People Add.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "poll.fill.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
//
// PollComposeSection.swift
//
//
// Created by MainasuK on 2021-11-29.
//
import Foundation
public enum PollComposeSection: Hashable {
case main
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Content: View, Item: Identifiable & Equatable & NSItemProviderWriting & TypeIdentifiedItemProvider>: View {
@State var currentReorderItem: Item? = nil
@State var isCurrentReorderItemOutside: Bool = false
@Binding var items: [Item]
@ViewBuilder let content: (Binding<Item>) -> 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<Item: Equatable>: 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<Item: Equatable>: 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 }
}

View File

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

View File

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