forked from zelo72/mastodon-ios
feat: add pick compose image attachment logic
This commit is contained in:
parent
556964373e
commit
1b3ba1ccfb
|
@ -194,7 +194,12 @@
|
|||
"new_reply": "New Reply"
|
||||
},
|
||||
"content_input_placeholder": "Type or paste what's on your mind",
|
||||
"compose_action": "Publish"
|
||||
"compose_action": "Publish",
|
||||
"attachment": {
|
||||
"photo": "photo",
|
||||
"video": "video",
|
||||
"attachment_broken": "This %s is broken and can't be\nuploaded to Mastodon."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -212,6 +212,8 @@
|
|||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; };
|
||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; };
|
||||
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; };
|
||||
DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */; };
|
||||
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; };
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BE825E4F5340051B173 /* SearchViewController.swift */; };
|
||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */; };
|
||||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */; };
|
||||
|
@ -496,6 +498,8 @@
|
|||
DB98338525C945ED00AD9700 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
DB98338625C945ED00AD9700 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
|
||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
||||
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = "<group>"; };
|
||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = "<group>"; };
|
||||
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -718,6 +722,7 @@
|
|||
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */,
|
||||
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */,
|
||||
DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */,
|
||||
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */,
|
||||
);
|
||||
path = Vender;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1061,6 +1066,7 @@
|
|||
children = (
|
||||
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */,
|
||||
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */,
|
||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1799,6 +1805,7 @@
|
|||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
||||
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */,
|
||||
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */,
|
||||
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */,
|
||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
||||
|
@ -1834,6 +1841,7 @@
|
|||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
|
||||
DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */,
|
||||
2D206B8625F5FB0900143C56 /* Double.swift in Sources */,
|
||||
DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||
2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */,
|
||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
||||
|
|
|
@ -10,6 +10,7 @@ import Combine
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import TwitterTextEditor
|
||||
import AlamofireImage
|
||||
|
||||
enum ComposeStatusSection: Equatable, Hashable {
|
||||
case repliedTo
|
||||
|
@ -30,9 +31,10 @@ extension ComposeStatusSection {
|
|||
dependency: NeedsDependency,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
composeKind: ComposeKind,
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentTableViewCellDelegate
|
||||
) -> UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem> {
|
||||
UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>(tableView: tableView) { [weak textEditorViewTextAttributesDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||
UITableViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>(tableView: tableView) { [weak textEditorViewTextAttributesDelegate, weak composeStatusAttachmentTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .replyTo(let repliedToStatusObjectID):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeRepliedToTootContentTableViewCell.self), for: indexPath) as! ComposeRepliedToTootContentTableViewCell
|
||||
|
@ -62,7 +64,35 @@ extension ComposeStatusSection {
|
|||
return cell
|
||||
case .attachment(let attachmentService):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self), for: indexPath) as! ComposeStatusAttachmentTableViewCell
|
||||
|
||||
cell.delegate = composeStatusAttachmentTableViewCellDelegate
|
||||
attachmentService.imageData
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { imageData in
|
||||
guard let imageData = imageData,
|
||||
let image = UIImage(data: imageData) else {
|
||||
let placeholder = UIImage.placeholder(
|
||||
size: cell.attachmentContainerView.previewImageView.frame.size,
|
||||
color: Asset.Colors.Background.systemGroupedBackground.color
|
||||
)
|
||||
.af.imageRounded(
|
||||
withCornerRadius: AttachmentContainerView.containerViewCornerRadius
|
||||
)
|
||||
cell.attachmentContainerView.previewImageView.image = placeholder
|
||||
return
|
||||
}
|
||||
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
cell.attachmentContainerView.previewImageView.image = image
|
||||
.af.imageAspectScaled(toFill: cell.attachmentContainerView.previewImageView.frame.size)
|
||||
.af.imageRounded(withCornerRadius: AttachmentContainerView.containerViewCornerRadius)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
attachmentService.error
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { error in
|
||||
cell.attachmentContainerView.activityIndicatorView.stopAnimating()
|
||||
cell.attachmentContainerView.emptyStateView.isHidden = error == nil
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,16 @@ internal enum Asset {
|
|||
}
|
||||
internal enum Colors {
|
||||
internal enum Background {
|
||||
internal enum AudioPlayer {
|
||||
internal static let highlight = ColorAsset(name: "Colors/Background/AudioPlayer/highlight")
|
||||
}
|
||||
internal enum Poll {
|
||||
internal static let disabled = ColorAsset(name: "Colors/Background/Poll/disabled")
|
||||
internal static let highlight = ColorAsset(name: "Colors/Background/Poll/highlight")
|
||||
}
|
||||
internal static let mediaTypeIndicotor = ColorAsset(name: "Colors/Background/mediaTypeIndicotor")
|
||||
internal static let dangerBorder = ColorAsset(name: "Colors/Background/danger.border")
|
||||
internal static let danger = ColorAsset(name: "Colors/Background/danger")
|
||||
internal static let mediaTypeIndicotor = ColorAsset(name: "Colors/Background/media.type.indicotor")
|
||||
internal static let onboardingBackground = ColorAsset(name: "Colors/Background/onboarding.background")
|
||||
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Colors/Background/secondary.grouped.system.background")
|
||||
internal static let secondarySystemBackground = ColorAsset(name: "Colors/Background/secondary.system.background")
|
||||
|
@ -45,7 +50,6 @@ internal enum Asset {
|
|||
internal enum Button {
|
||||
internal static let actionToolbar = ColorAsset(name: "Colors/Button/action.toolbar")
|
||||
internal static let disabled = ColorAsset(name: "Colors/Button/disabled")
|
||||
internal static let highlight = ColorAsset(name: "Colors/Button/highlight")
|
||||
internal static let normal = ColorAsset(name: "Colors/Button/normal")
|
||||
}
|
||||
internal enum Icon {
|
||||
|
@ -79,10 +83,12 @@ internal enum Asset {
|
|||
internal static let lightSecondaryText = ColorAsset(name: "Colors/lightSecondaryText")
|
||||
internal static let lightSuccessGreen = ColorAsset(name: "Colors/lightSuccessGreen")
|
||||
internal static let lightWhite = ColorAsset(name: "Colors/lightWhite")
|
||||
internal static let plusCircleFill = ImageAsset(name: "Colors/plus.circle.fill")
|
||||
internal static let systemGreen = ColorAsset(name: "Colors/system.green")
|
||||
internal static let systemOrange = ColorAsset(name: "Colors/system.orange")
|
||||
}
|
||||
internal enum Connectivity {
|
||||
internal static let photoFillSplit = ImageAsset(name: "Connectivity/photo.fill.split")
|
||||
}
|
||||
internal enum Welcome {
|
||||
internal enum Illustration {
|
||||
internal static let backgroundCyan = ColorAsset(name: "Welcome/illustration/background.cyan")
|
||||
|
|
|
@ -144,6 +144,16 @@ internal enum L10n {
|
|||
internal static let composeAction = L10n.tr("Localizable", "Scene.Compose.ComposeAction")
|
||||
/// Type or paste what's on your mind
|
||||
internal static let contentInputPlaceholder = L10n.tr("Localizable", "Scene.Compose.ContentInputPlaceholder")
|
||||
internal enum Attachment {
|
||||
/// This %@ is broken and can't be\nuploaded to Mastodon.
|
||||
internal static func attachmentBroken(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Compose.Attachment.AttachmentBroken", String(describing: p1))
|
||||
}
|
||||
/// photo
|
||||
internal static let photo = L10n.tr("Localizable", "Scene.Compose.Attachment.Photo")
|
||||
/// video
|
||||
internal static let video = L10n.tr("Localizable", "Scene.Compose.Attachment.Video")
|
||||
}
|
||||
internal enum Title {
|
||||
/// New Post
|
||||
internal static let newPost = L10n.tr("Localizable", "Scene.Compose.Title.NewPost")
|
||||
|
|
|
@ -2,20 +2,14 @@
|
|||
"images" : [
|
||||
{
|
||||
"filename" : "plus.circle.fill.pdf",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.839",
|
||||
"green" : "0.573",
|
||||
"red" : "0.204"
|
||||
"blue" : "0.851",
|
||||
"green" : "0.565",
|
||||
"red" : "0.169"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.353",
|
||||
"green" : "0.251",
|
||||
"red" : "0.875"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "90",
|
||||
"green" : "64",
|
||||
"red" : "223"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE8",
|
||||
"green" : "0xE1",
|
||||
"red" : "0xD9"
|
||||
"blue" : "232",
|
||||
"green" : "225",
|
||||
"red" : "217"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "plus.circle.fill.pdf",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
%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 0.000000 0.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
30.000000 15.000000 m
|
||||
30.000000 6.715729 23.284271 0.000000 15.000000 0.000000 c
|
||||
6.715729 0.000000 0.000000 6.715729 0.000000 15.000000 c
|
||||
0.000000 23.284271 6.715729 30.000000 15.000000 30.000000 c
|
||||
23.284271 30.000000 30.000000 23.284271 30.000000 15.000000 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
15.009004 0.000000 m
|
||||
23.233341 0.000000 30.000000 6.766640 30.000000 15.009003 c
|
||||
30.000000 23.233379 23.233341 30.000000 14.991017 30.000000 c
|
||||
6.766642 30.000000 0.000000 23.233379 0.000000 15.009003 c
|
||||
0.000000 6.766640 6.766643 0.000000 15.009004 0.000000 c
|
||||
h
|
||||
8.098384 15.009003 m
|
||||
8.098384 16.034798 8.836217 16.790653 9.844025 16.790653 c
|
||||
13.209368 16.790653 l
|
||||
13.209368 20.155996 l
|
||||
13.209368 21.163769 13.965223 21.919624 14.991017 21.919624 c
|
||||
16.016811 21.919624 16.772667 21.163769 16.772667 20.155996 c
|
||||
16.772667 16.790653 l
|
||||
20.137974 16.790653 l
|
||||
21.163769 16.790653 21.901638 16.034798 21.901638 15.009003 c
|
||||
21.901638 13.983208 21.163769 13.227352 20.137974 13.227352 c
|
||||
16.772667 13.227352 l
|
||||
16.772667 9.862047 l
|
||||
16.772667 8.854239 16.016811 8.098381 14.991017 8.098381 c
|
||||
13.965223 8.098381 13.209368 8.854239 13.209368 9.862047 c
|
||||
13.209368 13.227352 l
|
||||
9.844025 13.227352 l
|
||||
8.836217 13.227352 8.098384 13.983208 8.098384 15.009003 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1426
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.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
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001516 00000 n
|
||||
0000001539 00000 n
|
||||
0000001712 00000 n
|
||||
0000001786 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1845
|
||||
%%EOF
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
15
Mastodon/Resources/Assets.xcassets/Connectivity/photo.fill.split.imageset/Contents.json
vendored
Normal file
15
Mastodon/Resources/Assets.xcassets/Connectivity/photo.fill.split.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Frame 2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
114
Mastodon/Resources/Assets.xcassets/Connectivity/photo.fill.split.imageset/Frame 2.pdf
vendored
Normal file
114
Mastodon/Resources/Assets.xcassets/Connectivity/photo.fill.split.imageset/Frame 2.pdf
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
0.992546 -0.121869 0.121869 0.992546 42.624641 7.462139 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
29.841717 4.404652 m
|
||||
10.813622 4.404652 l
|
||||
5.729721 9.441498 l
|
||||
29.810324 9.441498 l
|
||||
32.782593 9.441498 34.628483 11.256016 34.628483 14.259354 c
|
||||
34.628483 19.077240 l
|
||||
20.237179 32.404518 l
|
||||
18.766808 33.781090 16.983574 34.438072 15.262939 34.438072 c
|
||||
13.481962 34.438072 11.763362 33.813934 10.231857 32.441067 c
|
||||
0.000000 39.493633 l
|
||||
11.853184 50.706104 l
|
||||
1.586006 62.000000 l
|
||||
29.841717 62.000000 l
|
||||
36.411587 62.000000 39.665127 58.746330 39.665127 52.301659 c
|
||||
39.665127 14.102936 l
|
||||
39.665127 7.658268 36.411587 4.404652 29.841717 4.404652 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.000000 11.404663 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
35.690556 57.595337 m
|
||||
9.823408 57.595337 l
|
||||
3.284870 57.595337 0.000000 54.372997 0.000000 47.896996 c
|
||||
0.000000 9.698273 l
|
||||
0.000000 3.222317 3.284870 -0.000011 9.823408 -0.000011 c
|
||||
44.918179 -0.000011 l
|
||||
39.834278 5.036835 l
|
||||
9.886006 5.036835 l
|
||||
6.851334 5.036835 5.036836 6.851357 5.036836 9.917267 c
|
||||
5.036836 11.825638 l
|
||||
14.641250 20.209938 l
|
||||
16.017820 21.430046 17.519461 22.055767 18.896032 22.055767 c
|
||||
20.428938 22.055767 22.024504 21.430050 23.401012 20.147408 c
|
||||
29.376427 14.766380 l
|
||||
44.330532 28.031185 l
|
||||
44.332489 28.032942 44.334446 28.034697 44.336403 28.036451 c
|
||||
34.104435 35.089096 l
|
||||
45.957619 46.301567 l
|
||||
35.690556 57.595337 l
|
||||
h
|
||||
15.736227 35.758499 m
|
||||
15.736227 31.503782 19.208826 28.031185 23.463608 28.031185 c
|
||||
27.687059 28.031185 31.159658 31.503782 31.159658 35.758499 c
|
||||
31.159658 39.981949 27.687059 43.485878 23.463608 43.485878 c
|
||||
19.208826 43.485878 15.736227 39.981949 15.736227 35.758499 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1681
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 92.000000 76.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
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001771 00000 n
|
||||
0000001794 00000 n
|
||||
0000001967 00000 n
|
||||
0000002041 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2100
|
||||
%%EOF
|
|
@ -38,6 +38,10 @@
|
|||
"Common.Controls.Timeline.Loader.LoadingMissingPosts" = "Loading missing posts...";
|
||||
"Common.Countable.Photo.Multiple" = "photos";
|
||||
"Common.Countable.Photo.Single" = "photo";
|
||||
"Scene.Compose.Attachment.AttachmentBroken" = "This %@ is broken and can't be
|
||||
uploaded to Mastodon.";
|
||||
"Scene.Compose.Attachment.Photo" = "photo";
|
||||
"Scene.Compose.Attachment.Video" = "video";
|
||||
"Scene.Compose.ComposeAction" = "Publish";
|
||||
"Scene.Compose.ContentInputPlaceholder" = "Type or paste what's on your mind";
|
||||
"Scene.Compose.Title.NewPost" = "New Post";
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import TwitterTextEditor
|
||||
import PhotosUI
|
||||
import Kingfisher
|
||||
import TwitterTextEditor
|
||||
|
||||
final class ComposeViewController: UIViewController, NeedsDependency {
|
||||
|
||||
|
@ -42,6 +43,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
|||
tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self))
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.separatorStyle = .none
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
return tableView
|
||||
}()
|
||||
|
||||
|
@ -57,6 +59,16 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
|||
return backgroundView
|
||||
}()
|
||||
|
||||
lazy var imagePicker: PHPickerViewController = {
|
||||
var configuration = PHPickerConfiguration()
|
||||
configuration.filter = .images
|
||||
configuration.selectionLimit = 4
|
||||
|
||||
let imagePicker = PHPickerViewController(configuration: configuration)
|
||||
imagePicker.delegate = self
|
||||
return imagePicker
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
extension ComposeViewController {
|
||||
|
@ -109,7 +121,8 @@ extension ComposeViewController {
|
|||
viewModel.setupDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: self,
|
||||
textEditorViewTextAttributesDelegate: self
|
||||
textEditorViewTextAttributesDelegate: self,
|
||||
composeStatusAttachmentTableViewCellDelegate: self
|
||||
)
|
||||
|
||||
// respond scrollView overlap change
|
||||
|
@ -377,15 +390,12 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - ComposeToolbarViewDelegate
|
||||
extension ComposeViewController: ComposeToolbarViewDelegate {
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let attachmentService = MastodonAttachmentService()
|
||||
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
|
||||
present(imagePicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, gifButtonDidPressed sender: UIButton) {
|
||||
|
@ -431,3 +441,29 @@ extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - PHPickerViewControllerDelegate
|
||||
extension ComposeViewController: PHPickerViewControllerDelegate {
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true, completion: nil)
|
||||
let attachmentServices = results.map { MastodonAttachmentService(pickerResult: $0) }
|
||||
viewModel.attachmentServices.value = viewModel.attachmentServices.value + attachmentServices
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ComposeStatusAttachmentTableViewCellDelegate
|
||||
extension ComposeViewController: ComposeStatusAttachmentTableViewCellDelegate {
|
||||
|
||||
func composeStatusAttachmentTableViewCell(_ cell: ComposeStatusAttachmentTableViewCell, removeButtonDidPressed button: UIButton) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
guard case let .attachment(attachmentService) = item else { return }
|
||||
|
||||
var attachmentServices = viewModel.attachmentServices.value
|
||||
guard let index = attachmentServices.firstIndex(of: attachmentService) else { return }
|
||||
attachmentServices.remove(at: index)
|
||||
viewModel.attachmentServices.value = attachmentServices
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,14 +13,16 @@ extension ComposeViewModel {
|
|||
func setupDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentTableViewCellDelegate
|
||||
) {
|
||||
diffableDataSource = ComposeStatusSection.tableViewDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: dependency,
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
composeKind: composeKind,
|
||||
textEditorViewTextAttributesDelegate: textEditorViewTextAttributesDelegate
|
||||
textEditorViewTextAttributesDelegate: textEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate
|
||||
)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusSection, ComposeStatusItem>()
|
||||
|
|
|
@ -5,13 +5,44 @@
|
|||
// Created by MainasuK Cirno on 2021-3-17.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
protocol ComposeStatusAttachmentTableViewCellDelegate: class {
|
||||
func composeStatusAttachmentTableViewCell(_ cell: ComposeStatusAttachmentTableViewCell, removeButtonDidPressed button: UIButton)
|
||||
}
|
||||
|
||||
final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
||||
|
||||
static let verticalMarginHeight: CGFloat = 8
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
static let verticalMarginHeight: CGFloat = ComposeStatusAttachmentTableViewCell.removeButtonSize.height * 0.5
|
||||
static let removeButtonSize = CGSize(width: 22, height: 22)
|
||||
|
||||
weak var delegate: ComposeStatusAttachmentTableViewCellDelegate?
|
||||
|
||||
let attachmentContainerView = AttachmentContainerView()
|
||||
let removeButton: UIButton = {
|
||||
let button = HighlightDimmableButton()
|
||||
button.expandEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
|
||||
let image = UIImage(systemName: "minus")!.withConfiguration(UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))
|
||||
button.tintColor = .white
|
||||
button.setImage(image, for: .normal)
|
||||
button.setBackgroundImage(.placeholder(color: Asset.Colors.Background.danger.color), for: .normal)
|
||||
button.layer.masksToBounds = true
|
||||
button.layer.cornerRadius = ComposeStatusAttachmentTableViewCell.removeButtonSize.width * 0.5
|
||||
button.layer.borderColor = Asset.Colors.Background.dangerBorder.color.cgColor
|
||||
button.layer.borderWidth = 1
|
||||
return button
|
||||
}()
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
attachmentContainerView.activityIndicatorView.startAnimating()
|
||||
delegate = nil
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
@ -28,6 +59,8 @@ final class ComposeStatusAttachmentTableViewCell: UITableViewCell {
|
|||
extension ComposeStatusAttachmentTableViewCell {
|
||||
|
||||
private func _init() {
|
||||
selectionStyle = .none
|
||||
|
||||
attachmentContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(attachmentContainerView)
|
||||
NSLayoutConstraint.activate([
|
||||
|
@ -38,8 +71,26 @@ extension ComposeStatusAttachmentTableViewCell {
|
|||
attachmentContainerView.heightAnchor.constraint(equalToConstant: 205).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
attachmentContainerView.attachmentPreviewImageView.backgroundColor = .systemFill
|
||||
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(removeButton)
|
||||
NSLayoutConstraint.activate([
|
||||
removeButton.centerXAnchor.constraint(equalTo: attachmentContainerView.trailingAnchor),
|
||||
removeButton.centerYAnchor.constraint(equalTo: attachmentContainerView.topAnchor),
|
||||
removeButton.widthAnchor.constraint(equalToConstant: ComposeStatusAttachmentTableViewCell.removeButtonSize.width).priority(.defaultHigh),
|
||||
removeButton.heightAnchor.constraint(equalToConstant: ComposeStatusAttachmentTableViewCell.removeButtonSize.height).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
removeButton.addTarget(self, action: #selector(ComposeStatusAttachmentTableViewCell.removeButtonDidPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension ComposeStatusAttachmentTableViewCell {
|
||||
|
||||
@objc private func removeButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeStatusAttachmentTableViewCell(self, removeButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// AttachmentContainerView+EmptyStateView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension AttachmentContainerView {
|
||||
final class EmptyStateView: UIView {
|
||||
|
||||
static let photoFillSplitImage = Asset.Connectivity.photoFillSplit.image.withRenderingMode(.alwaysTemplate)
|
||||
static let videoSplashImage: UIImage = {
|
||||
let image = UIImage(systemName: "video.slash")!.withConfiguration(UIImage.SymbolConfiguration(pointSize: 64))
|
||||
return image
|
||||
}()
|
||||
|
||||
let imageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.tintColor = Asset.Colors.Label.secondary.color
|
||||
imageView.image = AttachmentContainerView.EmptyStateView.photoFillSplitImage
|
||||
return imageView
|
||||
}()
|
||||
let label: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textColor = Asset.Colors.Label.secondary.color
|
||||
label.textAlignment = .center
|
||||
label.text = L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentContainerView.EmptyStateView {
|
||||
private func _init() {
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = AttachmentContainerView.containerViewCornerRadius
|
||||
layer.cornerCurve = .continuous
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .center
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(stackView)
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
let topPaddingView = UIView()
|
||||
let middlePaddingView = UIView()
|
||||
let bottomPaddingView = UIView()
|
||||
|
||||
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(topPaddingView)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: 92).priority(.defaultHigh),
|
||||
imageView.heightAnchor.constraint(equalToConstant: 76).priority(.defaultHigh),
|
||||
])
|
||||
imageView.setContentHuggingPriority(.required - 1, for: .vertical)
|
||||
middlePaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(middlePaddingView)
|
||||
stackView.addArrangedSubview(label)
|
||||
bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(bottomPaddingView)
|
||||
NSLayoutConstraint.activate([
|
||||
topPaddingView.heightAnchor.constraint(equalTo: middlePaddingView.heightAnchor, multiplier: 1.5),
|
||||
bottomPaddingView.heightAnchor.constraint(equalTo: middlePaddingView.heightAnchor, multiplier: 1.5),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct AttachmentContainerView_EmptyStateView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview(width: 375) {
|
||||
let emptyStateView = AttachmentContainerView.EmptyStateView()
|
||||
NSLayoutConstraint.activate([
|
||||
emptyStateView.heightAnchor.constraint(equalToConstant: 205)
|
||||
])
|
||||
return emptyStateView
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 205))
|
||||
UIViewPreview(width: 375) {
|
||||
let emptyStateView = AttachmentContainerView.EmptyStateView()
|
||||
NSLayoutConstraint.activate([
|
||||
emptyStateView.heightAnchor.constraint(equalToConstant: 205)
|
||||
])
|
||||
return emptyStateView
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
.previewLayout(.fixed(width: 375, height: 205))
|
||||
UIViewPreview(width: 375) {
|
||||
let emptyStateView = AttachmentContainerView.EmptyStateView()
|
||||
emptyStateView.imageView.image = AttachmentContainerView.EmptyStateView.videoSplashImage
|
||||
emptyStateView.label.text = L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
emptyStateView.heightAnchor.constraint(equalToConstant: 205)
|
||||
])
|
||||
return emptyStateView
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 205))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -8,16 +8,20 @@
|
|||
import UIKit
|
||||
|
||||
final class AttachmentContainerView: UIView {
|
||||
|
||||
static let containerViewCornerRadius: CGFloat = 4
|
||||
|
||||
let attachmentPreviewImageView: UIImageView = {
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
|
||||
|
||||
let previewImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = 4
|
||||
imageView.layer.cornerCurve = .continuous
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let emptyStateView = AttachmentContainerView.EmptyStateView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
|
@ -33,16 +37,34 @@ final class AttachmentContainerView: UIView {
|
|||
extension AttachmentContainerView {
|
||||
|
||||
private func _init() {
|
||||
|
||||
attachmentPreviewImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(attachmentPreviewImageView)
|
||||
previewImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(previewImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
attachmentPreviewImageView.topAnchor.constraint(equalTo: topAnchor),
|
||||
attachmentPreviewImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
attachmentPreviewImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
attachmentPreviewImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
previewImageView.topAnchor.constraint(equalTo: topAnchor),
|
||||
previewImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
previewImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
previewImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
|
||||
emptyStateView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(emptyStateView)
|
||||
NSLayoutConstraint.activate([
|
||||
emptyStateView.topAnchor.constraint(equalTo: topAnchor),
|
||||
emptyStateView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
emptyStateView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
emptyStateView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
|
||||
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(activityIndicatorView)
|
||||
NSLayoutConstraint.activate([
|
||||
activityIndicatorView.centerXAnchor.constraint(equalTo: previewImageView.centerXAnchor),
|
||||
activityIndicatorView.centerYAnchor.constraint(equalTo: previewImageView.centerYAnchor),
|
||||
])
|
||||
|
||||
emptyStateView.isHidden = true
|
||||
activityIndicatorView.hidesWhenStopped = true
|
||||
activityIndicatorView.startAnimating()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ final class HomeTimelineNavigationBarView {
|
|||
}()
|
||||
|
||||
static let newPostsView: UIView = {
|
||||
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.Button.highlight.color)
|
||||
let view = HomeTimelineNavigationBarView.backgroundViewWithColor(color: Asset.Colors.Button.normal.color)
|
||||
let label = HomeTimelineNavigationBarView.contentLabel(text: L10n.Scene.HomeTimeline.NavigationBarState.newPosts)
|
||||
HomeTimelineNavigationBarView.addLabelToView(label: label, view: view)
|
||||
return view
|
||||
|
|
|
@ -37,10 +37,10 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
|
|||
private(set) lazy var signUpButton: PrimaryActionButton = {
|
||||
let button = PrimaryActionButton()
|
||||
button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
|
||||
let backgroundImageColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? .white : Asset.Colors.Button.highlight.color
|
||||
let backgroundImageColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? .white : Asset.Colors.Button.normal.color
|
||||
button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal)
|
||||
button.setBackgroundImage(.placeholder(color: backgroundImageColor.withAlphaComponent(0.9)), for: .highlighted)
|
||||
let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? Asset.Colors.Button.highlight.color : UIColor.white
|
||||
let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? Asset.Colors.Button.normal.color : UIColor.white
|
||||
button.setTitleColor(titleColor, for: .normal)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
return button
|
||||
|
@ -50,7 +50,7 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
|
|||
let button = UIButton(type: .system)
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold))
|
||||
button.setTitle(L10n.Common.Controls.Actions.signIn, for: .normal)
|
||||
let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? UIColor.white.withAlphaComponent(0.8) : Asset.Colors.Button.highlight.color
|
||||
let titleColor: UIColor = traitCollection.userInterfaceIdiom == .phone ? UIColor.white.withAlphaComponent(0.8) : Asset.Colors.Button.normal.color
|
||||
button.setTitleColor(titleColor, for: .normal)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
return button
|
||||
|
|
|
@ -9,6 +9,8 @@ import UIKit
|
|||
|
||||
final class HighlightDimmableButton: UIButton {
|
||||
|
||||
var expandEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
|
@ -19,6 +21,9 @@ final class HighlightDimmableButton: UIButton {
|
|||
_init()
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return bounds.inset(by: expandEdgeInsets).contains(point)
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
|
|
|
@ -38,8 +38,8 @@ extension PrimaryActionButton {
|
|||
private func _init() {
|
||||
titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
|
||||
setTitleColor(.white, for: .normal)
|
||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.highlight.color), for: .normal)
|
||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.highlight.color.withAlphaComponent(0.5)), for: .highlighted)
|
||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.normal.color), for: .normal)
|
||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted)
|
||||
setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
|
||||
applyCornerRadius(radius: 10)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ final class AudioContainerView: UIView {
|
|||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
stackView.layer.cornerRadius = AudioContainerView.cornerRadius
|
||||
stackView.clipsToBounds = true
|
||||
stackView.backgroundColor = Asset.Colors.Button.highlight.color
|
||||
stackView.backgroundColor = Asset.Colors.Background.AudioPlayer.highlight.color
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return stackView
|
||||
}()
|
||||
|
@ -31,7 +31,7 @@ final class AudioContainerView: UIView {
|
|||
let view = UIView()
|
||||
view.layer.cornerRadius = 16
|
||||
view.clipsToBounds = true
|
||||
view.backgroundColor = Asset.Colors.Button.highlight.color
|
||||
view.backgroundColor = Asset.Colors.Background.AudioPlayer.highlight.color
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
|
@ -109,3 +109,20 @@ extension AudioContainerView {
|
|||
])
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct AudioContainerView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
UIViewPreview(width: 375) {
|
||||
AudioContainerView()
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 100))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -151,8 +151,8 @@ final class StatusView: UIView {
|
|||
let button = HitTestExpandedButton()
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14, weight: .semibold))
|
||||
button.setTitle(L10n.Common.Controls.Status.Poll.vote, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color.withAlphaComponent(0.8), for: .highlighted)
|
||||
button.setTitleColor(Asset.Colors.Button.normal.color, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.normal.color.withAlphaComponent(0.8), for: .highlighted)
|
||||
button.setTitleColor(Asset.Colors.Button.disabled.color, for: .disabled)
|
||||
button.isEnabled = false
|
||||
return button
|
||||
|
|
|
@ -35,7 +35,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
|||
let imageView = UIImageView()
|
||||
let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))!
|
||||
imageView.image = image.withRenderingMode(.alwaysTemplate)
|
||||
imageView.tintColor = Asset.Colors.Button.highlight.color
|
||||
imageView.tintColor = Asset.Colors.Button.normal.color
|
||||
return imageView
|
||||
}()
|
||||
|
||||
|
|
|
@ -7,11 +7,41 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import PhotosUI
|
||||
|
||||
final class MastodonAttachmentService {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let identifier = UUID()
|
||||
|
||||
// input
|
||||
let pickerResult: PHPickerResult
|
||||
|
||||
// output
|
||||
let imageData = CurrentValueSubject<Data?, Never>(nil)
|
||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||
|
||||
init(pickerResult: PHPickerResult) {
|
||||
self.pickerResult = pickerResult
|
||||
// end init
|
||||
|
||||
PHPickerResultLoader.loadImageData(from: pickerResult)
|
||||
.sink { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
self.error.value = error
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] imageData in
|
||||
guard let self = self else { return }
|
||||
self.imageData.value = imageData
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonAttachmentService: Equatable, Hashable {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// PHPickerResultLoader.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-18.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import Combine
|
||||
import MobileCoreServices
|
||||
import PhotosUI
|
||||
|
||||
// load image with low memory usage
|
||||
// Refs: https://christianselig.com/2020/09/phpickerviewcontroller-efficiently/
|
||||
enum PHPickerResultLoader {
|
||||
|
||||
static func loadImageData(from result: PHPickerResult) -> Future<Data?, Error> {
|
||||
Future { promise in
|
||||
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
|
||||
if let error = error {
|
||||
promise(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = url else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
|
||||
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
|
||||
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
|
||||
return
|
||||
}
|
||||
|
||||
let downsampleOptions = [
|
||||
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
kCGImageSourceThumbnailMaxPixelSize: 4096,
|
||||
] as CFDictionary
|
||||
|
||||
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {
|
||||
return
|
||||
}
|
||||
|
||||
let data = NSMutableData()
|
||||
guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
|
||||
let isPNG: Bool = {
|
||||
guard let utType = cgImage.utType else { return false }
|
||||
return (utType as String) == UTType.png.identifier
|
||||
}()
|
||||
|
||||
let destinationProperties = [
|
||||
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
|
||||
] as CFDictionary
|
||||
|
||||
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
|
||||
CGImageDestinationFinalize(imageDestination)
|
||||
|
||||
let dataSize = ByteCountFormatter.string(fromByteCount: Int64(data.length), countStyle: .memory)
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: load image %s", ((#file as NSString).lastPathComponent), #line, #function, dataSize)
|
||||
|
||||
promise(.success(data as Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue