Skip to content

Commit

Permalink
Merge pull request #464 from Iterable/MOB-2769-6.2.21
Browse files Browse the repository at this point in the history
[MOB-2769] version 6.2.21
  • Loading branch information
roninopf committed Mar 31, 2021
2 parents 04101b9 + 8ea5f99 commit 19fb463
Show file tree
Hide file tree
Showing 25 changed files with 572 additions and 188 deletions.
7 changes: 7 additions & 0 deletions .github/pull_request_template.md
@@ -0,0 +1,7 @@
## 🔹 JIRA Ticket(s) if any

* [MOB-XXXX](https://iterable.atlassian.net/browse/MOB-XXXX)

## ✏️ Description

> Please provide a brief description of what this pull request does.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## 6.2.21
#### Added
- Support for syncing in-app message read state across multiple devices:
- When the SDK fetches in-app messages from Iterable, it examines each
message's `read` field to determine if it has already been read.
- The SDK's default implementation no longer automatically displays in-app
messages that have already been seen on another device (even if those
messages were _not_ configured to go directly to the inbox).
- When you view a message, the SDK calls [`POST /api/events/trackInAppOpen`](https://api.iterable.com/api/docs#events_trackInAppOpen)
to create an `inAppOpen` event on the user's Iterable profile. Previous
versions of the SDK made this same API call, but the call now also causes
Iterable to set the message's `read` field to `true`.
- Previous versions of the SDK will correctly sync a message's read / unread
indicator for the default implementation of a mobile inbox. However, these
older SDK versions will not automatically suppress messages that have
already been seen on another device (as this version of the SDK will).
- Support for the display of a custom message (title and body) in an empty
mobile inbox. For more details, see [Customizing Mobile Inbox on iOS](https://support.iterable.com/hc/articles/360039091471#empty-state)

## 6.2.20
#### Added
- Added callback to initialize method needed for React Native. This change should have no effect for iOS SDK.
Expand Down
2 changes: 1 addition & 1 deletion Iterable-iOS-AppExtensions.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "Iterable-iOS-AppExtensions"
s.module_name = "IterableAppExtensions"
s.version = "6.2.20"
s.version = "6.2.21"
s.summary = "App Extensions for Iterable SDK"

s.description = <<-DESC
Expand Down
2 changes: 1 addition & 1 deletion Iterable-iOS-SDK.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "Iterable-iOS-SDK"
s.module_name = "IterableSDK"
s.version = "6.2.20"
s.version = "6.2.21"
s.summary = "Iterable's official SDK for iOS"

s.description = <<-DESC
Expand Down
12 changes: 12 additions & 0 deletions swift-sdk.xcodeproj/project.pbxproj
Expand Up @@ -18,6 +18,7 @@
556FB1EA244FAF6A00EDF6BD /* InAppPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 556FB1E9244FAF6A00EDF6BD /* InAppPresenter.swift */; };
557AE6BF24A56E5E00B57750 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557AE6BE24A56E5E00B57750 /* Auth.swift */; };
5585DF8F22A73390000A32B9 /* IterableInboxViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */; };
55AEA95925F05B7D00B38CED /* InAppMessageProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */; };
55B3119B251015CF0056E4FC /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55298B222501A5AB00190BAE /* AuthManager.swift */; };
55B37FC1229620D20042F13A /* CommerceItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B37FC0229620D20042F13A /* CommerceItemTests.swift */; };
55B37FC42297135F0042F13A /* NotificationMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B37FC32297135F0042F13A /* NotificationMetadataTests.swift */; };
Expand Down Expand Up @@ -366,6 +367,7 @@
557AE6BE24A56E5E00B57750 /* Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = "<group>"; };
5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxViewControllerTests.swift; sourceTree = "<group>"; };
5585DF9022A877E6000A32B9 /* IterableInboxViewControllerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxViewControllerUITests.swift; sourceTree = "<group>"; };
55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageProcessorTests.swift; sourceTree = "<group>"; };
55B37FC0229620D20042F13A /* CommerceItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommerceItemTests.swift; sourceTree = "<group>"; };
55B37FC32297135F0042F13A /* NotificationMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMetadataTests.swift; sourceTree = "<group>"; };
55B37FC5229752DD0042F13A /* OrderedDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedDictionaryTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -676,6 +678,13 @@
path = "swift-sdk/misc";
sourceTree = "<group>";
};
55A8F6E92613E0B800184ECC /* Recovered References */ = {
isa = PBXGroup;
children = (
);
name = "Recovered References";
sourceTree = "<group>";
};
AC0248062279132400495FB9 /* Dwifft */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -732,6 +741,7 @@
ACDA975A23159C37004C412E /* inbox-ui-tests-app */,
ACFCA72920EB02DB00BFB277 /* tests */,
5550F22324217CFC0014456A /* misc */,
55A8F6E92613E0B800184ECC /* Recovered References */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -955,6 +965,7 @@
55E6F45D238E066400808BCE /* DeferredDeepLinkTests.swift */,
551E5C0F234D485A005FEE9E /* DeprecatedFunctionsTests.swift */,
AC750A49234CD67900561902 /* InAppHelperTests.swift */,
55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */,
AC6FDD8B20F56309005D811E /* InAppParsingTests.swift */,
55B37FED229F59290042F13A /* InAppPersistenceTests.swift */,
55CC257A2462064F00A77FD5 /* InAppPresenterTests.swift */,
Expand Down Expand Up @@ -1752,6 +1763,7 @@
buildActionMask = 2147483647;
files = (
ACA8D1A62196309C001B1332 /* Common.swift in Sources */,
55AEA95925F05B7D00B38CED /* InAppMessageProcessorTests.swift in Sources */,
ACC362B824D17005002C67BA /* IterableRequestTests.swift in Sources */,
AC2C668720D3435700D46CC9 /* IterableActionRunnerTests.swift in Sources */,
00CB31B621096129004ACDEC /* TestUtils.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion swift-sdk/Internal/CoreDataUtil.swift
Expand Up @@ -62,7 +62,8 @@ struct CoreDataUtil {
} else if let boolValue = columnValue as? Bool {
return NSPredicate(format: "%K == %@", columnName, NSNumber(value: boolValue))
} else {
fatalError("unsuppored value: \(columnValue)")
ITBError("unsuppored value: \(columnValue) for columnPredicate")
return NSPredicate()
}
}
}
11 changes: 0 additions & 11 deletions swift-sdk/Internal/InAppCalculations.swift
Expand Up @@ -102,17 +102,6 @@ struct InAppCalculations {
}
}

static func adjustedPadding(from padding: UIEdgeInsets) -> UIEdgeInsets {
var insetPadding = padding
if insetPadding.left + insetPadding.right >= 100 {
ITBError("Can't display an in-app with padding > 100%. Defaulting to 0 for padding left/right")
insetPadding.left = 0
insetPadding.right = 0
}

return insetPadding
}

static func calculateWebViewPosition(safeAreaInsets: UIEdgeInsets,
parentPosition: ViewPosition,
paddingLeft: CGFloat,
Expand Down
131 changes: 109 additions & 22 deletions swift-sdk/Internal/InAppContentParser.swift
Expand Up @@ -8,6 +8,9 @@
import Foundation
import UIKit

typealias PaddingParser = HtmlContentParser.InAppDisplaySettingsParser.PaddingParser
typealias Padding = PaddingParser.Padding

enum InAppContentParseResult {
case success(content: IterableInAppContent)
case failure(reason: String)
Expand Down Expand Up @@ -41,7 +44,7 @@ private protocol ContentFromJsonParser {
}

struct HtmlContentParser {
static func getPadding(fromInAppSettings settings: [AnyHashable: Any]?) -> UIEdgeInsets {
static func getPadding(fromInAppSettings settings: [AnyHashable: Any]?) -> Padding {
InAppDisplaySettingsParser.PaddingParser.getPadding(fromInAppSettings: settings)
}

Expand Down Expand Up @@ -101,6 +104,65 @@ struct HtmlContentParser {
case bottom
}

enum PaddingValue: Equatable {
case percent(value: Int)
case autoExpand

func toCGFloat() -> CGFloat {
switch self {
case .percent(value: let value):
return CGFloat(value)
case .autoExpand:
return CGFloat(-1)
}
}

static func from(cgFloat: CGFloat) -> PaddingValue {
switch cgFloat {
case -1:
return .autoExpand
default:
return .percent(value: Int(cgFloat))
}
}
}

struct Padding: Equatable {
static let zero = Padding(top: .percent(value: 0),
left: 0,
bottom: .percent(value: 0),
right: 0)
let top: PaddingValue
let left: Int
let bottom: PaddingValue
let right: Int

func adjusted() -> Padding {
if left + right >= 100 {
return Padding(top: top,
left: 0,
bottom: bottom,
right: 0)
} else {
return self
}
}

func toEdgeInsets() -> UIEdgeInsets {
UIEdgeInsets(top: top.toCGFloat(),
left: CGFloat(left),
bottom: bottom.toCGFloat(),
right: CGFloat(right))
}

static func from(edgeInsets: UIEdgeInsets) -> Padding {
Padding(top: PaddingValue.from(cgFloat: edgeInsets.top),
left: Int(edgeInsets.left),
bottom: PaddingValue.from(cgFloat: edgeInsets.bottom),
right: Int(edgeInsets.right))
}
}

enum PaddingKey: String, JsonKeyRepresentable {
var jsonKey: String {
rawValue
Expand All @@ -114,50 +176,75 @@ struct HtmlContentParser {

/// `settings` json looks like the following
/// {"bottom": {"displayOption": "AutoExpand"}, "left": {"percentage": 60}, "right": {"percentage": 60}, "top": {"displayOption": "AutoExpand"}}
static func getPadding(fromInAppSettings settings: [AnyHashable: Any]?) -> UIEdgeInsets {
UIEdgeInsets(top: getEdgePadding(fromInAppSettings: settings, edge: .top),
left: getEdgePadding(fromInAppSettings: settings, edge: .left),
bottom: getEdgePadding(fromInAppSettings: settings, edge: .bottom),
right: getEdgePadding(fromInAppSettings: settings, edge: .right))
static func getPadding(fromInAppSettings settings: [AnyHashable: Any]?) -> Padding {
Padding(top: getEdgePaddingValue(fromInAppSettings: settings, edge: .top),
left: getEdgePadding(fromInAppSettings: settings, edge: .left),
bottom: getEdgePaddingValue(fromInAppSettings: settings, edge: .bottom),
right: getEdgePadding(fromInAppSettings: settings, edge: .right))
}

/// json comes in as
/// `{"displayOption": "AutoExpand"}`
/// or `{"percentage": 60}`
/// returns `-1` for `AutoExpand` type
static func decodePadding(_ value: Any?) -> Int {
static func decodePaddingValue(_ value: Any?) -> PaddingValue {
guard let dict = value as? [AnyHashable: Any] else {
return 0
return .percent(value: 0)
}

if let displayOption = dict.getValue(for: PaddingKey.displayOption) as? String, displayOption == Self.displayOptionAutoExpand {
return -1
return .autoExpand
} else {
if let percentage = dict.getValue(for: PaddingKey.percentage) as? NSNumber {
return percentage.intValue
return .percent(value: Int(truncating: percentage))
}

return .percent(value: 0)
}
}

/// json comes in as
/// `{"percentage": 60}`
static func decodePadding(_ value: Any?) -> Int {
guard let dict = value as? [AnyHashable: Any] else {
return 0
}

if let percentage = dict.getValue(for: PaddingKey.percentage) as? NSNumber {
return Int(truncating: percentage)
}

return 0
}

static func location(fromPadding padding: UIEdgeInsets) -> IterableMessageLocation {
if padding.top == 0, padding.bottom == 0 {

static func location(fromPadding padding: Padding) -> IterableMessageLocation {
if case .percent(let topPadding) = padding.top,
case .percent(let bottomPadding) = padding.bottom,
topPadding == 0,
bottomPadding == 0 {
return .full
} else if padding.top == 0, padding.bottom < 0 {
} else if case .autoExpand = padding.bottom,
case .percent(let topPadding) = padding.top,
topPadding == 0 {
return .top
} else if padding.top < 0, padding.bottom == 0 {
} else if case .autoExpand = padding.top,
case .percent(let bottomPadding) = padding.bottom,
bottomPadding == 0 {
return .bottom
} else {
return .center
}
}


private static func getEdgePaddingValue(fromInAppSettings settings: [AnyHashable: Any]?,
edge: PaddingEdge) -> PaddingValue {
settings?.getValue(for: edge)
.map(decodePaddingValue(_:)) ?? .percent(value: 0)
}

private static func getEdgePadding(fromInAppSettings settings: [AnyHashable: Any]?,
edge: PaddingEdge) -> CGFloat {
edge: PaddingEdge) -> Int {
settings?.getValue(for: edge)
.map(decodePadding(_:))
.map { CGFloat($0) } ?? .zero
.map(decodePadding(_:)) ?? 0
}
}
}
Expand All @@ -174,12 +261,12 @@ extension HtmlContentParser: ContentFromJsonParser {
}

let inAppDisplaySettings = json[JsonKey.InApp.inAppDisplaySettings] as? [AnyHashable: Any]
let edgeInsets = getPadding(fromInAppSettings: inAppDisplaySettings)
let padding = getPadding(fromInAppSettings: inAppDisplaySettings)

let shouldAnimate = inAppDisplaySettings.map(Self.parseShouldAnimate(fromInAppSettings:)) ?? false
let backgroundColor = inAppDisplaySettings.flatMap(Self.parseBackgroundColor(fromInAppSettings:))

return .success(content: IterableHtmlInAppContent(edgeInsets: edgeInsets,
return .success(content: IterableHtmlInAppContent(edgeInsets: padding.toEdgeInsets(),
html: html,
shouldAnimate: shouldAnimate,
backgroundColor: backgroundColor))
Expand Down
4 changes: 2 additions & 2 deletions swift-sdk/Internal/InAppDisplayer.swift
Expand Up @@ -42,7 +42,7 @@ class InAppDisplayer: InAppDisplayerProtocol {
*/
@discardableResult static func showIterableHtmlMessage(_ htmlString: String,
messageMetadata: IterableInAppMessageMetadata? = nil,
padding: UIEdgeInsets = .zero) -> ShowResult {
padding: Padding = .zero) -> ShowResult {
guard !InAppPresenter.isPresenting else {
return .notShown("In-app notification is being presented.")
}
Expand Down Expand Up @@ -131,7 +131,7 @@ class InAppDisplayer: InAppDisplayerProtocol {

return showIterableHtmlMessage(content.html,
messageMetadata: metadata,
padding: content.edgeInsets)
padding: content.padding)
}

// deprecated - will be removed in version 6.3.x or above
Expand Down
6 changes: 3 additions & 3 deletions swift-sdk/Internal/InAppHelper.swift
Expand Up @@ -3,12 +3,12 @@
// Ported to Swift by Tapash Majumder on 6/7/18.
// Copyright © 2018 Iterable. All rights reserved.
//
// Utility Methods for in-app
// All classes/structs are internal.

import UIKit

// This is Internal Struct, no public methods
/// Utility Methods for in-app
/// All classes/structs are internal.

struct InAppHelper {
static func getInAppMessagesFromServer(apiClient: ApiClientProtocol, number: Int) -> Future<[IterableInAppMessage], SendRequestError> {
apiClient.getInAppMessages(NSNumber(value: number)).map {
Expand Down

0 comments on commit 19fb463

Please sign in to comment.