mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Eventually, listing permissions will be mandatory so AltStore can verify that apps only require the permissions they declare. Until then, we’ll allow apps to not list their permissions.
235 lines
8.9 KiB
Swift
235 lines
8.9 KiB
Swift
//
|
|
// AppContentViewController.swift
|
|
// AltStore
|
|
//
|
|
// Created by Riley Testut on 7/22/19.
|
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
import AltStoreCore
|
|
import Roxas
|
|
|
|
import Nuke
|
|
|
|
extension AppContentViewController
|
|
{
|
|
private enum Row: Int, CaseIterable
|
|
{
|
|
case subtitle
|
|
case screenshots
|
|
case description
|
|
case versionDescription
|
|
case permissions
|
|
}
|
|
}
|
|
|
|
class AppContentViewController: UITableViewController
|
|
{
|
|
var app: StoreApp!
|
|
|
|
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
|
|
private lazy var permissionsDataSource = self.makePermissionsDataSource()
|
|
|
|
private lazy var dateFormatter: DateFormatter = {
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateStyle = .medium
|
|
dateFormatter.timeStyle = .none
|
|
return dateFormatter
|
|
}()
|
|
|
|
private lazy var byteCountFormatter: ByteCountFormatter = {
|
|
let formatter = ByteCountFormatter()
|
|
return formatter
|
|
}()
|
|
|
|
@IBOutlet private var subtitleLabel: UILabel!
|
|
@IBOutlet private var descriptionTextView: CollapsingTextView!
|
|
@IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
|
@IBOutlet private var versionLabel: UILabel!
|
|
@IBOutlet private var versionDateLabel: UILabel!
|
|
@IBOutlet private var sizeLabel: UILabel!
|
|
|
|
@IBOutlet private var screenshotsCollectionView: UICollectionView!
|
|
@IBOutlet private var permissionsCollectionView: UICollectionView!
|
|
|
|
var preferredScreenshotSize: CGSize? {
|
|
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
|
|
|
let aspectRatio: CGFloat = 16.0 / 9.0 // Hardcoded for now.
|
|
|
|
let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2)
|
|
|
|
let itemWidth = width / 1.5
|
|
let itemHeight = itemWidth * aspectRatio
|
|
|
|
return CGSize(width: itemWidth, height: itemHeight)
|
|
}
|
|
|
|
override func viewDidLoad()
|
|
{
|
|
super.viewDidLoad()
|
|
|
|
self.tableView.contentInset.bottom = 20
|
|
|
|
self.screenshotsCollectionView.dataSource = self.screenshotsDataSource
|
|
self.screenshotsCollectionView.prefetchDataSource = self.screenshotsDataSource
|
|
|
|
self.permissionsCollectionView.dataSource = self.permissionsDataSource
|
|
|
|
self.subtitleLabel.text = self.app.subtitle
|
|
self.descriptionTextView.text = self.app.localizedDescription
|
|
self.versionDescriptionTextView.text = self.app.versionDescription
|
|
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version)
|
|
self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter)
|
|
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size))
|
|
|
|
self.descriptionTextView.maximumNumberOfLines = 5
|
|
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
|
|
|
self.versionDescriptionTextView.maximumNumberOfLines = 3
|
|
self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
|
}
|
|
|
|
override func viewDidLayoutSubviews()
|
|
{
|
|
super.viewDidLayoutSubviews()
|
|
|
|
guard var size = self.preferredScreenshotSize else { return }
|
|
size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning.
|
|
|
|
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
|
layout.itemSize = size
|
|
}
|
|
|
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
|
{
|
|
guard segue.identifier == "showPermission" else { return }
|
|
|
|
guard let cell = sender as? UICollectionViewCell, let indexPath = self.permissionsCollectionView.indexPath(for: cell) else { return }
|
|
|
|
let permission = self.permissionsDataSource.item(at: indexPath)
|
|
|
|
let maximumWidth = self.view.bounds.width - 20
|
|
|
|
let permissionPopoverViewController = segue.destination as! PermissionPopoverViewController
|
|
permissionPopoverViewController.permission = permission
|
|
permissionPopoverViewController.view.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true
|
|
|
|
let size = permissionPopoverViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
|
permissionPopoverViewController.preferredContentSize = size
|
|
|
|
permissionPopoverViewController.popoverPresentationController?.delegate = self
|
|
permissionPopoverViewController.popoverPresentationController?.sourceRect = cell.frame
|
|
permissionPopoverViewController.popoverPresentationController?.sourceView = self.permissionsCollectionView
|
|
}
|
|
}
|
|
|
|
private extension AppContentViewController
|
|
{
|
|
func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
|
|
{
|
|
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: self.app.screenshotURLs as [NSURL])
|
|
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
|
let cell = cell as! ScreenshotCollectionViewCell
|
|
cell.imageView.image = nil
|
|
cell.imageView.isIndicatingActivity = true
|
|
}
|
|
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
|
return RSTAsyncBlockOperation() { (operation) in
|
|
let request = ImageRequest(url: imageURL as URL, processor: .screenshot)
|
|
ImagePipeline.shared.loadImage(with: request, progress: nil, completion: { (response, error) in
|
|
guard !operation.isCancelled else { return operation.finish() }
|
|
|
|
if let image = response?.image
|
|
{
|
|
completionHandler(image, nil)
|
|
}
|
|
else
|
|
{
|
|
completionHandler(nil, error)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
|
let cell = cell as! ScreenshotCollectionViewCell
|
|
cell.imageView.isIndicatingActivity = false
|
|
cell.imageView.image = image
|
|
|
|
if let error = error
|
|
{
|
|
print("Error loading image:", error)
|
|
}
|
|
}
|
|
|
|
return dataSource
|
|
}
|
|
|
|
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
|
|
{
|
|
let dataSource = RSTArrayCollectionViewDataSource(items: self.app.permissions)
|
|
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
|
let cell = cell as! PermissionCollectionViewCell
|
|
cell.button.setImage(permission.type.icon, for: .normal)
|
|
cell.textLabel.text = permission.type.localizedShortName
|
|
}
|
|
|
|
return dataSource
|
|
}
|
|
}
|
|
|
|
private extension AppContentViewController
|
|
{
|
|
@objc func toggleCollapsingSection(_ sender: UIButton)
|
|
{
|
|
let indexPath: IndexPath
|
|
|
|
switch sender
|
|
{
|
|
case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
|
case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
|
default: return
|
|
}
|
|
|
|
// Disable animations to prevent some potentially strange ones.
|
|
UIView.performWithoutAnimation {
|
|
self.tableView.reloadRows(at: [indexPath], with: .none)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension AppContentViewController
|
|
{
|
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
|
|
{
|
|
cell.tintColor = self.app.tintColor
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
|
{
|
|
switch Row.allCases[indexPath.row]
|
|
{
|
|
case .screenshots:
|
|
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
|
return size.height
|
|
|
|
case .permissions:
|
|
guard !self.app.permissions.isEmpty else { return 0.0 }
|
|
return super.tableView(tableView, heightForRowAt: indexPath)
|
|
|
|
default:
|
|
return super.tableView(tableView, heightForRowAt: indexPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension AppContentViewController: UIPopoverPresentationControllerDelegate
|
|
{
|
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
|
|
{
|
|
return .none
|
|
}
|
|
}
|