mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'mkirk/color-picker'
This commit is contained in:
commit
6f9c99f99e
|
@ -429,6 +429,7 @@
|
|||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
|
||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
|
||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; };
|
||||
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */; };
|
||||
4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
|
||||
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
|
||||
|
@ -1115,6 +1116,7 @@
|
|||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
||||
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
|
||||
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
|
||||
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = "<group>"; };
|
||||
4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTableViewCell.swift; sourceTree = "<group>"; };
|
||||
4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParamParserTest.swift; sourceTree = "<group>"; };
|
||||
4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = "<group>"; };
|
||||
|
@ -1972,6 +1974,7 @@
|
|||
34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */,
|
||||
34AC09BF211B39AE00997B47 /* ViewControllerUtils.h */,
|
||||
34AC09D1211B39B000997B47 /* ViewControllerUtils.m */,
|
||||
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3219,6 +3222,7 @@
|
|||
346129C91FD2072E00532771 /* NSString+OWS.m in Sources */,
|
||||
347850691FD9B78A007B8332 /* AppSetup.m in Sources */,
|
||||
346941A3215D2EE400B5BFAD /* Theme.m in Sources */,
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */,
|
||||
34AC0A14211B39EA00997B47 /* ContactCellView.m in Sources */,
|
||||
34AC0A15211B39EA00997B47 /* ContactsViewHelper.m in Sources */,
|
||||
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */,
|
||||
|
|
|
@ -4,138 +4,184 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
let colorSwatchHeight: CGFloat = 40
|
||||
protocol ColorViewDelegate: class {
|
||||
func colorViewWasTapped(_ colorView: ColorView)
|
||||
}
|
||||
|
||||
class ColorView: UIView {
|
||||
let color: UIColor
|
||||
let swatchView: UIView
|
||||
public weak var delegate: ColorViewDelegate?
|
||||
public let conversationColor: OWSConversationColor
|
||||
|
||||
required init(color: UIColor) {
|
||||
self.color = color
|
||||
private let swatchView: UIView
|
||||
private let selectedRing: UIView
|
||||
public var isSelected: Bool = false {
|
||||
didSet {
|
||||
self.selectedRing.isHidden = !isSelected
|
||||
}
|
||||
}
|
||||
|
||||
required init(conversationColor: OWSConversationColor) {
|
||||
self.conversationColor = conversationColor
|
||||
self.swatchView = UIView()
|
||||
self.selectedRing = UIView()
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
swatchView.backgroundColor = color
|
||||
|
||||
self.swatchView.layer.cornerRadius = colorSwatchHeight / 2
|
||||
|
||||
self.addSubview(selectedRing)
|
||||
self.addSubview(swatchView)
|
||||
|
||||
swatchView.autoVCenterInSuperview()
|
||||
swatchView.autoSetDimension(.height, toSize: colorSwatchHeight)
|
||||
swatchView.autoPinEdge(toSuperviewMargin: .top, relation: .greaterThanOrEqual)
|
||||
swatchView.autoPinEdge(toSuperviewMargin: .bottom, relation: .greaterThanOrEqual)
|
||||
swatchView.autoPinLeadingToSuperviewMargin()
|
||||
swatchView.autoPinTrailingToSuperviewMargin()
|
||||
let cellHeight: CGFloat = 64
|
||||
|
||||
selectedRing.autoSetDimensions(to: CGSize(width: cellHeight, height: cellHeight))
|
||||
selectedRing.layer.cornerRadius = cellHeight / 2
|
||||
selectedRing.layer.borderColor = Theme.secondaryColor.cgColor
|
||||
selectedRing.layer.borderWidth = 2
|
||||
selectedRing.autoPinEdgesToSuperviewEdges()
|
||||
selectedRing.isHidden = true
|
||||
|
||||
swatchView.backgroundColor = conversationColor.primaryColor
|
||||
let swatchSize: CGFloat = 48
|
||||
self.swatchView.layer.cornerRadius = swatchSize / 2
|
||||
swatchView.autoSetDimensions(to: CGSize(width: swatchSize, height: swatchSize))
|
||||
swatchView.autoCenterInSuperview()
|
||||
|
||||
// gestures
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
|
||||
self.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@objc
|
||||
func didTap() {
|
||||
delegate?.colorViewWasTapped(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
protocol ColorPickerDelegate: class {
|
||||
func colorPickerDidCancel(_ colorPicker: ColorPickerViewController)
|
||||
func colorPicker(_ colorPicker: ColorPickerViewController, didPickColorName colorName: String)
|
||||
func colorPicker(_ colorPicker: ColorPicker, didPickConversationColor conversationColor: OWSConversationColor)
|
||||
}
|
||||
|
||||
@objc
|
||||
class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
private let pickerView: UIPickerView
|
||||
private let thread: TSThread
|
||||
private let colorNames: [String]
|
||||
|
||||
@objc public weak var delegate: ColorPickerDelegate?
|
||||
@objc(OWSColorPicker)
|
||||
class ColorPicker: NSObject, ColorPickerViewDelegate {
|
||||
|
||||
@objc
|
||||
required init(thread: TSThread) {
|
||||
self.thread = thread
|
||||
self.pickerView = UIPickerView()
|
||||
self.colorNames = OWSConversationColor.conversationColorNames
|
||||
public weak var delegate: ColorPickerDelegate?
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
@objc
|
||||
let sheetViewController: SheetViewController
|
||||
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(didTapCancel))
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(didTapSave))
|
||||
private let currentConversationColor: OWSConversationColor
|
||||
|
||||
pickerView.dataSource = self
|
||||
pickerView.delegate = self
|
||||
@objc
|
||||
init(currentConversationColor: OWSConversationColor) {
|
||||
self.currentConversationColor = currentConversationColor
|
||||
sheetViewController = SheetViewController()
|
||||
|
||||
super.init()
|
||||
|
||||
let colorPickerView = ColorPickerView()
|
||||
colorPickerView.delegate = self
|
||||
colorPickerView.select(conversationColor: currentConversationColor)
|
||||
sheetViewController.contentView.addSubview(colorPickerView)
|
||||
colorPickerView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
// MARK: ColorPickerViewDelegate
|
||||
|
||||
func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor) {
|
||||
self.delegate?.colorPicker(self, didPickConversationColor: conversationColor)
|
||||
}
|
||||
}
|
||||
|
||||
protocol ColorPickerViewDelegate: class {
|
||||
func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor)
|
||||
}
|
||||
|
||||
class ColorPickerView: UIView, ColorViewDelegate {
|
||||
|
||||
private let colorViews: [ColorView]
|
||||
weak var delegate: ColorPickerViewDelegate?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
let allConversationColors = OWSConversationColor.conversationColorNames.map { OWSConversationColor.conversationColorOrDefault(colorName: $0) }
|
||||
|
||||
self.colorViews = allConversationColors.map { ColorView(conversationColor: $0) }
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
colorViews.forEach { $0.delegate = self }
|
||||
|
||||
let headerView = self.buildHeaderView()
|
||||
let paletteView = self.buildPaletteView(colorViews: colorViews)
|
||||
|
||||
let rowsStackView = UIStackView(arrangedSubviews: [headerView, paletteView])
|
||||
rowsStackView.axis = .vertical
|
||||
addSubview(rowsStackView)
|
||||
rowsStackView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
self.view = UIView()
|
||||
view.backgroundColor = Theme.backgroundColor
|
||||
view.addSubview(pickerView)
|
||||
// MARK: ColorViewDelegate
|
||||
|
||||
pickerView.autoVCenterInSuperview()
|
||||
pickerView.autoPinLeadingToSuperviewMargin()
|
||||
pickerView.autoPinTrailingToSuperviewMargin()
|
||||
func colorViewWasTapped(_ colorView: ColorView) {
|
||||
self.select(conversationColor: colorView.conversationColor)
|
||||
self.delegate?.colorPickerView(self, didPickConversationColor: colorView.conversationColor)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let colorName = thread.conversationColorName
|
||||
if let index = colorNames.index(of: colorName) {
|
||||
pickerView.selectRow(index, inComponent: 0, animated: false)
|
||||
fileprivate func select(conversationColor selectedConversationColor: OWSConversationColor) {
|
||||
colorViews.forEach { colorView in
|
||||
colorView.isSelected = colorView.conversationColor == selectedConversationColor
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UIPickerViewDataSource
|
||||
// MARK: View Building
|
||||
|
||||
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
private func buildHeaderView() -> UIView {
|
||||
let headerView = UIView()
|
||||
headerView.layoutMargins = UIEdgeInsets(top: 15, left: 16, bottom: 15, right: 16)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = NSLocalizedString("COLOR_PICKER_SHEET_TITLE", comment: "Modal Sheet title when picking a conversation color.")
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
|
||||
titleLabel.textColor = Theme.primaryColor
|
||||
|
||||
headerView.addSubview(titleLabel)
|
||||
titleLabel.ows_autoPinToSuperviewMargins()
|
||||
|
||||
let bottomBorderView = UIView()
|
||||
bottomBorderView.backgroundColor = Theme.hairlineColor
|
||||
headerView.addSubview(bottomBorderView)
|
||||
bottomBorderView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
|
||||
bottomBorderView.autoSetDimension(.height, toSize: CGHairlineWidth())
|
||||
|
||||
return headerView
|
||||
}
|
||||
|
||||
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
return self.colorNames.count
|
||||
}
|
||||
private func buildPaletteView(colorViews: [ColorView]) -> UIView {
|
||||
let paletteView = UIView()
|
||||
paletteView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
|
||||
|
||||
// MARK: UIPickerViewDelegate
|
||||
|
||||
public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
|
||||
let vMargin: CGFloat = 16
|
||||
return colorSwatchHeight + vMargin * 2
|
||||
}
|
||||
|
||||
public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
|
||||
guard let colorName = colorNames[safe: row] else {
|
||||
owsFailDebug("color was unexpectedly nil")
|
||||
return ColorView(color: .white)
|
||||
let kRowLength = 4
|
||||
let rows: [UIView] = colorViews.chunked(by: kRowLength).map { colorViewsInRow in
|
||||
let row = UIStackView(arrangedSubviews: colorViewsInRow)
|
||||
row.distribution = UIStackViewDistribution.equalSpacing
|
||||
return row
|
||||
}
|
||||
guard let colors = OWSConversationColor.conversationColor(colorName: colorName) else {
|
||||
owsFailDebug("unknown color name")
|
||||
return ColorView(color: OWSConversationColor.default().themeColor)
|
||||
}
|
||||
return ColorView(color: colors.themeColor)
|
||||
}
|
||||
let rowsStackView = UIStackView(arrangedSubviews: rows)
|
||||
rowsStackView.axis = .vertical
|
||||
rowsStackView.spacing = ScaleFromIPhone5To7Plus(16, 50)
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
var currentColorName: String {
|
||||
let index = pickerView.selectedRow(inComponent: 0)
|
||||
guard let colorName = colorNames[safe: index] else {
|
||||
owsFailDebug("index was unexpectedly nil")
|
||||
return OWSConversationColor.defaultConversationColorName()
|
||||
}
|
||||
return colorName
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didTapSave() {
|
||||
let colorName = self.currentColorName
|
||||
self.delegate?.colorPicker(self, didPickColorName: colorName)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didTapCancel() {
|
||||
self.delegate?.colorPickerDidCancel(self)
|
||||
paletteView.addSubview(rowsStackView)
|
||||
rowsStackView.ows_autoPinToSuperviewMargins()
|
||||
return paletteView
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#import <SignalMessaging/OWSProfileManager.h>
|
||||
#import <SignalMessaging/OWSSounds.h>
|
||||
#import <SignalMessaging/OWSUserProfile.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalMessaging/UIUtil.h>
|
||||
#import <SignalServiceKit/NSDate+OWS.h>
|
||||
#import <SignalServiceKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
|
||||
|
@ -41,7 +42,8 @@ const CGFloat kIconViewLength = 24;
|
|||
|
||||
@interface OWSConversationSettingsViewController () <ContactEditingDelegate,
|
||||
ContactsViewHelperDelegate,
|
||||
ColorPickerDelegate>
|
||||
ColorPickerDelegate,
|
||||
OWSSheetViewControllerDelegate>
|
||||
|
||||
@property (nonatomic) TSThread *thread;
|
||||
@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection;
|
||||
|
@ -57,6 +59,7 @@ const CGFloat kIconViewLength = 24;
|
|||
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
|
||||
@property (nonatomic, readonly) UIImageView *avatarView;
|
||||
@property (nonatomic, readonly) UILabel *disappearingMessagesDurationLabel;
|
||||
@property (nonatomic) OWSColorPicker *colorPicker;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -247,6 +250,11 @@ const CGFloat kIconViewLength = 24;
|
|||
[[OWSDisappearingMessagesConfiguration alloc] initDefaultWithThreadId:self.thread.uniqueId];
|
||||
}
|
||||
|
||||
NSString *colorName = self.thread.conversationColorName;
|
||||
OWSConversationColor *currentConversationColor = [OWSConversationColor conversationColorOrDefaultForColorName:colorName];
|
||||
self.colorPicker = [[OWSColorPicker alloc] initWithCurrentConversationColor:currentConversationColor];
|
||||
self.colorPicker.delegate = self;
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
|
@ -1299,18 +1307,22 @@ const CGFloat kIconViewLength = 24;
|
|||
|
||||
- (void)showColorPicker
|
||||
{
|
||||
ColorPickerViewController *pickerController = [[ColorPickerViewController alloc] initWithThread:self.thread];
|
||||
pickerController.delegate = self;
|
||||
OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:pickerController];
|
||||
OWSSheetViewController *sheetViewController = self.colorPicker.sheetViewController;
|
||||
sheetViewController.delegate = self;
|
||||
|
||||
[self presentViewController:modal animated:YES completion:nil];
|
||||
[self presentViewController:sheetViewController
|
||||
animated:YES
|
||||
completion:^() {
|
||||
OWSLogInfo(@"presented sheet view");
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)colorPicker:(ColorPickerViewController *)colorPicker didPickColorName:(NSString *)colorName
|
||||
- (void)colorPicker:(OWSColorPicker *)colorPicker
|
||||
didPickConversationColor:(OWSConversationColor *_Nonnull)conversationColor
|
||||
{
|
||||
OWSLogDebug(@"picked color: %@", colorName);
|
||||
OWSLogDebug(@"picked color: %@", conversationColor.name);
|
||||
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[self.thread updateConversationColorName:colorName transaction:transaction];
|
||||
[self.thread updateConversationColorName:conversationColor.name transaction:transaction];
|
||||
}];
|
||||
|
||||
[self.contactsManager.avatarCache removeAllImages];
|
||||
|
@ -1323,11 +1335,11 @@ const CGFloat kIconViewLength = 24;
|
|||
OWSAssertDebug(operation.isReady);
|
||||
[operation start];
|
||||
});
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)colorPickerDidCancel:(ColorPickerViewController *)colorPicker
|
||||
#pragma mark - OWSSheetViewController
|
||||
|
||||
- (void)sheetViewControllerRequestedDismiss:(OWSSheetViewController *)sheetViewController
|
||||
{
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
|
|
@ -386,6 +386,9 @@
|
|||
/* Error indicating that the app was prevented from accessing the user's CloudKit account. */
|
||||
"CLOUDKIT_STATUS_RESTRICTED" = "Signal was not allowed to access your iCloud account for backups.";
|
||||
|
||||
/* Modal Sheet title when picking a conversation color. */
|
||||
"COLOR_PICKER_SHEET_TITLE" = "Conversation Color";
|
||||
|
||||
/* Activity Sheet label */
|
||||
"COMPARE_SAFETY_NUMBER_ACTION" = "Compare with Clipboard";
|
||||
|
||||
|
|
213
SignalMessaging/ViewControllers/SheetViewController.swift
Normal file
213
SignalMessaging/ViewControllers/SheetViewController.swift
Normal file
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc(OWSSheetViewControllerDelegate)
|
||||
public protocol SheetViewControllerDelegate: class {
|
||||
func sheetViewControllerRequestedDismiss(_ sheetViewController: SheetViewController)
|
||||
}
|
||||
|
||||
@objc(OWSSheetViewController)
|
||||
public class SheetViewController: UIViewController {
|
||||
|
||||
@objc
|
||||
weak var delegate: SheetViewControllerDelegate?
|
||||
|
||||
@objc
|
||||
public let contentView: UIView = UIView()
|
||||
|
||||
private let sheetView: SheetView = SheetView()
|
||||
private let handleView: UIView = UIView()
|
||||
|
||||
deinit {
|
||||
Logger.verbose("")
|
||||
}
|
||||
|
||||
@objc
|
||||
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
self.transitioningDelegate = self
|
||||
self.modalPresentationStyle = .overCurrentContext
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: View LifeCycle
|
||||
|
||||
var sheetViewVerticalConstraint: NSLayoutConstraint?
|
||||
|
||||
override public func loadView() {
|
||||
self.view = UIView()
|
||||
|
||||
sheetView.preservesSuperviewLayoutMargins = true
|
||||
|
||||
sheetView.addSubview(contentView)
|
||||
contentView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
|
||||
contentView.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
|
||||
view.addSubview(sheetView)
|
||||
sheetView.autoPinWidthToSuperview()
|
||||
sheetView.setContentHuggingVerticalHigh()
|
||||
sheetView.setCompressionResistanceHigh()
|
||||
self.sheetViewVerticalConstraint = sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
|
||||
|
||||
handleView.backgroundColor = Theme.backgroundColor
|
||||
let kHandleViewHeight: CGFloat = 5
|
||||
handleView.autoSetDimensions(to: CGSize(width: 40, height: kHandleViewHeight))
|
||||
handleView.layer.cornerRadius = kHandleViewHeight / 2
|
||||
view.addSubview(handleView)
|
||||
handleView.autoAlignAxis(.vertical, toSameAxisOf: sheetView)
|
||||
handleView.autoPinEdge(.bottom, to: .top, of: sheetView, withOffset: -6)
|
||||
|
||||
// Gestures
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground))
|
||||
self.view.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
// MARK: Present / Dismiss animations
|
||||
|
||||
fileprivate func animatePresentation(completion: @escaping (Bool) -> Void) {
|
||||
guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
|
||||
owsFailDebug("sheetViewVerticalConstraint was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
let backgroundDuration: TimeInterval = 0.1
|
||||
UIView.animate(withDuration: backgroundDuration) {
|
||||
let alpha: CGFloat = Theme.isDarkThemeEnabled ? 0.7 : 0.6
|
||||
self.view.backgroundColor = UIColor.black.withAlphaComponent(alpha)
|
||||
}
|
||||
|
||||
self.sheetView.superview?.layoutIfNeeded()
|
||||
|
||||
NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
|
||||
self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
UIView.animate(withDuration: 0.2,
|
||||
delay: backgroundDuration,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
self.sheetView.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
fileprivate func animateDismiss(completion: @escaping (Bool) -> Void) {
|
||||
guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
|
||||
owsFailDebug("sheetVerticalConstraint was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
self.sheetView.superview?.layoutIfNeeded()
|
||||
NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
|
||||
|
||||
let dismissDuration: TimeInterval = 0.2
|
||||
self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
|
||||
UIView.animate(withDuration: dismissDuration,
|
||||
delay: 0,
|
||||
options: .curveEaseOut,
|
||||
animations: {
|
||||
self.view.backgroundColor = UIColor.clear
|
||||
self.sheetView.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@objc
|
||||
func didTapBackground() {
|
||||
// inform delegate to
|
||||
delegate?.sheetViewControllerRequestedDismiss(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension SheetViewController: UIViewControllerTransitioningDelegate {
|
||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SheetViewPresentationController(sheetViewController: self)
|
||||
}
|
||||
|
||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return SheetViewDismissalController(sheetViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
private class SheetViewPresentationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
let sheetViewController: SheetViewController
|
||||
init(sheetViewController: SheetViewController) {
|
||||
self.sheetViewController = sheetViewController
|
||||
}
|
||||
|
||||
// This is used for percent driven interactive transitions, as well as for
|
||||
// container controllers that have companion animations that might need to
|
||||
// synchronize with the main animation.
|
||||
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.3
|
||||
}
|
||||
|
||||
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
|
||||
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
Logger.debug("")
|
||||
transitionContext.containerView.addSubview(sheetViewController.view)
|
||||
sheetViewController.view.autoPinEdgesToSuperviewEdges()
|
||||
sheetViewController.animatePresentation { didComplete in
|
||||
Logger.debug("completed: \(didComplete)")
|
||||
transitionContext.completeTransition(didComplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SheetViewDismissalController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
let sheetViewController: SheetViewController
|
||||
init(sheetViewController: SheetViewController) {
|
||||
self.sheetViewController = sheetViewController
|
||||
}
|
||||
|
||||
// This is used for percent driven interactive transitions, as well as for
|
||||
// container controllers that have companion animations that might need to
|
||||
// synchronize with the main animation.
|
||||
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return 0.3
|
||||
}
|
||||
|
||||
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
|
||||
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
Logger.debug("")
|
||||
sheetViewController.animateDismiss { didComplete in
|
||||
Logger.debug("completed: \(didComplete)")
|
||||
transitionContext.completeTransition(didComplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SheetView: UIView {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_gray90
|
||||
: UIColor.ows_gray05
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
override var bounds: CGRect {
|
||||
didSet {
|
||||
updateMask()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateMask() {
|
||||
let cornerRadius: CGFloat = 16
|
||||
let path: UIBezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
|
||||
let mask = CAShapeLayer()
|
||||
mask.path = path.cgPath
|
||||
self.layer.mask = mask
|
||||
}
|
||||
}
|
|
@ -8,16 +8,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface OWSConversationColor : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@property (nonatomic, readonly) UIColor *primaryColor;
|
||||
@property (nonatomic, readonly) UIColor *shadeColor;
|
||||
@property (nonatomic, readonly) UIColor *tintColor;
|
||||
|
||||
@property (nonatomic, readonly) UIColor *themeColor;
|
||||
|
||||
+ (OWSConversationColor *)conversationColorWithPrimaryColor:(UIColor *)primaryColor
|
||||
shadeColor:(UIColor *)shadeColor
|
||||
tintColor:(UIColor *)tintColor;
|
||||
|
||||
+ (OWSConversationColor *)conversationColorWithName:(NSString *)name
|
||||
primaryColor:(UIColor *)primaryColor
|
||||
shadeColor:(UIColor *)shadeColor
|
||||
tintColor:(UIColor *)tintColor;
|
||||
#pragma mark - Conversation Colors
|
||||
|
||||
@property (class, readonly, nonatomic) UIColor *ows_crimsonColor;
|
||||
|
|
|
@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface OWSConversationColor ()
|
||||
|
||||
@property (nonatomic) NSString *name;
|
||||
@property (nonatomic) UIColor *primaryColor;
|
||||
@property (nonatomic) UIColor *shadeColor;
|
||||
@property (nonatomic) UIColor *tintColor;
|
||||
|
@ -20,23 +21,37 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@implementation OWSConversationColor
|
||||
|
||||
+ (OWSConversationColor *)conversationColorWithPrimaryColor:(UIColor *)primaryColor
|
||||
shadeColor:(UIColor *)shadeColor
|
||||
tintColor:(UIColor *)tintColor
|
||||
+ (OWSConversationColor *)conversationColorWithName:(NSString *)name
|
||||
primaryColor:(UIColor *)primaryColor
|
||||
shadeColor:(UIColor *)shadeColor
|
||||
tintColor:(UIColor *)tintColor
|
||||
{
|
||||
OWSConversationColor *instance = [OWSConversationColor new];
|
||||
instance.name = name;
|
||||
instance.primaryColor = primaryColor;
|
||||
instance.shadeColor = shadeColor;
|
||||
instance.tintColor = tintColor;
|
||||
return instance;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (UIColor *)themeColor
|
||||
{
|
||||
return Theme.isDarkThemeEnabled ? self.shadeColor : self.primaryColor;
|
||||
}
|
||||
|
||||
#pragma mark - Conversation Colors
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if (![other isKindOfClass:[OWSConversationColor class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
OWSConversationColor *otherColor = (OWSConversationColor *)other;
|
||||
return [self.name isEqual:otherColor.name];
|
||||
}
|
||||
|
||||
#pragma mark - Conversation Color (Primary)
|
||||
|
||||
+ (UIColor *)ows_crimsonColor
|
||||
{
|
||||
|
@ -222,75 +237,78 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [UIColor colorWithRGBHex:0x5A5A63];
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, UIColor *> *)conversationColorMap
|
||||
+ (NSArray<OWSConversationColor *> *)allConversationColors
|
||||
{
|
||||
static NSDictionary<NSString *, UIColor *> *colorMap;
|
||||
static NSArray<OWSConversationColor *> *allConversationColors;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorMap = @{
|
||||
@"crimson" : self.ows_crimsonColor,
|
||||
@"vermilion" : self.ows_vermilionColor,
|
||||
@"burlap" : self.ows_burlapColor,
|
||||
@"forest" : self.ows_forestColor,
|
||||
@"wintergreen" : self.ows_wintergreenColor,
|
||||
@"teal" : self.ows_tealColor,
|
||||
@"blue" : self.ows_blueColor,
|
||||
@"indigo" : self.ows_indigoColor,
|
||||
@"violet" : self.ows_violetColor,
|
||||
@"plum" : self.ows_plumColor,
|
||||
@"taupe" : self.ows_taupeColor,
|
||||
@"steel" : self.ows_steelColor,
|
||||
};
|
||||
// Order here affects the order in the conversation color picker.
|
||||
allConversationColors = @[
|
||||
[OWSConversationColor conversationColorWithName:@"crimson"
|
||||
primaryColor:self.ows_crimsonColor
|
||||
shadeColor:self.ows_crimsonShadeColor
|
||||
tintColor:self.ows_crimsonTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"vermilion"
|
||||
primaryColor:self.ows_vermilionColor
|
||||
shadeColor:self.ows_vermilionShadeColor
|
||||
tintColor:self.ows_vermilionTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"burlap"
|
||||
primaryColor:self.ows_burlapColor
|
||||
shadeColor:self.ows_burlapShadeColor
|
||||
tintColor:self.ows_burlapTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"forest"
|
||||
primaryColor:self.ows_forestColor
|
||||
shadeColor:self.ows_forestShadeColor
|
||||
tintColor:self.ows_forestTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"wintergreen"
|
||||
primaryColor:self.ows_wintergreenColor
|
||||
shadeColor:self.ows_wintergreenShadeColor
|
||||
tintColor:self.ows_wintergreenTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"teal"
|
||||
primaryColor:self.ows_tealColor
|
||||
shadeColor:self.ows_tealShadeColor
|
||||
tintColor:self.ows_tealTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"blue"
|
||||
primaryColor:self.ows_blueColor
|
||||
shadeColor:self.ows_blueShadeColor
|
||||
tintColor:self.ows_blueTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"indigo"
|
||||
primaryColor:self.ows_indigoColor
|
||||
shadeColor:self.ows_indigoShadeColor
|
||||
tintColor:self.ows_indigoTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"violet"
|
||||
primaryColor:self.ows_violetColor
|
||||
shadeColor:self.ows_violetShadeColor
|
||||
tintColor:self.ows_violetTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"plum"
|
||||
primaryColor:self.ows_plumColor
|
||||
shadeColor:self.ows_plumShadeColor
|
||||
tintColor:self.ows_plumTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"taupe"
|
||||
primaryColor:self.ows_taupeColor
|
||||
shadeColor:self.ows_taupeShadeColor
|
||||
tintColor:self.ows_taupeTintColor],
|
||||
[OWSConversationColor conversationColorWithName:@"steel"
|
||||
primaryColor:self.ows_steelColor
|
||||
shadeColor:self.ows_steelShadeColor
|
||||
tintColor:self.ows_steelTintColor],
|
||||
];
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
return allConversationColors;
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, UIColor *> *)conversationColorMapShade
|
||||
+ (NSDictionary<NSString *, OWSConversationColor *> *)conversationColorMap
|
||||
{
|
||||
static NSDictionary<NSString *, UIColor *> *colorMap;
|
||||
static NSDictionary<NSString *, OWSConversationColor *> *colorMap;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorMap = @{
|
||||
@"crimson" : self.ows_crimsonShadeColor,
|
||||
@"vermilion" : self.ows_vermilionShadeColor,
|
||||
@"burlap" : self.ows_burlapShadeColor,
|
||||
@"forest" : self.ows_forestShadeColor,
|
||||
@"wintergreen" : self.ows_wintergreenShadeColor,
|
||||
@"teal" : self.ows_tealShadeColor,
|
||||
@"blue" : self.ows_blueShadeColor,
|
||||
@"indigo" : self.ows_indigoShadeColor,
|
||||
@"violet" : self.ows_violetShadeColor,
|
||||
@"plum" : self.ows_plumShadeColor,
|
||||
@"taupe" : self.ows_taupeShadeColor,
|
||||
@"steel" : self.ows_steelShadeColor,
|
||||
};
|
||||
OWSAssertDebug([self.conversationColorMap.allKeys isEqualToArray:colorMap.allKeys]);
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, UIColor *> *)conversationColorMapTint
|
||||
{
|
||||
static NSDictionary<NSString *, UIColor *> *colorMap;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorMap = @{
|
||||
@"crimson" : self.ows_crimsonTintColor,
|
||||
@"vermilion" : self.ows_vermilionTintColor,
|
||||
@"burlap" : self.ows_burlapTintColor,
|
||||
@"forest" : self.ows_forestTintColor,
|
||||
@"wintergreen" : self.ows_wintergreenTintColor,
|
||||
@"teal" : self.ows_tealTintColor,
|
||||
@"blue" : self.ows_blueTintColor,
|
||||
@"indigo" : self.ows_indigoTintColor,
|
||||
@"violet" : self.ows_violetTintColor,
|
||||
@"plum" : self.ows_plumTintColor,
|
||||
@"taupe" : self.ows_taupeTintColor,
|
||||
@"steel" : self.ows_steelTintColor,
|
||||
};
|
||||
OWSAssertDebug([self.conversationColorMap.allKeys isEqualToArray:colorMap.allKeys]);
|
||||
NSMutableDictionary<NSString *, OWSConversationColor *> *mutableColorMap = [NSMutableDictionary new];
|
||||
for (OWSConversationColor *conversationColor in self.allConversationColors) {
|
||||
mutableColorMap[conversationColor.name] = conversationColor;
|
||||
}
|
||||
colorMap = [mutableColorMap copy];
|
||||
});
|
||||
|
||||
return colorMap;
|
||||
|
@ -329,7 +347,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
+ (NSArray<NSString *> *)conversationColorNames
|
||||
{
|
||||
return self.conversationColorMap.allKeys;
|
||||
NSMutableArray<NSString *> *names = [NSMutableArray new];
|
||||
for (OWSConversationColor *conversationColor in self.allConversationColors) {
|
||||
[names addObject:conversationColor.name];
|
||||
}
|
||||
return [names copy];
|
||||
}
|
||||
|
||||
+ (nullable OWSConversationColor *)conversationColorForColorName:(NSString *)conversationColorName
|
||||
|
@ -341,17 +363,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(self.conversationColorMap[conversationColorName] != nil);
|
||||
}
|
||||
|
||||
UIColor *_Nullable primaryColor = self.conversationColorMap[conversationColorName];
|
||||
UIColor *_Nullable shadeColor = self.conversationColorMapShade[conversationColorName];
|
||||
UIColor *_Nullable tintColor = self.conversationColorMapTint[conversationColorName];
|
||||
if (!primaryColor || !shadeColor || !tintColor) {
|
||||
return nil;
|
||||
}
|
||||
OWSAssertDebug(primaryColor);
|
||||
OWSAssertDebug(shadeColor);
|
||||
OWSAssertDebug(tintColor);
|
||||
return
|
||||
[OWSConversationColor conversationColorWithPrimaryColor:primaryColor shadeColor:shadeColor tintColor:tintColor];
|
||||
return self.conversationColorMap[conversationColorName];
|
||||
}
|
||||
|
||||
+ (OWSConversationColor *)conversationColorOrDefaultForColorName:(NSString *)conversationColorName
|
||||
|
|
|
@ -9,3 +9,11 @@ public extension Collection {
|
|||
return indices.contains(index) ? self[index] : nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array {
|
||||
func chunked(by chunkSize: Int) -> [[Element]] {
|
||||
return stride(from: 0, to: self.count, by: chunkSize).map {
|
||||
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMath.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "OWSMath.h"
|
||||
#import "Theme.h"
|
||||
#import <SignalServiceKit/Cryptography.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
|
Loading…
Reference in a new issue