diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index f908704a9..d15ed8979 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -7,7 +7,7 @@ CarthageVersion 0.31.2 OSXVersion - 10.14.1 + 10.14.2 WebRTCCommit ca71024b4993ba95e3e6b8d0758004cffc54ddaf M70 diff --git a/Signal/test/views/ImageEditorTest.swift b/Signal/test/views/ImageEditorTest.swift index 9c9315c22..1c2352b6c 100644 --- a/Signal/test/views/ImageEditorTest.swift +++ b/Signal/test/views/ImageEditorTest.swift @@ -6,6 +6,14 @@ import XCTest @testable import Signal @testable import SignalMessaging +extension ImageEditorModel { + func itemIds() -> [String] { + return items().map { (item) in + item.itemId + } + } +} + class ImageEditorTest: SignalBaseTest { override func setUp() { @@ -19,7 +27,10 @@ class ImageEditorTest: SignalBaseTest { func testImageEditorContents() { let contents = ImageEditorContents() - let item = ImageEditorItem() + XCTAssertEqual(0, contents.itemMap.count) + XCTAssertEqual(0, contents.itemIds.count) + + let item = ImageEditorItem(itemType: .test) contents.append(item: item) XCTAssertEqual(1, contents.itemMap.count) XCTAssertEqual(1, contents.itemIds.count) @@ -36,7 +47,7 @@ class ImageEditorTest: SignalBaseTest { XCTAssertEqual(0, contentsCopy.itemMap.count) XCTAssertEqual(0, contentsCopy.itemIds.count) - let modifiedItem = ImageEditorItem(itemId: item.itemId) + let modifiedItem = ImageEditorItem(itemId: item.itemId, itemType: item.itemType) contents.replace(item: modifiedItem) XCTAssertEqual(1, contents.itemMap.count) XCTAssertEqual(1, contents.itemIds.count) @@ -71,11 +82,12 @@ class ImageEditorTest: SignalBaseTest { XCTAssertFalse(imageEditor.canRedo()) XCTAssertEqual(0, imageEditor.itemCount()) - let itemA = ImageEditorItem() + let itemA = ImageEditorItem(itemType: .test) imageEditor.append(item: itemA) XCTAssertTrue(imageEditor.canUndo()) XCTAssertFalse(imageEditor.canRedo()) XCTAssertEqual(1, imageEditor.itemCount()) + XCTAssertEqual([itemA.itemId], imageEditor.itemIds()) imageEditor.undo() XCTAssertFalse(imageEditor.canUndo()) @@ -86,27 +98,31 @@ class ImageEditorTest: SignalBaseTest { XCTAssertTrue(imageEditor.canUndo()) XCTAssertFalse(imageEditor.canRedo()) XCTAssertEqual(1, imageEditor.itemCount()) + XCTAssertEqual([itemA.itemId], imageEditor.itemIds()) imageEditor.undo() XCTAssertFalse(imageEditor.canUndo()) XCTAssertTrue(imageEditor.canRedo()) XCTAssertEqual(0, imageEditor.itemCount()) - let itemB = ImageEditorItem() + let itemB = ImageEditorItem(itemType: .test) imageEditor.append(item: itemB) XCTAssertTrue(imageEditor.canUndo()) XCTAssertFalse(imageEditor.canRedo()) XCTAssertEqual(1, imageEditor.itemCount()) + XCTAssertEqual([itemB.itemId], imageEditor.itemIds()) - let itemC = ImageEditorItem() + let itemC = ImageEditorItem(itemType: .test) imageEditor.append(item: itemC) XCTAssertTrue(imageEditor.canUndo()) XCTAssertFalse(imageEditor.canRedo()) XCTAssertEqual(2, imageEditor.itemCount()) + XCTAssertEqual([itemB.itemId, itemC.itemId], imageEditor.itemIds()) imageEditor.undo() XCTAssertTrue(imageEditor.canUndo()) XCTAssertTrue(imageEditor.canRedo()) XCTAssertEqual(1, imageEditor.itemCount()) + XCTAssertEqual([itemB.itemId], imageEditor.itemIds()) } } diff --git a/SignalMessaging/Views/ImageEditor/ImageEditor.swift b/SignalMessaging/Views/ImageEditor/ImageEditor.swift index e09148ea0..9ca18d8f8 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditor.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditor.swift @@ -9,23 +9,36 @@ import UIKit case invalidInput } +@objc +public enum ImageEditorItemType: Int { + case test +} + // MARK: - +// Instances of ImageEditorItem should be treated +// as immutable, once configured. @objc public class ImageEditorItem: NSObject { @objc public let itemId: String @objc - public override required init() { + public let itemType: ImageEditorItemType + + @objc + public required init(itemType: ImageEditorItemType) { self.itemId = UUID().uuidString + self.itemType = itemType super.init() } @objc - public init(itemId: String) { + public required init(itemId: String, + itemType: ImageEditorItemType) { self.itemId = itemId + self.itemType = itemType super.init() } @@ -33,9 +46,14 @@ public class ImageEditorItem: NSObject { // MARK: - +// Instances of ImageEditorContents should be treated +// as immutable, once configured. public class ImageEditorContents: NSObject { + // This represents the current state of each item. var itemMap = [String: ImageEditorItem]() + + // This represents the back-to-front ordering of the items. var itemIds = [String]() @objc @@ -50,6 +68,8 @@ public class ImageEditorContents: NSObject { self.itemIds = itemIds } + // Since the contents are immutable, we only modify copies + // made with this method. @objc public func clone() -> ImageEditorContents { return ImageEditorContents(itemMap: itemMap, itemIds: itemIds) @@ -120,11 +140,29 @@ public class ImageEditorContents: NSObject { } return itemIds.count } + + @objc + public func items() -> [ImageEditorItem] { + var items = [ImageEditorItem]() + for itemId in itemIds { + guard let item = self.itemMap[itemId] else { + owsFailDebug("Missing item") + continue + } + items.append(item) + } + return items + } } // MARK: - // Used to represent undo/redo operations. +// +// Because the image editor's "contents" and "items" +// are immutable, these operations simply take a +// snapshot of the current contents which can be used +// (multiple times) to preserve/restore editor state. private class ImageEditorOperation: NSObject { let contents: ImageEditorContents @@ -149,6 +187,10 @@ public class ImageEditorModel: NSObject { private var undoStack = [ImageEditorOperation]() private var redoStack = [ImageEditorOperation]() + // We don't want to allow editing of images if: + // + // * They are invalid. + // * We can't determine their size / aspect-ratio. @objc public required init(srcImagePath: String) throws { self.srcImagePath = srcImagePath @@ -179,6 +221,11 @@ public class ImageEditorModel: NSObject { return contents.itemCount() } + @objc + public func items() -> [ImageEditorItem] { + return contents.items() + } + @objc public func canUndo() -> Bool { return !undoStack.isEmpty