Adds RecordVersionsViewController
Allows users to restore remote versions of records, or resolve conflicts
This commit is contained in:
parent
b226698760
commit
f8c47fcb86
@ -278,11 +278,11 @@
|
||||
<rect key="frame" x="0.0" y="755" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SSL-t4-QZj" id="hQB-Iy-bzy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="349" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="App Icon Shortcuts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c5i-qG-ir9">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -882,7 +882,6 @@
|
||||
<!--Record Sync Status View Controller-->
|
||||
<scene sceneID="GG8-80-sV4">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fmY-uC-Naa" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<tableViewController id="kBh-Lp-rBr" customClass="RecordSyncStatusViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="k6O-hT-4zC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
@ -1068,6 +1067,9 @@
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="zfS-39-NF3" kind="presentation" identifier="showVersions" id="xvh-Yg-gc5"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
@ -1086,9 +1088,114 @@
|
||||
<outlet property="syncingEnabledSwitch" destination="K0v-VD-uUj" id="BSt-cy-p4F"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fmY-uC-Naa" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="5047" y="1872"/>
|
||||
</scene>
|
||||
<!--Versions-->
|
||||
<scene sceneID="xaH-AW-NqP">
|
||||
<objects>
|
||||
<tableViewController id="PGW-Yp-czd" customClass="RecordVersionsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="9fJ-qb-tfO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" indentationWidth="10" reuseIdentifier="Cell" textLabel="Itj-QW-4dw" detailTextLabel="2fS-9A-FKk" style="IBUITableViewCellStyleSubtitle" id="kAh-xQ-U0r">
|
||||
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="kAh-xQ-U0r" id="Uoy-xs-wjL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Itj-QW-4dw">
|
||||
<rect key="frame" x="16" y="5" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2fS-9A-FKk">
|
||||
<rect key="frame" x="16" y="25.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="LoadingCell" id="lkg-MY-0hB">
|
||||
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lkg-MY-0hB" id="TM3-U4-9sx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="hJz-RO-Pi8">
|
||||
<rect key="frame" x="177.5" y="12" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="hJz-RO-Pi8" firstAttribute="centerX" secondItem="TM3-U4-9sx" secondAttribute="centerX" id="dwn-pH-e6v"/>
|
||||
<constraint firstItem="hJz-RO-Pi8" firstAttribute="centerY" secondItem="TM3-U4-9sx" secondAttribute="centerY" id="yIw-Ou-nWN"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" indentationWidth="10" reuseIdentifier="ConfirmCell" textLabel="saf-eQ-eJc" style="IBUITableViewCellStyleDefault" id="x0b-KE-P94">
|
||||
<rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="x0b-KE-P94" id="ufh-yE-uv2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Restore Version" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="saf-eQ-eJc">
|
||||
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="PGW-Yp-czd" id="O27-2u-XUE"/>
|
||||
<outlet property="delegate" destination="PGW-Yp-czd" id="qqj-9a-7id"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Versions" id="GLC-Lc-6VM">
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="LMc-KQ-vea">
|
||||
<connections>
|
||||
<segue destination="oyk-u7-Dn0" kind="unwind" unwindAction="unwindToRecordSyncStatusViewController:" id="JOO-Bj-i1E"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="p6Y-9f-cL2" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="oyk-u7-Dn0" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="5807" y="1182"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="J41-NY-ZAz">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="zfS-39-NF3" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="iJQ-8h-Ho6">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="PGW-Yp-czd" kind="relationship" relationship="rootViewController" id="ybZ-FQ-HsP"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bJV-tR-yMT" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="5046" y="1182"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<namedColor name="Purple">
|
||||
|
||||
@ -64,6 +64,23 @@ class RecordSyncStatusViewController: UITableViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension RecordSyncStatusViewController
|
||||
{
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard segue.identifier == "showVersions" else { return }
|
||||
|
||||
let navigationController = segue.destination as! UINavigationController
|
||||
|
||||
let recordVersionsViewController = navigationController.viewControllers[0] as! RecordVersionsViewController
|
||||
recordVersionsViewController.record = self.record
|
||||
}
|
||||
|
||||
@IBAction private func unwindToRecordSyncStatusViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private extension RecordSyncStatusViewController
|
||||
{
|
||||
func update()
|
||||
|
||||
394
Delta/Settings/Syncing/RecordVersionsViewController.swift
Normal file
394
Delta/Settings/Syncing/RecordVersionsViewController.swift
Normal file
@ -0,0 +1,394 @@
|
||||
//
|
||||
// RecordVersionsViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 11/20/18.
|
||||
// Copyright © 2018 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
import Harmony
|
||||
|
||||
extension RecordVersionsViewController
|
||||
{
|
||||
private enum Section: Int, CaseIterable
|
||||
{
|
||||
case local
|
||||
case remote
|
||||
case confirm
|
||||
}
|
||||
|
||||
private enum Mode
|
||||
{
|
||||
case restoreVersion
|
||||
case resolveConflict
|
||||
}
|
||||
}
|
||||
|
||||
private class Version
|
||||
{
|
||||
let version: Harmony.Version
|
||||
|
||||
init(_ version: Harmony.Version)
|
||||
{
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
|
||||
class RecordVersionsViewController: UITableViewController
|
||||
{
|
||||
var record: Record<NSManagedObject>! {
|
||||
didSet {
|
||||
self.mode = self.record.isConflicted ? .resolveConflict : .restoreVersion
|
||||
}
|
||||
}
|
||||
|
||||
private var mode = Mode.restoreVersion {
|
||||
didSet {
|
||||
switch self.mode
|
||||
{
|
||||
case .restoreVersion: self._selectedVersionIndexPath = IndexPath(item: 0, section: Section.local.rawValue)
|
||||
case .resolveConflict: self._selectedVersionIndexPath = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var versions: [Version]?
|
||||
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
private var remoteVersionsDataSource: RSTArrayTableViewDataSource<Version> {
|
||||
let compositeDataSource = self.dataSource.dataSources[1] as! RSTCompositeTableViewDataSource
|
||||
|
||||
let dataSource = compositeDataSource.dataSources[1] as! RSTArrayTableViewDataSource<Version>
|
||||
return dataSource
|
||||
}
|
||||
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.timeStyle = .short
|
||||
dateFormatter.dateStyle = .short
|
||||
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
private var isSyncingRecord = false
|
||||
private var _selectedVersionIndexPath: IndexPath?
|
||||
|
||||
private var progressView: UIProgressView!
|
||||
|
||||
private var _progressObservation: NSKeyValueObservation?
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.progressView = UIProgressView(progressViewStyle: .bar)
|
||||
self.progressView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.progressView.progress = 0
|
||||
|
||||
if let navigationBar = self.navigationController?.navigationBar
|
||||
{
|
||||
navigationBar.addSubview(self.progressView)
|
||||
|
||||
NSLayoutConstraint.activate([self.progressView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
|
||||
self.progressView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
|
||||
self.progressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)])
|
||||
}
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
self.fetchVersions()
|
||||
}
|
||||
}
|
||||
|
||||
private extension RecordVersionsViewController
|
||||
{
|
||||
func makeDataSource() -> RSTCompositeTableViewDataSource<Version>
|
||||
{
|
||||
func configure(_ cell: UITableViewCell, isSelected: Bool, isEnabled: Bool)
|
||||
{
|
||||
cell.accessoryType = isSelected ? .checkmark : .none
|
||||
|
||||
if isEnabled
|
||||
{
|
||||
cell.textLabel?.alpha = 1.0
|
||||
cell.detailTextLabel?.alpha = 1.0
|
||||
cell.selectionStyle = .gray
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.textLabel?.alpha = 0.33
|
||||
cell.detailTextLabel?.alpha = 0.33
|
||||
cell.selectionStyle = .none
|
||||
}
|
||||
}
|
||||
|
||||
let localVersionsDataSource = RSTDynamicTableViewDataSource<Version>()
|
||||
localVersionsDataSource.numberOfSectionsHandler = { 1 }
|
||||
localVersionsDataSource.numberOfItemsHandler = { _ in self.record.localModificationDate != nil ? 1 : 0 }
|
||||
localVersionsDataSource.cellConfigurationHandler = { [weak self] (cell, _, indexPath) in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
let date = self.record.localModificationDate!
|
||||
cell.textLabel?.text = self.dateFormatter.string(from: date)
|
||||
cell.detailTextLabel?.text = nil
|
||||
|
||||
let isSelected = (indexPath == self._selectedVersionIndexPath)
|
||||
configure(cell, isSelected: isSelected, isEnabled: !self.isSyncingRecord)
|
||||
}
|
||||
|
||||
let remoteVersionsDataSource = RSTArrayTableViewDataSource<Version>(items: [])
|
||||
remoteVersionsDataSource.cellConfigurationHandler = { [weak self] (cell, version, indexPath) in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
cell.textLabel?.text = self.dateFormatter.string(from: version.version.date)
|
||||
cell.detailTextLabel?.text = (version.version.identifier == self.record.remoteVersion?.identifier) ? self.record.remoteAuthor : nil
|
||||
|
||||
let isSelected = (self._selectedVersionIndexPath?.section == Section.remote.rawValue && self._selectedVersionIndexPath?.row == indexPath.row)
|
||||
configure(cell, isSelected: isSelected, isEnabled: !self.isSyncingRecord)
|
||||
}
|
||||
|
||||
let loadingDataSource = RSTDynamicTableViewDataSource<Version>()
|
||||
loadingDataSource.numberOfSectionsHandler = { 1 }
|
||||
loadingDataSource.numberOfItemsHandler = { _ in (self.versions == nil) ? 1 : 0 }
|
||||
loadingDataSource.cellIdentifierHandler = { _ in "LoadingCell" }
|
||||
loadingDataSource.cellConfigurationHandler = { (_, _, _) in }
|
||||
|
||||
let remoteVersionsLoadingDataSource = RSTCompositeTableViewDataSource(dataSources: [loadingDataSource, remoteVersionsDataSource])
|
||||
remoteVersionsLoadingDataSource.shouldFlattenSections = true
|
||||
|
||||
let restoreVersionDataSource = RSTDynamicTableViewDataSource<Version>()
|
||||
restoreVersionDataSource.numberOfSectionsHandler = { 1 }
|
||||
restoreVersionDataSource.numberOfItemsHandler = { _ in 1}
|
||||
restoreVersionDataSource.cellIdentifierHandler = { _ in "ConfirmCell" }
|
||||
restoreVersionDataSource.cellConfigurationHandler = { [weak self] (cell, _, indexPath) in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
switch self.mode
|
||||
{
|
||||
case .restoreVersion:
|
||||
cell.textLabel?.text = NSLocalizedString("Restore Version", comment: "")
|
||||
cell.textLabel?.textColor = .deltaPurple
|
||||
|
||||
let isEnabled = (self._selectedVersionIndexPath?.section == Section.remote.rawValue && !self.isSyncingRecord)
|
||||
configure(cell, isSelected: false, isEnabled: isEnabled)
|
||||
|
||||
case .resolveConflict:
|
||||
cell.textLabel?.text = NSLocalizedString("Resolve Conflict", comment: "")
|
||||
cell.textLabel?.textColor = .red
|
||||
|
||||
let isEnabled = (self._selectedVersionIndexPath != nil && !self.isSyncingRecord)
|
||||
configure(cell, isSelected: false, isEnabled: isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
let dataSource = RSTCompositeTableViewDataSource(dataSources: [localVersionsDataSource, remoteVersionsLoadingDataSource, restoreVersionDataSource])
|
||||
dataSource.proxy = self
|
||||
return dataSource
|
||||
}
|
||||
|
||||
func fetchVersions()
|
||||
{
|
||||
SyncManager.shared.syncCoordinator.fetchVersions(for: self.record) { (result) in
|
||||
do
|
||||
{
|
||||
let versions = try result.value().map(Version.init)
|
||||
self.versions = versions
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let count = self.tableView.numberOfRows(inSection: Section.remote.rawValue)
|
||||
|
||||
let deletions = (0 ..< count).map { (row) -> RSTCellContentChange in
|
||||
let change = RSTCellContentChange(type: .delete,
|
||||
currentIndexPath: IndexPath(row: row, section: 0),
|
||||
destinationIndexPath: nil)
|
||||
change.rowAnimation = .fade
|
||||
return change
|
||||
}
|
||||
|
||||
let inserts = (0 ..< versions.count).map { (row) -> RSTCellContentChange in
|
||||
let change = RSTCellContentChange(type: .insert,
|
||||
currentIndexPath: nil,
|
||||
destinationIndexPath: IndexPath(row: row, section: 0))
|
||||
change.rowAnimation = .fade
|
||||
return change
|
||||
}
|
||||
|
||||
let changes = deletions + inserts
|
||||
self.remoteVersionsDataSource.setItems(versions, with: changes)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Failed to Fetch Record Versions", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restoreVersion()
|
||||
{
|
||||
guard !self.isSyncingRecord else { return }
|
||||
|
||||
guard let indexPath = self._selectedVersionIndexPath else { return }
|
||||
|
||||
func finish(_ result: Result<Record<NSManagedObject>>)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
|
||||
CATransaction.begin()
|
||||
|
||||
CATransaction.setCompletionBlock {
|
||||
self.isSyncingRecord = false
|
||||
self._progressObservation = nil
|
||||
|
||||
self.progressView.setHidden(true, animated: true)
|
||||
|
||||
self.tableView.reloadData()
|
||||
|
||||
self.navigationItem.rightBarButtonItem?.isIndicatingActivity = false
|
||||
|
||||
switch result
|
||||
{
|
||||
case .success: self.fetchVersions()
|
||||
case .failure: break
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
let record = try result.value()
|
||||
self.record = record
|
||||
|
||||
self.progressView.setProgress(1.0, animated: true)
|
||||
}
|
||||
catch
|
||||
{
|
||||
let title: String
|
||||
|
||||
switch self.mode
|
||||
{
|
||||
case .restoreVersion: title = NSLocalizedString("Failed to Restore Version", comment: "")
|
||||
case .resolveConflict: title = NSLocalizedString("Failed to Resolve Conflict", comment: "")
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
CATransaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
let progress: Progress
|
||||
|
||||
switch (self.mode, Section.allCases[indexPath.section])
|
||||
{
|
||||
case (.restoreVersion, _):
|
||||
let version = self.dataSource.item(at: indexPath)
|
||||
|
||||
progress = SyncManager.shared.syncCoordinator.restore(self.record, to: version.version) { (result) in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case (.resolveConflict, .local):
|
||||
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .local) { (result) in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case (.resolveConflict, .remote):
|
||||
let version = self.dataSource.item(at: indexPath)
|
||||
|
||||
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .remote(version.version)) { (result) in
|
||||
finish(result)
|
||||
}
|
||||
|
||||
case (.resolveConflict, .confirm): return
|
||||
}
|
||||
|
||||
self.isSyncingRecord = true
|
||||
self.navigationItem.rightBarButtonItem?.isIndicatingActivity = true
|
||||
|
||||
self.progressView.progress = 0
|
||||
self.progressView.isHidden = false
|
||||
|
||||
self._progressObservation = progress.observe(\.fractionCompleted) { [weak progressView] (_, change) in
|
||||
DispatchQueue.main.async {
|
||||
progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
extension RecordVersionsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||
{
|
||||
switch Section.allCases[section]
|
||||
{
|
||||
case .local: return NSLocalizedString("Local", comment: "")
|
||||
case .remote: return NSLocalizedString("Remote", comment: "")
|
||||
case .confirm: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
guard let cell = tableView.cellForRow(at: indexPath), cell.selectionStyle != .none else { return }
|
||||
|
||||
switch Section.allCases[indexPath.section]
|
||||
{
|
||||
case .local, .remote:
|
||||
let indexPaths = [indexPath, self._selectedVersionIndexPath, IndexPath(item: 0, section: Section.confirm.rawValue)].compactMap { $0 }
|
||||
self._selectedVersionIndexPath = indexPath
|
||||
|
||||
tableView.reloadRows(at: indexPaths, with: .none)
|
||||
|
||||
case .confirm:
|
||||
let message: String
|
||||
let actionTitle: String
|
||||
|
||||
switch self.mode
|
||||
{
|
||||
case .restoreVersion:
|
||||
message = NSLocalizedString("Restoring a remote version will cause any local changes to be lost.", comment: "")
|
||||
actionTitle = NSLocalizedString("Restore Version", comment: "")
|
||||
|
||||
case .resolveConflict:
|
||||
if self._selectedVersionIndexPath?.section == Section.local.rawValue
|
||||
{
|
||||
message = NSLocalizedString("The local version will be uploaded and synced to your other devices.", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
message = NSLocalizedString("Selecting a remote version will cause any local changes to be lost.", comment: "")
|
||||
}
|
||||
|
||||
actionTitle = NSLocalizedString("Resolve Conflict", comment: "")
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
alertController.addAction(UIAlertAction(title: actionTitle, style: .destructive) { (action) in
|
||||
self.restoreVersion()
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ final class SyncManager
|
||||
|
||||
private(set) var isAuthenticated = false
|
||||
|
||||
private let syncCoordinator = SyncCoordinator(service: DriveService.shared, persistentContainer: DatabaseManager.shared)
|
||||
let syncCoordinator = SyncCoordinator(service: DriveService.shared, persistentContainer: DatabaseManager.shared)
|
||||
|
||||
private init()
|
||||
{
|
||||
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit d76e0eed8bdcf6ab9ee8d2bbfe522159c29116f5
|
||||
Subproject commit 11afd3fd1e3c2d9603d53394452631bd138e2b2c
|
||||
Loading…
Reference in New Issue
Block a user