Signal-iOS/SignalMessaging/ViewControllers/Stickers/ManageStickersViewController.swift
2019-05-22 16:03:07 -04:00

399 lines
15 KiB
Swift

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
private class StickerPackActionButton: UIView {
private let block: () -> Void
@available(*, unavailable, message:"use other constructor instead.")
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
init(actionIconName: String, block: @escaping () -> Void) {
self.block = block
super.init(frame: .zero)
configure(actionIconName: actionIconName)
}
private func configure(actionIconName: String) {
let actionIconSize: CGFloat = 20
let actionCircleSize: CGFloat = 32
let actionCircleView = CircleView(diameter: actionCircleSize)
actionCircleView.backgroundColor = Theme.offBackgroundColor
let actionIcon = UIImage(named: actionIconName)?.withRenderingMode(.alwaysTemplate)
let actionIconView = UIImageView(image: actionIcon)
actionIconView.tintColor = Theme.secondaryColor
actionCircleView.addSubview(actionIconView)
actionIconView.autoCenterInSuperview()
actionIconView.autoSetDimensions(to: CGSize(width: actionIconSize, height: actionIconSize))
self.addSubview(actionCircleView)
actionCircleView.autoPinEdgesToSuperviewEdges()
isUserInteractionEnabled = true
addGestureRecognizer(UITapGestureRecognizer(target: self,
action: #selector(didTapButton)))
}
@objc
func didTapButton(sender: UIGestureRecognizer) {
block()
}
}
// MARK: -
@objc
public class ManageStickersViewController: OWSTableViewController {
// MARK: - Dependencies
private var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
private var stickerManager: StickerManager {
return SSKEnvironment.shared.stickerManager
}
// MARK: Initializers
@available(*, unavailable, message:"use other constructor instead.")
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
@objc
public required override init() {
super.init()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - View Lifecycle
override public func loadView() {
super.loadView()
navigationItem.title = NSLocalizedString("STICKERS_MANAGE_VIEW_TITLE", comment: "Title for the 'manage stickers' view.")
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(didPressDismiss))
if FeatureFlags.stickerPackOrdering {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(didPressEditButton))
}
}
override public func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(stickersOrPacksDidChange),
name: StickerManager.StickersOrPacksDidChange,
object: nil)
updateState()
StickerManager.refreshContents()
}
private var installedStickerPacks = [StickerPack]()
private var availableBuiltInStickerPacks = [StickerPack]()
private var availableKnownStickerPacks = [StickerPack]()
private func updateState() {
self.databaseStorage.read { (transaction) in
let allPacks = StickerManager.allStickerPacks(transaction: transaction)
// Only show packs with installed covers.
let packsWithCovers = allPacks.filter {
StickerManager.isStickerInstalled(stickerInfo: $0.coverInfo,
transaction: transaction)
}
// Sort sticker packs by "date saved, descending" so that we feature
// packs that the user has just learned about.
let installedStickerPacks = packsWithCovers.filter { $0.isInstalled }
let availableBuiltInStickerPacks = packsWithCovers.filter { !$0.isInstalled && StickerManager.isDefaultStickerPack($0) }
let availableKnownStickerPacks = packsWithCovers.filter { !$0.isInstalled && !StickerManager.isDefaultStickerPack($0) }
self.installedStickerPacks = installedStickerPacks.sorted {
$0.dateCreated > $1.dateCreated
}
let sortAvailablePacks = { (pack0: StickerPack, pack1: StickerPack) -> Bool in
// Sort "default" packs before "known" packs.
let isDefault0 = StickerManager.isDefaultStickerPack(pack0)
let isDefault1 = StickerManager.isDefaultStickerPack(pack1)
if isDefault0 && !isDefault1 {
return true
}
if !isDefault0 && isDefault1 {
return false
}
return pack0.dateCreated > pack1.dateCreated
}
self.availableBuiltInStickerPacks = availableBuiltInStickerPacks.sorted(by: sortAvailablePacks)
self.availableKnownStickerPacks = availableKnownStickerPacks.sorted(by: sortAvailablePacks)
}
updateTableContents()
}
private func updateTableContents() {
let contents = OWSTableContents()
let installedSection = OWSTableSection()
installedSection.headerTitle = NSLocalizedString("STICKERS_MANAGE_VIEW_INSTALLED_PACKS_SECTION_TITLE", comment: "Title for the 'installed stickers' section of the 'manage stickers' view.")
if installedStickerPacks.count < 1 {
installedSection.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
return UITableViewCell()
}
let text = NSLocalizedString("STICKERS_MANAGE_VIEW_NO_INSTALLED_PACKS", comment: "Label indicating that the user has no installed sticker packs.")
return self.buildEmptySectionCell(labelText: text)
},
customRowHeight: UITableView.automaticDimension))
}
for stickerPack in installedStickerPacks {
installedSection.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
return UITableViewCell()
}
return self.buildTableCell(installedStickerPack: stickerPack)
},
customRowHeight: UITableView.automaticDimension,
actionBlock: { [weak self] in
self?.show(stickerPack: stickerPack)
}))
}
contents.addSection(installedSection)
let itemForAvailablePack = { (stickerPack: StickerPack) -> OWSTableItem in
OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
return UITableViewCell()
}
return self.buildTableCell(availableStickerPack: stickerPack)
},
customRowHeight: UITableView.automaticDimension,
actionBlock: { [weak self] in
self?.show(stickerPack: stickerPack)
})
}
if availableBuiltInStickerPacks.count > 0 {
let section = OWSTableSection()
section.headerTitle = NSLocalizedString("STICKERS_MANAGE_VIEW_AVAILABLE_BUILT_IN_PACKS_SECTION_TITLE", comment: "Title for the 'available built-in stickers' section of the 'manage stickers' view.")
for stickerPack in availableBuiltInStickerPacks {
section.add(itemForAvailablePack(stickerPack))
}
contents.addSection(section)
}
let knownSection = OWSTableSection()
knownSection.headerTitle = NSLocalizedString("STICKERS_MANAGE_VIEW_AVAILABLE_KNOWN_PACKS_SECTION_TITLE", comment: "Title for the 'available known stickers' section of the 'manage stickers' view.")
if availableKnownStickerPacks.count < 1 {
knownSection.add(OWSTableItem(customCellBlock: { [weak self] in
guard let self = self else {
return UITableViewCell()
}
let text = NSLocalizedString("STICKERS_MANAGE_VIEW_NO_KNOWN_PACKS", comment: "Label indicating that the user has no known sticker packs.")
return self.buildEmptySectionCell(labelText: text)
},
customRowHeight: UITableView.automaticDimension))
}
for stickerPack in availableKnownStickerPacks {
knownSection.add(itemForAvailablePack(stickerPack))
}
contents.addSection(knownSection)
self.contents = contents
}
private func buildTableCell(installedStickerPack stickerPack: StickerPack) -> UITableViewCell {
let actionIconName = CurrentAppContext().isRTL ? "reply-filled-24" : "reply-filled-reversed-24"
return buildTableCell(stickerPack: stickerPack,
stickerInfo: stickerPack.coverInfo,
title: stickerPack.title,
authorName: stickerPack.author,
actionIconName: actionIconName) { [weak self] in
self?.share(stickerPack: stickerPack)
}
}
private func buildTableCell(availableStickerPack stickerPack: StickerPack) -> UITableViewCell {
let actionIconName = "download-filled-24"
return buildTableCell(stickerPack: stickerPack,
stickerInfo: stickerPack.coverInfo,
title: stickerPack.title,
authorName: stickerPack.author,
actionIconName: actionIconName) { [weak self] in
self?.install(stickerPack: stickerPack)
}
}
private func buildTableCell(stickerPack: StickerPack,
stickerInfo: StickerInfo,
title titleValue: String?,
authorName authorNameValue: String?,
actionIconName: String?,
block: @escaping () -> Void) -> UITableViewCell {
let cell = OWSTableItem.newCell()
let iconView = StickerView(stickerInfo: stickerInfo, size: 64)
let title: String
if let titleValue = titleValue?.ows_stripped(),
titleValue.count > 0 {
title = titleValue
} else {
title = NSLocalizedString("STICKERS_PACK_DEFAULT_TITLE", comment: "Default title for sticker packs.")
}
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
titleLabel.textColor = Theme.primaryColor
titleLabel.lineBreakMode = .byTruncatingTail
let textStack = UIStackView(arrangedSubviews: [
titleLabel
])
textStack.axis = .vertical
textStack.alignment = .leading
textStack.spacing = 4
textStack.setContentHuggingHorizontalLow()
textStack.setCompressionResistanceHorizontalLow()
// TODO: Should we show a default author name?
let isDefaultStickerPack = StickerManager.isDefaultStickerPack(stickerPack)
var authorViews = [UIView]()
if isDefaultStickerPack {
let builtInPackView = UIImageView()
builtInPackView.setTemplateImageName("check-circle-filled-16", tintColor: UIColor.ows_signalBrandBlue)
builtInPackView.setCompressionResistanceHigh()
builtInPackView.setContentHuggingHigh()
authorViews.append(builtInPackView)
}
if let authorName = authorNameValue?.ows_stripped(),
authorName.count > 0 {
let authorLabel = UILabel()
authorLabel.text = authorName
authorLabel.font = isDefaultStickerPack ? UIFont.ows_dynamicTypeCaption1.ows_mediumWeight() : UIFont.ows_dynamicTypeCaption1
authorLabel.textColor = isDefaultStickerPack ? UIColor.ows_signalBlue : Theme.secondaryColor
authorLabel.lineBreakMode = .byTruncatingTail
authorViews.append(authorLabel)
}
if authorViews.count > 0 {
let authorStack = UIStackView(arrangedSubviews: authorViews)
authorStack.axis = .horizontal
authorStack.alignment = .center
authorStack.spacing = 4
textStack.addArrangedSubview(authorStack)
}
var subviews: [UIView] = [
iconView,
textStack
]
if let actionIconName = actionIconName {
let actionButton = StickerPackActionButton(actionIconName: actionIconName, block: block)
subviews.append(actionButton)
}
let stack = UIStackView(arrangedSubviews: subviews)
stack.axis = .horizontal
stack.alignment = .center
stack.spacing = 12
cell.contentView.addSubview(stack)
stack.ows_autoPinToSuperviewMargins()
return cell
}
private func buildEmptySectionCell(labelText: String) -> UITableViewCell {
let cell = OWSTableItem.newCell()
let bubbleView = UIView()
bubbleView.backgroundColor = Theme.offBackgroundColor
bubbleView.layer.cornerRadius = 8
let label = UILabel()
label.text = labelText
label.font = UIFont.ows_dynamicTypeCaption1
label.textColor = Theme.secondaryColor
label.textAlignment = .center
bubbleView.addSubview(label)
label.autoPinHeightToSuperview(withMargin: 24)
label.autoPinWidthToSuperview(withMargin: 16)
cell.contentView.addSubview(bubbleView)
bubbleView.ows_autoPinToSuperviewMargins()
return cell
}
// MARK: Events
private func show(stickerPack: StickerPack) {
AssertIsOnMainThread()
Logger.verbose("")
let packView = StickerPackViewController(stickerPackInfo: stickerPack.info)
present(packView, animated: true)
}
private func share(stickerPack: StickerPack) {
AssertIsOnMainThread()
Logger.verbose("")
StickerSharingViewController.shareStickerPack(stickerPack.info, from: self)
}
private func install(stickerPack: StickerPack) {
AssertIsOnMainThread()
Logger.verbose("")
StickerManager.installStickerPack(stickerPack: stickerPack)
}
@objc func stickersOrPacksDidChange() {
AssertIsOnMainThread()
Logger.verbose("")
updateState()
}
@objc
private func didPressEditButton(sender: UIButton) {
AssertIsOnMainThread()
Logger.verbose("")
// TODO:
}
@objc
private func didPressDismiss(sender: UIButton) {
AssertIsOnMainThread()
Logger.verbose("")
dismiss(animated: true)
}
}