feat: add AvatarConfigurableView

This commit is contained in:
CMK 2021-02-04 19:28:16 +08:00
parent ef55edca10
commit 0abd8ab863
5 changed files with 258 additions and 4 deletions

View File

@ -67,6 +67,10 @@
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */; };
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */; };
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; };
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; };
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; };
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; };
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; };
DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DB89B9F025C10FD0008580ED /* CoreDataStack.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -97,6 +101,7 @@
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 */; };
DBD4ED1125CC0FEB0041B741 /* HomeTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD4ED1025CC0FEB0041B741 /* HomeTimelineViewModel.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -220,6 +225,9 @@
DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonAuthentication.swift"; sourceTree = "<group>"; };
DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = "<group>"; };
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HomeTimeline.swift"; sourceTree = "<group>"; };
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = "<group>"; };
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = "<group>"; };
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = "<group>"; };
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = "<group>"; };
DB89B9F125C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -252,6 +260,7 @@
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>"; };
DBD4ED1025CC0FEB0041B741 /* HomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewModel.swift; sourceTree = "<group>"; };
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = "<group>"; };
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MastodonSDK.xctestplan; sourceTree = "<group>"; };
EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = "<group>"; };
@ -266,6 +275,7 @@
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */,
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */,
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
45B49097460EDE530AD5AA72 /* Pods_Mastodon.framework in Frameworks */,
@ -339,6 +349,7 @@
2D42FF8325C82245004A627A /* Button */ = {
isa = PBXGroup;
children = (
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */,
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */,
);
path = Button;
@ -364,6 +375,7 @@
2D69CFF225CA9E2200C3A1B2 /* Protocol */ = {
isa = PBXGroup;
children = (
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */,
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */,
);
path = Protocol;
@ -529,13 +541,14 @@
children = (
DB427DE325BAA00100D1B89D /* Info.plist */,
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
2D69CFF225CA9E2200C3A1B2 /* Protocol */,
2D76319C25C151DE00929FB9 /* Diffiable */,
DB8AF52A25C13561002E6C99 /* State */,
2D61335525C1886800CAE157 /* Service */,
DB8AF55525C1379F002E6C99 /* Scene */,
DB8AF54125C13647002E6C99 /* Coordinator */,
DB8AF56225C138BC002E6C99 /* Extension */,
DB5086CB25CC0DB400C2C187 /* Preference */,
2D69CFF225CA9E2200C3A1B2 /* Protocol */,
DB98338425C945ED00AD9700 /* Generated */,
DB3D0FF825BAA6B200EAA174 /* Resources */,
DB3D0FF725BAA68500EAA174 /* Supporting Files */,
@ -587,6 +600,14 @@
path = CoreData;
sourceTree = "<group>";
};
DB5086CB25CC0DB400C2C187 /* Preference */ = {
isa = PBXGroup;
children = (
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */,
);
path = Preference;
sourceTree = "<group>";
};
DB89B9EF25C10FD0008580ED /* CoreDataStack */ = {
isa = PBXGroup;
children = (
@ -677,10 +698,10 @@
isa = PBXGroup;
children = (
2D7631A425C1532200929FB9 /* Share */,
DB8AF54E25C13703002E6C99 /* MainTab */,
DB01409B25C40BB600F9F3CF /* Authentication */,
2D76316325C14BAC00929FB9 /* PublicTimeline */,
DB8AF54E25C13703002E6C99 /* MainTab */,
DB8AF55625C137A8002E6C99 /* HomeViewController.swift */,
DBD4ED0B25CC0FD40041B741 /* HomeTimeline */,
);
path = Scene;
sourceTree = "<group>";
@ -720,6 +741,15 @@
path = Generated;
sourceTree = "<group>";
};
DBD4ED0B25CC0FD40041B741 /* HomeTimeline */ = {
isa = PBXGroup;
children = (
DB8AF55625C137A8002E6C99 /* HomeViewController.swift */,
DBD4ED1025CC0FEB0041B741 /* HomeTimelineViewModel.swift */,
);
path = HomeTimeline;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@ -758,6 +788,7 @@
2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */,
2D42FF6025C8177C004A627A /* ActiveLabel */,
DB0140BC25C40D7500F9F3CF /* CommonOSLog */,
DB5086B725CC0D6400C2C187 /* Kingfisher */,
);
productName = Mastodon;
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
@ -884,6 +915,7 @@
2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */,
2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */,
DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */,
DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */,
);
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
projectDirPath = "";
@ -1082,11 +1114,13 @@
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */,
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */,
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
@ -1096,6 +1130,7 @@
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */,
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
DBD4ED1125CC0FEB0041B741 /* HomeTimelineViewModel.swift in Sources */,
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
@ -1116,6 +1151,7 @@
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */,
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */,
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */,
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
);
@ -1661,6 +1697,14 @@
minimumVersion = 4.1.0;
};
};
DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 6.1.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -1688,6 +1732,11 @@
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
productName = AlamofireImage;
};
DB5086B725CC0D6400C2C187 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */

View File

@ -46,6 +46,15 @@
"version": "0.1.1"
}
},
{
"package": "Kingfisher",
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": {
"branch": null,
"revision": "daebf8ddf974164d1b9a050c8231e263f3106b09",
"version": "6.1.0"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",

View File

@ -0,0 +1,146 @@
//
// AvatarConfigurableView.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-2-4.
//
import UIKit
import AlamofireImage
import Kingfisher
protocol AvatarConfigurableView {
static var configurableAvatarImageViewSize: CGSize { get }
static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { get }
var configurableAvatarImageView: UIImageView? { get }
var configurableAvatarButton: UIButton? { get }
var configurableVerifiedBadgeImageView: UIImageView? { get }
func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input)
func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration)
}
extension AvatarConfigurableView {
static var configurableAvatarImageViewBadgeAppearanceStyle: AvatarConfigurableViewConfiguration.BadgeAppearanceStyle { return .mini }
public func configure(withConfigurationInput input: AvatarConfigurableViewConfiguration.Input) {
// TODO: set badge
configurableVerifiedBadgeImageView?.isHidden = true
let cornerRadius = Self.configurableAvatarImageViewSize.width * 0.5
// let scale = (configurableAvatarImageView ?? configurableAvatarButton)?.window?.screen.scale ?? UIScreen.main.scale
let placeholderImage: UIImage = {
let placeholderImage = input.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageViewSize, color: .systemFill)
return placeholderImage.af.imageRoundedIntoCircle()
}()
// cancel previous task
configurableAvatarImageView?.af.cancelImageRequest()
configurableAvatarImageView?.kf.cancelDownloadTask()
configurableAvatarButton?.af.cancelImageRequest(for: .normal)
configurableAvatarButton?.kf.cancelImageDownloadTask()
// reset layer attributes
configurableAvatarImageView?.layer.masksToBounds = false
configurableAvatarImageView?.layer.cornerRadius = 0
configurableAvatarImageView?.layer.cornerCurve = .circular
configurableAvatarButton?.layer.masksToBounds = false
configurableAvatarButton?.layer.cornerRadius = 0
configurableAvatarButton?.layer.cornerCurve = .circular
defer {
let configuration = AvatarConfigurableViewConfiguration(input: input)
avatarConfigurableView(self, didFinishConfiguration: configuration)
}
// set placeholder if no asset
guard let avatarImageURL = input.avatarImageURL else {
configurableAvatarImageView?.image = placeholderImage
configurableAvatarButton?.setImage(placeholderImage, for: .normal)
return
}
if let avatarImageView = configurableAvatarImageView {
// set avatar (GIF using Kingfisher)
switch avatarImageURL.pathExtension {
case "gif":
avatarImageView.kf.setImage(
with: avatarImageURL,
placeholder: placeholderImage,
options: [
.transition(.fade(0.2))
]
)
avatarImageView.layer.masksToBounds = true
avatarImageView.layer.cornerRadius = cornerRadius
avatarImageView.layer.cornerCurve = .circular
default:
let filter = ScaledToSizeCircleFilter(size: Self.configurableAvatarImageViewSize)
avatarImageView.af.setImage(
withURL: avatarImageURL,
placeholderImage: placeholderImage,
filter: filter,
imageTransition: .crossDissolve(0.3),
runImageTransitionIfCached: false,
completion: nil
)
}
}
if let avatarButton = configurableAvatarButton {
switch avatarImageURL.pathExtension {
case "gif":
avatarButton.kf.setImage(
with: avatarImageURL,
for: .normal,
placeholder: placeholderImage,
options: [
.transition(.fade(0.2))
]
)
avatarButton.layer.masksToBounds = true
avatarButton.layer.cornerRadius = cornerRadius
avatarButton.layer.cornerCurve = .circular
default:
let filter = ScaledToSizeCircleFilter(size: Self.configurableAvatarImageViewSize)
avatarButton.af.setImage(
for: .normal,
url: avatarImageURL,
placeholderImage: placeholderImage,
filter: filter,
completion: nil
)
}
}
}
func avatarConfigurableView(_ avatarConfigurableView: AvatarConfigurableView, didFinishConfiguration configuration: AvatarConfigurableViewConfiguration) { }
}
struct AvatarConfigurableViewConfiguration {
enum BadgeAppearanceStyle {
case mini
case normal
}
struct Input {
let avatarImageURL: URL?
let placeholderImage: UIImage?
let blocked: Bool
let verified: Bool
init(avatarImageURL: URL?, placeholderImage: UIImage? = nil, blocked: Bool = false, verified: Bool = false) {
self.avatarImageURL = avatarImageURL
self.placeholderImage = placeholderImage
self.blocked = blocked
self.verified = verified
}
}
let input: Input
}

View File

@ -0,0 +1,49 @@
//
// AvatarBarButtonItem.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-2-4.
//
import UIKit
final class AvatarBarButtonItem: UIBarButtonItem {
static let avatarButtonSize = CGSize(width: 32, height: 32)
let avatarButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: avatarButtonSize.width).priority(.defaultHigh),
button.heightAnchor.constraint(equalToConstant: avatarButtonSize.height).priority(.defaultHigh),
])
return button
}()
override init() {
super.init()
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension AvatarBarButtonItem {
private func _init() {
customView = avatarButton
}
}
extension AvatarBarButtonItem: AvatarConfigurableView {
static var configurableAvatarImageViewSize: CGSize { return avatarButtonSize }
var configurableAvatarImageView: UIImageView? { return nil }
var configurableAvatarButton: UIButton? { return avatarButton }
var configurableVerifiedBadgeImageView: UIImageView? { return nil }
}

View File

@ -43,13 +43,14 @@ arch -x86_64 pod install
## Acknowledgements
- [ActiveLabel](https://github.com/optonaut/ActiveLabel.swift)
- [ActiveLabel](https://github.com/TwidereProject/ActiveLabel.swift)
- [AlamofireImage](https://github.com/Alamofire/AlamofireImage)
- [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator)
- [Alamofire](https://github.com/Alamofire/Alamofire)
- [CommonOSLog](https://github.com/mainasuk/CommonOSLog)
- [DateToolSwift](https://github.com/MatthewYork/DateTools)
- [Kanna](https://github.com/tid-kijyun/Kanna)
- [Kingfisher](https://github.com/onevcat/Kingfisher)
- [SwiftGen](https://github.com/SwiftGen/SwiftGen)
- [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)