Skip to content

Commit

Permalink
[iOS] [12800] Request App Store review (#108)
Browse files Browse the repository at this point in the history
We should request app store reviews in the app.

We will prompt the user to review the app in the App Store after certain events occur (e.g. event rated, video finished playing, about screen loaded) and if the following requirements are met:

Running iOS 10.3 or above
App has been running for at least 5 minutes
It's been a week since we last prompted the user for a review.
  • Loading branch information
colemancda authored May 19, 2017
1 parent 1412d53 commit 1a13b0e
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 6 deletions.
4 changes: 4 additions & 0 deletions OpenStack Summit/OpenStack Summit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@
6E793E701EC4BCE100C636AE /* LinkJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E55B5081EBCBD0C003AAFE6 /* LinkJSON.swift */; };
6E793E711EC4BCE900C636AE /* LinkJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E55B5081EBCBD0C003AAFE6 /* LinkJSON.swift */; };
6E7E18EA1D490DAC009549BA /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7E18E91D490DAC009549BA /* Preference.swift */; };
6E7F770D1ECF3D040003738D /* AppReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7F770C1ECF3D040003738D /* AppReview.swift */; };
6E7FEA2B1D78959900B9E809 /* VenuesInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7FEA2A1D78959900B9E809 /* VenuesInterfaceController.swift */; };
6E7FEA321D78AA6200B9E809 /* EventDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7FEA2E1D78A19000B9E809 /* EventDetail.swift */; };
6E7FEA331D78E8D800B9E809 /* YouTubeThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6AB19D1D650B8C00E940BE /* YouTubeThumbnail.swift */; };
Expand Down Expand Up @@ -1603,6 +1604,7 @@
6E77C70C1DBE5C6B008BF684 /* ComplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = "<group>"; };
6E7847F01DC9224700B8AD05 /* LocationManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationManagedObject.swift; sourceTree = "<group>"; };
6E7E18E91D490DAC009549BA /* Preference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preference.swift; sourceTree = "<group>"; };
6E7F770C1ECF3D040003738D /* AppReview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppReview.swift; sourceTree = "<group>"; };
6E7FEA2A1D78959900B9E809 /* VenuesInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VenuesInterfaceController.swift; sourceTree = "<group>"; };
6E7FEA2E1D78A19000B9E809 /* EventDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventDetail.swift; sourceTree = "<group>"; };
6E81DE5B1EC1092C000A0806 /* MainRevealViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainRevealViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2419,6 +2421,7 @@
6E3E8C271D47E64D00A29003 /* ShowVenueDetail.swift */,
6EC417E71D76B60000E5888C /* ShowVideo.swift */,
6E57DDC61E4AD22100783BA6 /* PresentPopover.swift */,
6E7F770C1ECF3D040003738D /* AppReview.swift */,
);
name = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -4185,6 +4188,7 @@
6EB3EA111D1B520B000BA5A4 /* FeedbackTableViewCell.swift in Sources */,
6E25384B1D1AF416003E3400 /* Filter.swift in Sources */,
6E2D18541E29417F0095508A /* PushNotificationManager.swift in Sources */,
6E7F770D1ECF3D040003738D /* AppReview.swift in Sources */,
6EF3A88E1E47095100CD85EF /* TableViewController.swift in Sources */,
6EF12C681D149CA600814AD0 /* R.generated.swift in Sources */,
6EF3A8911E470BEC00CD85EF /* CollectionViewController.swift in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions OpenStack Summit/OpenStack Summit/AboutViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ final class AboutViewController: UITableViewController, RevealViewController, Em
userActivity?.resignCurrent()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

requestAppReview()
}

override func updateUserActivityState(_ userActivity: NSUserActivity) {

let userInfo = [AppActivityUserInfo.screen.rawValue: AppActivityScreen.about.rawValue]
Expand Down
2 changes: 2 additions & 0 deletions OpenStack Summit/OpenStack Summit/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, SummitActivityHandl
static var shared: AppDelegate { return unsafeBitCast(UIApplication.shared.delegate!, to: AppDelegate.self) }

var window: UIWindow?

let appLaunch = Date()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

Expand Down
55 changes: 55 additions & 0 deletions OpenStack Summit/OpenStack Summit/AppReview.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// AppReview.swift
// OpenStack Summit
//
// Created by Alsey Coleman Miller on 5/19/17.
// Copyright © 2017 OpenStack. All rights reserved.
//

import Foundation
import UIKit
import StoreKit

extension UIViewController {

/// Attempts to prompt the user to review the app in the App Store.
///
/// This method is called after certain events occur
/// (e.g. event rated, video finished playing, about screen loaded)
/// and can only show if the following requirements are met:
/// - Running iOS 10.3 or above
/// - App has been running for at least 5 minutes
/// - It's been a week since we last prompted the user for a review.
func requestAppReview() {

guard canReviewApp
else { return }

// store new value
Preference.lastAppReview = Date()

// show UI
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
}
}

private var canReviewApp: Bool {

let lastReview = Preference.lastAppReview

// first prompt or interval after last prompt
if let lastReview = lastReview {

let interval: Double = 60 * 24 * 7 // 1 week

return Date() >= lastReview.addingTimeInterval(interval)

} else {

let interval: Double = 60 * 5 // 5 min

return Date() >= AppDelegate.shared.appLaunch.addingTimeInterval(interval)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ final class FeedbackViewController: UIViewController, MessageEnabledViewControll

case .value:

controller.requestAppReview()

controller.close()
}
}
Expand Down
37 changes: 32 additions & 5 deletions OpenStack Summit/OpenStack Summit/Preference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,55 @@ import Foundation

struct Preference {

// MARK: - Properties

static var appBuild: Int {

get { return UserDefaults.standard.integer(forKey: Key.appBuild.rawValue) }
get { return object(for: .appBuild) as? Int ?? 0 }

set { UserDefaults.standard.set(newValue, forKey: Key.appBuild.rawValue) }
set { set(object: newValue, for: .appBuild) }
}

static var goingToSummit: Bool {

get { return UserDefaults.standard.bool(forKey: Key.goingToSummit.rawValue)}
get { return object(for: .goingToSummit) as? Bool ?? false }

set { set(object: newValue, for: .goingToSummit) }
}

/// Last time user has offered to review the app.
static var lastAppReview: Date? {

get { return object(for: .lastAppReview) as? Date }

set { set(object: newValue, for: .lastAppReview) }
}

// MARK: - Private Methods

@inline(__always)
static func object(for key: Key) -> Any? {

return UserDefaults.standard.object(forKey: key.rawValue)
}

@inline(__always)
static func set(object: Any?, for key: Key) {

UserDefaults.standard.set(object, forKey: key.rawValue)

set { UserDefaults.standard.set(newValue, forKey: Key.goingToSummit.rawValue) }
UserDefaults.standard.synchronize()
}
}

// MARK: - Keys

private extension Preference {
extension Preference {

enum Key: String {

case appBuild
case goingToSummit
case lastAppReview
}
}
4 changes: 3 additions & 1 deletion OpenStack Summit/OpenStack Summit/ShowVideo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ extension UIViewController {

// listen to when the video stops playing
var observer: AnyObject!
observer = NotificationCenter.default.addObserver(forName: Notification.Name.MPMoviePlayerPlaybackDidFinish, object: videoPlayer.moviePlayer, queue: nil) { (notification) in
observer = NotificationCenter.default.addObserver(forName: Notification.Name.MPMoviePlayerPlaybackDidFinish, object: videoPlayer.moviePlayer, queue: nil) { [weak self] (notification) in

userActivity.invalidate()

NotificationCenter.default.removeObserver(observer)

self?.requestAppReview()
}
}
}

0 comments on commit 1a13b0e

Please sign in to comment.