Skip to content
This repository has been archived by the owner on Oct 21, 2020. It is now read-only.

Commit

Permalink
Merge pull request #147 from atfinke/2020.08
Browse files Browse the repository at this point in the history
2020.08
atfinke authored Jul 20, 2020
2 parents a625148 + a310d44 commit f30f580
Showing 37 changed files with 632 additions and 200 deletions.
18 changes: 16 additions & 2 deletions WKRKit/WKRKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
140CE30424C2967900ECA175 /* WKRLanguageHackery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140CE30324C2967900ECA175 /* WKRLanguageHackery.swift */; };
140CE30524C2967900ECA175 /* WKRLanguageHackery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140CE30324C2967900ECA175 /* WKRLanguageHackery.swift */; };
140FCFC31F36547E00FFA84E /* WKRResultsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140FCFC21F36547E00FFA84E /* WKRResultsInfo.swift */; };
140FCFC51F36549900FFA84E /* WKRPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140FCFC41F36549900FFA84E /* WKRPlayer.swift */; };
140FCFCB1F3654D500FFA84E /* WKRPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140FCFCA1F3654D500FFA84E /* WKRPlayerState.swift */; };
@@ -17,6 +19,9 @@
141427FF21FBB84600C48788 /* WKRGameKitNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141427FE21FBB84600C48788 /* WKRGameKitNetwork.swift */; };
142CB29A202EAC6B00BC6A33 /* WKRSoloNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142CB299202EAC6B00BC6A33 /* WKRSoloNetwork.swift */; };
142CB29E202EC42000BC6A33 /* WKRPeerNetworkConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142CB29D202EC42000BC6A33 /* WKRPeerNetworkConfig.swift */; };
144F33C024C34A24006E4437 /* WKRDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144F33BF24C34A24006E4437 /* WKRDefaults.swift */; };
144F33C124C34A24006E4437 /* WKRDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144F33BF24C34A24006E4437 /* WKRDefaults.swift */; };
144F33C224C34A24006E4437 /* WKRDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144F33BF24C34A24006E4437 /* WKRDefaults.swift */; };
1452B3721F36524400E81FD8 /* WKRPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1452B3711F36524400E81FD8 /* WKRPage.swift */; };
1452B3741F36529300E81FD8 /* WKRKitConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1452B3731F36529300E81FD8 /* WKRKitConstants.swift */; };
1452B3761F3652E900E81FD8 /* WKRRaceDurationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1452B3751F3652E900E81FD8 /* WKRRaceDurationConstants.swift */; };
@@ -106,6 +111,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
140CE30324C2967900ECA175 /* WKRLanguageHackery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRLanguageHackery.swift; sourceTree = "<group>"; };
140FCFC21F36547E00FFA84E /* WKRResultsInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRResultsInfo.swift; sourceTree = "<group>"; };
140FCFC41F36549900FFA84E /* WKRPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRPlayer.swift; sourceTree = "<group>"; };
140FCFCA1F3654D500FFA84E /* WKRPlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRPlayerState.swift; sourceTree = "<group>"; };
@@ -115,6 +121,7 @@
141427FE21FBB84600C48788 /* WKRGameKitNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRGameKitNetwork.swift; sourceTree = "<group>"; };
142CB299202EAC6B00BC6A33 /* WKRSoloNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRSoloNetwork.swift; sourceTree = "<group>"; };
142CB29D202EC42000BC6A33 /* WKRPeerNetworkConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRPeerNetworkConfig.swift; sourceTree = "<group>"; };
144F33BF24C34A24006E4437 /* WKRDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRDefaults.swift; sourceTree = "<group>"; };
1452B3711F36524400E81FD8 /* WKRPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRPage.swift; sourceTree = "<group>"; };
1452B3731F36529300E81FD8 /* WKRKitConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRKitConstants.swift; sourceTree = "<group>"; };
1452B3751F3652E900E81FD8 /* WKRRaceDurationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKRRaceDurationConstants.swift; sourceTree = "<group>"; };
@@ -320,6 +327,8 @@
14EB9F651F36833A00FF1A9E /* WKROperation.swift */,
14B4DB86222674E2007D4B54 /* WKRLogEvent.swift */,
1499606D2226FCBF00259B68 /* WKRDurationFormatter.swift */,
140CE30324C2967900ECA175 /* WKRLanguageHackery.swift */,
144F33BF24C34A24006E4437 /* WKRDefaults.swift */,
);
path = Other;
sourceTree = "<group>";
@@ -567,12 +576,14 @@
146E054724AE3E9E001E1917 /* WKRPlayer.swift in Sources */,
146E054B24AE3EB2001E1917 /* WKRLinkedPagesFetcher.swift in Sources */,
146E054124AE3E95001E1917 /* WKRSeenFinalArticlesStore.swift in Sources */,
140CE30524C2967900ECA175 /* WKRLanguageHackery.swift in Sources */,
146E053F24AE3E95001E1917 /* WKRHistoryEntry.swift in Sources */,
146E054224AE3E95001E1917 /* WKRPage.swift in Sources */,
146E053024AE3E80001E1917 /* WKRRaceDurationConstants.swift in Sources */,
146E053B24AE3E8F001E1917 /* WKRDurationFormatter.swift in Sources */,
146E053C24AE3E8F001E1917 /* WKRKit+Array.swift in Sources */,
146E053924AE3E85001E1917 /* WKRRaceSettings.swift in Sources */,
144F33C124C34A24006E4437 /* WKRDefaults.swift in Sources */,
146E054424AE3E9E001E1917 /* WKRPlayerRaceStats.swift in Sources */,
146E053324AE3E85001E1917 /* WKRReadyStates.swift in Sources */,
146E053A24AE3E8F001E1917 /* WKRLogEvent.swift in Sources */,
@@ -637,8 +648,10 @@
142CB29E202EC42000BC6A33 /* WKRPeerNetworkConfig.swift in Sources */,
149960702226FCBF00259B68 /* WKRDurationFormatter.swift in Sources */,
147EF6FE2202470700583D73 /* WKRSeenFinalArticlesStore.swift in Sources */,
144F33C024C34A24006E4437 /* WKRDefaults.swift in Sources */,
140FCFCD1F3654EB00FFA84E /* WKRHistoryEntry.swift in Sources */,
141427FF21FBB84600C48788 /* WKRGameKitNetwork.swift in Sources */,
140CE30424C2967900ECA175 /* WKRLanguageHackery.swift in Sources */,
14EB9F621F36832C00FF1A9E /* WKRMultiWindowNetwork.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -647,6 +660,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
144F33C224C34A24006E4437 /* WKRDefaults.swift in Sources */,
148164B11F5C94F200AC7178 /* WKRKitPageFetcherTests.swift in Sources */,
148164AF1F5C94E800AC7178 /* WKRKitRaceTests.swift in Sources */,
149FF8171F362B3D000A5D96 /* WKRKitTests.swift in Sources */,
@@ -851,7 +865,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2020.07;
MARKETING_VERSION = 2020.08;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.andrewfinke.WKRKit;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@@ -882,7 +896,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2020.07;
MARKETING_VERSION = 2020.08;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.andrewfinke.WKRKit;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
141 changes: 94 additions & 47 deletions WKRKit/WKRKit/Game/WKRPreRaceConfig.swift
Original file line number Diff line number Diff line change
@@ -7,19 +7,20 @@
//

import WKRUIKit
import os.log

/// Used to transmit voting data and starting page
public struct WKRPreRaceConfig: Codable, Equatable {

// MARK: - Properties

/// The voting info
internal var votingState: WKRVotingState
/// The starting page
internal let startingPage: WKRPage

// MARK: Initialization

/// Creates a WKRPreRaceConfig object
///
/// - Parameters:
@@ -29,9 +30,9 @@ public struct WKRPreRaceConfig: Codable, Equatable {
self.votingState = votingState
self.startingPage = startingPage
}

// MARK: - Creation

/// Creates a race config object based on starting page and voting data
///
/// - Returns: The new race config
@@ -42,58 +43,80 @@ public struct WKRPreRaceConfig: Codable, Equatable {
}
return (WKRRaceConfig(starting: startingPage, ending: page), logEvent)
}

/// Creates a WKRPreRaceConfig object
///
/// - Parameter completionHandler: The handler holding the new config object
static func new(settings: WKRGameSettings, completionHandler: @escaping ((_ config: WKRPreRaceConfig?, _ logEvents: [WKRLogEvent]) -> Void)) {
var logEvents = [WKRLogEvent]()
let operationQueue = OperationQueue()

var potentialFinalPages = [WKRPage]()
var startingPage: WKRPage?

var fastlaneFinalPage: WKRPage?

let completedOperation = BlockOperation {
// Used for quick debug to set first page to Apple and final page to first link on page.
if WKRKitConstants.current.isQuickRaceMode {
let startingURL = URL(string: "https://en.m.wikipedia.org/wiki/Apple_Inc.")!
startingPage = WKRPage(title: "Apple Inc.", url: startingURL)

let endingURL = URL(string: "https://en.m.wikipedia.org/wiki/Apple_Park")!
let fakeEnd = WKRPage(title: "Apple Park", url: endingURL)

potentialFinalPages.removeLast()
potentialFinalPages.insert(fakeEnd, at: 0)
}

// Uses a set to remove any duplicates. This could happen if two final articles end up redirecting to the same page.
let finalPages = Array(Set(potentialFinalPages.prefix(WKRKitConstants.current.votingArticlesCount)))

let events = logEvents.compactMap { $0 }
if !finalPages.isEmpty, let page = startingPage {
let config = WKRPreRaceConfig(startingPage: page, votingState: WKRVotingState(pages: finalPages))
var config = WKRPreRaceConfig(startingPage: page, votingState: WKRVotingState(pages: finalPages))
if WKRDefaults.isFastlaneSnapshotInstance, let page = fastlaneFinalPage {
config.votingState.fastlaneVotes(finalPage: page)
}
completionHandler(config, events)
} else {
completionHandler(nil, events)
}
}

// Gets the starting page
let startingPageOperation = WKROperation()
startingPageOperation.addExecutionBlock { [unowned startingPageOperation] in
switch settings.startPage {
case .random:
WKRPageFetcher.fetchRandom { page in
startingPage = page
startingPageOperation.state = .isFinished
if WKRDefaults.isFastlaneSnapshotInstance {
if WKRLanguageHackery.shared.isEnglish {
guard let url = URL(string: "https://en.m.wikipedia.org/wiki/Walt_Disney_World_Monorail_System") else { fatalError() }
startingPage = WKRPage(title: "Walt Disney World Monorail System", url: url)
startingPageOperation.state = .isFinished
} else {
WKRLanguageHackery.shared.adjustedPath(for: "/Walt_Disney") { path in
guard let path = path else { fatalError() }
WKRPageFetcher.fetch(path: path, useCache: true) { page, _ in
guard let page = page else { fatalError() }
startingPage = page
startingPageOperation.state = .isFinished
}
}
}
} else {
WKRPageFetcher.fetchRandom { page in
startingPage = page
startingPageOperation.state = .isFinished
os_log("fetched random start: %{public}s", log: .articlesValidation, type: .info, page?.title ?? "-")
}
}
case .custom(let page):
startingPage = page
startingPageOperation.state = .isFinished
}
}
completedOperation.addDependency(startingPageOperation)

let endingPageOperations: [BlockOperation]
switch settings.endPage {
case .curatedVoting:
@@ -104,39 +127,63 @@ public struct WKRPreRaceConfig: Codable, Equatable {
}
var randomPaths = [String]()
let numberOfPagesToFetch = WKRKitConstants.current.votingArticlesCount + 1

// pages are suffled so we can just take index 0-n from the shuffled array
for index in 0..<numberOfPagesToFetch where index < finalArticles.count {
randomPaths.append(finalArticles[index])
}


var fastlanePath: String?
if WKRDefaults.isFastlaneSnapshotInstance {
if WKRLanguageHackery.shared.isEnglish {
randomPaths[0] = "/Star_Wars:_Galaxy%27s_Edge"
} else {
randomPaths[0] = "/Magic_Kingdom"
}
fastlanePath = randomPaths[0]
}

// All the operations for get WKRPage objects to vote on
endingPageOperations = randomPaths.map { path -> WKROperation in
let operation = WKROperation()
operation.addExecutionBlock { [unowned operation] in
// don't use cache to make sure to get most recent page
WKRPageFetcher.fetch(path: path, useCache: false) { page, isRedirect in
// 1. Make sure not redirect
// 2. Make sure page not nil
// 3. Make sure page not already in voting list for this race
// 4. Make sure page is not a link to a section "/USA#History"
// 5. Sometimes removed pages redirect to the Wikipedia homepage.
// 6. Make sure path in unseen
// 7/8. Make sure link not equal to starting page
if !isRedirect,
let page = page,
!potentialFinalPages.contains(page),
!page.url.absoluteString.contains("#"),
page.title != "Wikipedia, the free encyclopedia",
finalArticles.contains(page.path),
let startingPage = startingPage,
startingPage.url.absoluteString.lowercased() != page.url.absoluteString.lowercased() {
potentialFinalPages.append(page)
} else {
logEvents.append(WKRLogEvent(type: .votingArticleValidationFailure,
attributes: ["PagePath": path]))

WKRLanguageHackery.shared.adjustedPath(for: path) { adjustedPath in
guard let adjustedPath = adjustedPath else {
operation.state = .isFinished
os_log("fetched error: no adjusted path", log: .articlesValidation, type: .error)
return
}

// don't use cache to make sure to get most recent page
WKRPageFetcher.fetch(path: adjustedPath, useCache: false) { page, isRedirect in
// 1. Make sure not redirect
// 2. Make sure page not nil
// 3. Make sure page not already in voting list for this race
// 4. Make sure page is not a link to a section "/USA#History"
// 5. Sometimes removed pages redirect to the Wikipedia homepage.
// 6. Make sure path in unseen
// 7/8. Make sure link not equal to starting page
if !isRedirect,
let page = page,
!potentialFinalPages.contains(page),
!page.url.absoluteString.contains("#"),
page.title != "Wikipedia, the free encyclopedia",
let startingPage = startingPage,
startingPage.url.absoluteString.lowercased() != page.url.absoluteString.lowercased() {
potentialFinalPages.append(page)
os_log("fetched final: %{public}s", log: .articlesValidation, type: .info, page.title ?? "-")
} else {
logEvents.append(WKRLogEvent(type: .votingArticleValidationFailure,
attributes: ["PagePath": adjustedPath]))
os_log("fetched error final: %{public}s", log: .articlesValidation, type: .error, page?.title ?? "-")
}

if WKRDefaults.isFastlaneSnapshotInstance && fastlanePath == path {
fastlaneFinalPage = page
}
operation.state = .isFinished
}
operation.state = .isFinished
}
}
operation.addDependency(startingPageOperation)
@@ -145,7 +192,7 @@ public struct WKRPreRaceConfig: Codable, Equatable {
}
case .randomVoting:
let numberOfPagesToFetch = Int(Double(WKRKitConstants.current.votingArticlesCount) * 1.5)

// All the operations for get WKRPage objects to vote on
endingPageOperations = (0..<numberOfPagesToFetch).map { _ -> WKROperation in
let operation = WKROperation()
@@ -157,7 +204,7 @@ public struct WKRPreRaceConfig: Codable, Equatable {
let title = page.title,
!page.url.absoluteString.contains("#"),
title != "Wikipedia, the free encyclopedia",
title.count < WKRKitConstants.current.pageTitleMaxRandomLength {
title.count < WKRKitConstants.current.pageTitleMaxRandomLength {
potentialFinalPages.append(page)
} else {
// Don't log the analytics, keep validation to just curated articles
@@ -179,9 +226,9 @@ public struct WKRPreRaceConfig: Codable, Equatable {
completedOperation.addDependency(operation)
endingPageOperations = [operation]
}

operationQueue.addOperations([startingPageOperation, completedOperation], waitUntilFinished: false)
operationQueue.addOperations(endingPageOperations, waitUntilFinished: false)
}

}
16 changes: 15 additions & 1 deletion WKRKit/WKRKit/Game/WKRRaceSettings.swift
Original file line number Diff line number Diff line change
@@ -106,6 +106,18 @@ public class WKRGameSettings: Codable {
}
}

public struct Language: Codable {
public let code: String

public var isStandard: Bool {
return code == "en"
}

public init(code: String) {
self.code = code
}
}

// MARK: - Properties -

public var isCustom: Bool {
@@ -114,7 +126,8 @@ public class WKRGameSettings: Codable {
&& timing.isStandard
&& other.isStandard
&& startPage.isStandard
&& endPage.isStandard,
&& endPage.isStandard
&& language.isStandard,
bannedPages.count == 1,
case .portal = bannedPages[0] {
return false
@@ -137,6 +150,7 @@ public class WKRGameSettings: Codable {
public var points = Points(bonusPointReward: WKRKitConstants.current.bonusPointReward, bonusPointsInterval: WKRKitConstants.current.bonusPointsInterval)
public var timing = Timing(votingTime: WKRRaceDurationConstants.votingState, resultsTime: WKRRaceDurationConstants.resultsState)
public var other = Other(isHelpEnabled: true)
public var language = Language(code: "en")

public func reset() {
startPage = .random
13 changes: 13 additions & 0 deletions WKRKit/WKRKit/Game/WKRVotingState.swift
Original file line number Diff line number Diff line change
@@ -91,6 +91,19 @@ public struct WKRVotingState: Codable, Equatable {
return (pagesWithMostVotes.randomElement, logEvent)
}

mutating func fastlaneVotes(finalPage: WKRPage) {
var copy = pages
guard let index = copy.firstIndex(where: { $0 == finalPage }) else { fatalError() }
copy.remove(at: index)
copy.shuffle()
player(WKRPlayerProfile(name: "A", playerID: "A"), votedFor: finalPage)
player(WKRPlayerProfile(name: "G", playerID: "G"), votedFor: finalPage)

player(WKRPlayerProfile(name: "C", playerID: "C"), votedFor: copy[0])
player(WKRPlayerProfile(name: "M", playerID: "M"), votedFor: copy[1])
player(WKRPlayerProfile(name: "X", playerID: "X"), votedFor: copy[2])
}

// MARK: - Public Accessors

public var current: [(page: WKRPage, voters: [WKRPlayerProfile])] {
2 changes: 1 addition & 1 deletion WKRKit/WKRKit/Info.plist
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>13082</string>
<string>13396</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
2 changes: 1 addition & 1 deletion WKRKit/WKRKit/Manager/WKRManager+PageNavigation.swift
Original file line number Diff line number Diff line change
@@ -111,7 +111,7 @@ extension WKRGameManager {

private func truncated(url: URL) -> String {
var reportedURLString = url.absoluteString
if reportedURLString.starts(with: "https://en.m.wikipedia.org/wiki/") {
if reportedURLString.starts(with: WKRLanguageHackery.shared.baseURLString) {
let index = reportedURLString.index(reportedURLString.startIndex, offsetBy: 31)
reportedURLString = String(reportedURLString[index...])
}
2 changes: 2 additions & 0 deletions WKRKit/WKRKit/Manager/WKRManager.swift
Original file line number Diff line number Diff line change
@@ -89,6 +89,8 @@ final public class WKRGameManager {
self.votingUpdate = votingUpdate
self.resultsUpdate = resultsUpdate

WKRLanguageHackery.shared.configure(for: settings)

let setup = networkConfig.create()
localPlayer = setup.player
peerNetwork = setup.network
3 changes: 2 additions & 1 deletion WKRKit/WKRKit/Other/OSLog+WKRKit.swift
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ extension OSLog {
// MARK: - Types -

private enum CustomCategory: String {
case constants, seenArticlesStore
case constants, seenArticlesStore, articlesValidation
}

private static let subsystem: String = {
@@ -24,5 +24,6 @@ extension OSLog {

static let constants = OSLog(subsystem: subsystem, category: CustomCategory.constants.rawValue)
static let seenArticlesStore = OSLog(subsystem: subsystem, category: CustomCategory.seenArticlesStore.rawValue)
static let articlesValidation = OSLog(subsystem: subsystem, category: CustomCategory.articlesValidation.rawValue)

}
18 changes: 18 additions & 0 deletions WKRKit/WKRKit/Other/WKRDefaults.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// WKRDefaults.swift
// WKRKit
//
// Created by Andrew Finke on 7/18/20.
// Copyright © 2020 Andrew Finke. All rights reserved.
//

import Foundation

struct WKRDefaults {
private static let fastlaneKey = "FASTLANE_SNAPSHOT"
static var isFastlaneSnapshotInstance: Bool {
get {
return UserDefaults.standard.bool(forKey: fastlaneKey)
}
}
}
124 changes: 124 additions & 0 deletions WKRKit/WKRKit/Other/WKRLanguageHackery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// WKRLanguageHackery.swift
// WKRKit
//
// Created by Andrew Finke on 7/17/20.
// Copyright © 2020 Andrew Finke. All rights reserved.
//

import Foundation
import os.log

/// A terrible, terrible approach, but I don't want to spend much time on this. + code quality/arch doesn't matter to me that much anymore given I will no longer be working on this project in a few weeks
internal class WKRLanguageHackery {

// MARK: - Types-

private struct WikiDataResponse: Decodable {
struct Entity: Decodable {
struct SiteLink: Decodable {
let url: String
}
let sitelinks: [String: SiteLink]
}

private let entities: [String: Entity]
func path(for language: String) -> String? {
let site = "\(language).wikipedia.org/wiki"
let urlString = entities
.values
.map { $0.sitelinks.values }
.flatMap { $0 }
.map { $0.url }
.first(where: { $0.contains(site) })

guard let string = urlString else {
return nil
}

let components = string.components(separatedBy: site)
guard components.count == 2 else {
return nil
}

return components[1]
}
}

// MARK: - Properties

static private let session: URLSession = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 2
config.timeoutIntervalForResource = 2
return URLSession(configuration: config)
}()

static let shared = WKRLanguageHackery()
private var language = "en"

var isEnglish: Bool {
return language == "en"
}

// MARK: - Initalization -

private init() {}

// MARK: - Helpers -

func configure(for settings: WKRGameSettings) {
language = settings.language.code
}

var baseURLString: String {
return WKRKitConstants.current.baseURLString.replacingOccurrences(of: "en", with: language)
}

var whatLinksHereURLString: String {
return WKRKitConstants.current.whatLinksHereURLString.replacingOccurrences(of: "en", with: language)
}

var randomURLString: String {
return WKRKitConstants.current.randomURLString.replacingOccurrences(of: "en", with: language)
}

var pageTitleStringToReplace: String {
if language == "es" {
return " - Wikipedia, la enciclopedia libre"
} else if language == "fr" {
return " — Wikipédia"
} else if language == "de" {
return " – Wikipedia"
} else if language == "ru" {
return " — Википедия"
} else {
return WKRKitConstants.current.pageTitleStringToReplace
}
}

func adjustedPath(for path: String, completion: @escaping ((String?) -> Void)) {
guard language != "en" else {
completion(path)
return
}

let adjustedPath = path.dropFirst()
let urlString = "https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&sites=enwiki&redirects=no&props=sitelinks%2Furls&titles=\(adjustedPath)"
guard let url = URL(string: urlString) else {
completion(nil)
return
}
let task = WKRLanguageHackery.session.dataTask(with: url) { data, _, _ in
if let data = data, let response = try? JSONDecoder().decode(WikiDataResponse.self, from: data) {
let newPath = response.path(for: self.language)
os_log("fetched adjusted path: %{public}s -> %{public}s", log: .articlesValidation, type: .info, path, newPath ?? "-")
completion(newPath)
} else {
completion(nil)
}
}
task.resume()
}

}
4 changes: 2 additions & 2 deletions WKRKit/WKRKit/Pages/WKRPage.swift
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ public struct WKRPage: Codable, Hashable, Equatable {
public init(title: String?, url: URL) {
self.title = WKRPage.formattedTitle(for: title)
self.url = url
self.path = url.absoluteString.replacingOccurrences(of: "https://en.m.wikipedia.org/wiki", with: "")
self.path = url.absoluteString.replacingOccurrences(of: WKRLanguageHackery.shared.baseURLString, with: "")
}

// MARK: - Helpers
@@ -56,7 +56,7 @@ public struct WKRPage: Codable, Hashable, Equatable {
return smartFormat(String(clippedTitle))
} else {
// The expected path
let title = title.replacingOccurrences(of: WKRKitConstants.current.pageTitleStringToReplace, with: "")
let title = title.replacingOccurrences(of: WKRLanguageHackery.shared.pageTitleStringToReplace, with: "")
return smartFormat(title)
}
}
31 changes: 21 additions & 10 deletions WKRKit/WKRKit/Pages/WKRPageFetcher.swift
Original file line number Diff line number Diff line change
@@ -35,8 +35,8 @@ public struct WKRPageFetcher {
/// Returns the title from the raw HTML
private static func title(from string: String) -> String? {
guard let titleAttributeStart = string.range(of: "<title>"),
let titleAttributeEnd = string.range(of: "</title>") else {
return nil
let titleAttributeEnd = string.range(of: "</title>") else {
return nil
}
let range = Range(uncheckedBounds: (titleAttributeStart.upperBound, titleAttributeEnd.lowerBound))
return String(string[range])
@@ -46,7 +46,7 @@ public struct WKRPageFetcher {

/// Fetches Wikipedia page with path ("/Apple_Inc.")
public static func fetch(path: String, useCache: Bool, completionHandler: @escaping ((_ page: WKRPage?, _ isRedirect: Bool) -> Void)) {
guard let url = URL(string: WKRKitConstants.current.baseURLString + path) else {
guard let url = URL(string: WKRLanguageHackery.shared.baseURLString + path) else {
completionHandler(nil, false)
return
}
@@ -55,7 +55,7 @@ public struct WKRPageFetcher {

/// Fetches a random Wikipedia page
static func fetchRandom(completionHandler: @escaping ((_ page: WKRPage?) -> Void)) {
guard let url = URL(string: WKRKitConstants.current.randomURLString) else {
guard let url = URL(string: WKRLanguageHackery.shared.randomURLString) else {
completionHandler(nil)
return
}
@@ -73,7 +73,11 @@ public struct WKRPageFetcher {
session = WKRPageFetcher.noCacheSession
}
let task = session.dataTask(with: url) { (data, response, _) in
if let data = data, let string = String(data: data, encoding: .utf8), let responseUrl = response?.url {
if let response = response as? HTTPURLResponse,
response.statusCode != 404,
let data = data,
let string = String(data: data, encoding: .utf8),
let responseUrl = response.url {
let page = WKRPage(title: title(from: string), url: responseUrl)
let isRedirect = string.contains("Redirected from")
completionHandler(page, isRedirect)
@@ -87,7 +91,7 @@ public struct WKRPageFetcher {
/// Fetches a Wikipedia page source.
static func fetchSource(url: URL,
useCache: Bool,
progressHandler: @escaping (_ progress: Float) -> Void,
progressHandler: ((_ progress: Float) -> Void)?,
completionHandler: @escaping (_ source: String?, _ error: Error?) -> Void) {
let session: URLSession
if useCache {
@@ -97,17 +101,24 @@ public struct WKRPageFetcher {
}

var observation: NSKeyValueObservation?
let task = session.dataTask(with: url) { (data, _, error) in
let task = session.dataTask(with: url) { (data, response, error) in
observation?.invalidate()
if let data = data, let string = String(data: data, encoding: .utf8) {
if let response = response as? HTTPURLResponse,
response.statusCode != 404,
let data = data,
let string = String(data: data, encoding: .utf8) {
completionHandler(string, nil)
} else {
completionHandler(nil, error)
}
}
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
progressHandler(Float(progress.fractionCompleted))

if let progressHandler = progressHandler {
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
progressHandler(Float(progress.fractionCompleted))
}
}

task.resume()
}

2 changes: 1 addition & 1 deletion WKRKit/WKRKit/Web Logic/WKRLinkedPagesFetcher.swift
Original file line number Diff line number Diff line change
@@ -85,7 +85,7 @@ final internal class WKRLinkedPagesFetcher: NSObject, WKScriptMessageHandler {

func start(for page: WKRPage) {
let query = "&namespace=0&limit=500&hidetrans=1"
guard let url = URL(string: WKRKitConstants.current.whatLinksHereURLString + page.path + query) else { return }
guard let url = URL(string: WKRLanguageHackery.shared.whatLinksHereURLString + page.path + query) else { return }
load(url: url)
}

2 changes: 1 addition & 1 deletion WKRKit/WKRKit/Web Logic/WKRPageNavigation.swift
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ final internal class WKRPageNavigation: NSObject, WKNavigationDelegate {
}
}

return urlString.contains(WKRKitConstants.current.baseURLString)
return urlString.contains(WKRLanguageHackery.shared.baseURLString)
}

// MARK: - WKNavigationDelegate
4 changes: 2 additions & 2 deletions WKRUIKit/WKRUIKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -619,7 +619,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2020.07;
MARKETING_VERSION = 2020.08;
PRODUCT_BUNDLE_IDENTIFIER = com.andrewfinke.WKRUIKit;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -647,7 +647,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2020.07;
MARKETING_VERSION = 2020.08;
PRODUCT_BUNDLE_IDENTIFIER = com.andrewfinke.WKRUIKit;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
2 changes: 1 addition & 1 deletion WKRUIKit/WKRUIKit/Constants/WKRUIKitConstants.swift
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ public struct WKRUIKitConstants {

static let alertViewHeight: CGFloat = 50.0
static let alertViewImageHeight: CGFloat = 22
static let alertViewImagePadding: CGFloat = 5
static let alertViewImagePadding: CGFloat = 6
static let alertAnimateInDuration = 0.2
static let alertAnimateOutDuration = 0.15
public static let alertDefaultDuration = 3.0
2 changes: 1 addition & 1 deletion WKRUIKit/WKRUIKit/Info.plist
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>13985</string>
<string>14299</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
12 changes: 6 additions & 6 deletions WKRUIKit/WKRUIKit/WKRPlayerProfile.swift
Original file line number Diff line number Diff line change
@@ -11,30 +11,30 @@ import SwiftUI
import UIKit

public struct WKRPlayerProfile: Identifiable, Equatable, Hashable, Codable {

// MARK: - Properties -

public var id: String {
return playerID
}
public let name: String
public let playerID: String

public var image: Image { Image(uiImage: rawImage) }
public var rawImage: UIImage { WKRUIPlayerImageManager.shared.image(for: name) }

// MARK: - Initalization -

public init(name: String, playerID: String) {
self.name = name
self.playerID = playerID
}

public init(player: GKPlayer) {
self.name = player.displayName
self.playerID = player.alias
}

public static func ==(lhs: WKRPlayerProfile, rhs: WKRPlayerProfile) -> Bool {
return lhs.playerID == rhs.playerID
}
2 changes: 1 addition & 1 deletion WKRUIKit/WKRUIKit/WKRUIPlayerImageManager.swift
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ public class WKRUIPlayerImageManager {
}
}
}

@discardableResult
private func generatePlaceholder(for player: String) -> UIImage {
let placeholder = WKRUIPlayerPlaceholderImageRenderer.render(name: player)
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// CustomRaceLanguageController.swift
// WikiRaces
//
// Created by Andrew Finke on 7/17/20.
// Copyright © 2020 Andrew Finke. All rights reserved.
//

import UIKit
import WKRKit

final class CustomRaceLanguageController: CustomRaceController {

// MARK: - Types -

private class Cell: UITableViewCell, UITextFieldDelegate {

// MARK: - Properties -

let textField = UITextField()
static let reuseIdentifier = "reuseIdentifier"

// MARK: - Initalization -

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
textField.textAlignment = .right
textField.returnKeyType = .done
textField.delegate = self
contentView.addSubview(textField)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - View Life Cycle -

override func layoutSubviews() {
super.layoutSubviews()
textField.frame = CGRect(origin: .zero, size: CGSize(width: 80, height: contentView.frame.height))
textField.center = CGPoint(
x: contentView.frame.width - contentView.layoutMargins.right - textField.frame.width / 2,
y: contentView.frame.height / 2)
}

// MARK: - UITextFieldDelegate -

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}

// MARK: - Properties -

var language: WKRGameSettings.Language {
didSet {
didUpdate?(language)
}
}
var didUpdate: ((WKRGameSettings.Language) -> Void)?
private var textField: UITextField?

// MARK: - Initalization -

init(language: WKRGameSettings.Language) {
self.language = language
super.init(style: .grouped)
title = "Language".uppercased()
tableView.allowsSelection = false
tableView.register(Cell.self, forCellReuseIdentifier: Cell.reuseIdentifier)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - View Life Cycle -

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let text = textField?.text?.lowercased(), text != language.code {
language = WKRGameSettings.Language(code: text)
}
}

// MARK: - UITableViewDataSource -

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: Cell.reuseIdentifier,
for: indexPath) as? Cell else {
fatalError()
}
switch indexPath.row {
case 0:
cell.textLabel?.text = "Language Code"
cell.textField.text = language.code.lowercased()
textField = cell.textField
default:
fatalError()
}

return cell
}

override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return """
Changing the Wikipedia language is highly experimental and not fully tested.
Some aspects of the game may not work as expected when using any language other than the standard English Wikipedia (“en”).
"""
}

}
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ final class CustomRaceViewController: CustomRaceController {
case endPage = "End Page"
case bannedPages = "Banned Pages"
case notifications = "Player Messages"
case points, timing, other
case points, timing, other, language
}

// MARK: - Properties -
@@ -217,6 +217,16 @@ final class CustomRaceViewController: CustomRaceController {
tableView.reloadData()
}
}
case .language:
let controller = CustomRaceLanguageController(language: settings.language)
navigationController?.pushViewController(controller, animated: true)
controller.didUpdate = { [weak self] language in
guard let self = self else { return }
self.settings.language = language
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
}

@@ -266,6 +276,12 @@ final class CustomRaceViewController: CustomRaceController {
}
case .other:
return ""
case .language:
if settings.language.isStandard {
return "English"
} else {
return settings.language.code.lowercased()
}
}
}

Original file line number Diff line number Diff line change
@@ -62,7 +62,12 @@ extension GKHostViewController: GKMatchDelegate {
self.match?.delegate = self
self.addPlayers()
} else {
fatalError()
os_log("%{public}s- gamekit did the impossible, no error, no match, nothing to see here", log: .gameKit, type: .error, #function)
DispatchQueue.main.async {
self.isMatchmakingEnabled = false
self.showError(title: "Failed to Create Race", message: "Please try again later.")
self.model.state = .soloRace
}
}
}

Original file line number Diff line number Diff line change
@@ -52,8 +52,18 @@ final internal class GKHostViewController: GKConnectViewController {

init() {
super.init(isPlayerHost: true)
startMatchmaking()
WKRSeenFinalArticlesStore.resetRemotePlayersSeenFinalArticles()
if Defaults.isFastlaneSnapshotInstance {
model.raceCode = "APPLE"
model.state = .showingRacers
model.connectedPlayers = [
WKRPlayerProfile(name: "C", playerID: "C"),
WKRPlayerProfile(name: "G", playerID: "G"),
WKRPlayerProfile(name: "M", playerID: "M"),
WKRPlayerProfile(name: "X", playerID: "X")
]
} else {
startMatchmaking()
}
}

required init?(coder: NSCoder) {
@@ -76,7 +86,7 @@ final internal class GKHostViewController: GKConnectViewController {
self?.contentViewHosting.view.alpha = 1
}

guard !Defaults.promptedAutoInvite else {
guard !Defaults.promptedAutoInvite && !Defaults.isFastlaneSnapshotInstance else {
return
}
Defaults.promptedAutoInvite = true
@@ -97,6 +107,7 @@ final internal class GKHostViewController: GKConnectViewController {
os_log("%{public}s: disabled auto invite", log: .gameKit, type: .info, #function)
}
controller.addAction(cancelAction)

present(controller, animated: true, completion: nil)
}

@@ -108,6 +119,18 @@ final internal class GKHostViewController: GKConnectViewController {
// MARK: - Actions -

func startMatch() {
if Defaults.isFastlaneSnapshotInstance {
guard let code = Locale.preferredLanguages.first?.split(separator: "-").first else { fatalError() }
model.settings.language = WKRGameSettings.Language(code: String(code))
let controller = GameViewController(network: .solo(name: "_"), settings: model.settings)
let nav = WKRUINavigationController(rootViewController: controller)
nav.modalPresentationStyle = .fullScreen
nav.modalTransitionStyle = .crossDissolve
nav.isModalInPresentation = true
present(nav, animated: false)
return
}

os_log("%{public}s", log: .gameKit, type: .info, #function)
PlayerFirebaseAnalytics.log(event: .userAction(#function))

Original file line number Diff line number Diff line change
@@ -55,13 +55,22 @@ struct HostContentView: View {
.allowsHitTesting(model.state != .raceStarting)

Spacer()
WKRUIPlayerImageView(
player: WKRPlayerProfile(player: GKLocalPlayer.local),
size: 100,
effectSize: 5)
.padding(.bottom, 20)
if Defaults.isFastlaneSnapshotInstance {
WKRUIPlayerImageView(
player: WKRPlayerProfile(name: "A", playerID: "A"),
size: 100,
effectSize: 5)
.padding(.bottom, 20)
} else {
WKRUIPlayerImageView(
player: WKRPlayerProfile(player: GKLocalPlayer.local),
size: 100,
effectSize: 5)
.padding(.bottom, 20)
}


if !WKRUIPlayerImageManager.shared.isLocalPlayerImageFromGameCenter {
if !WKRUIPlayerImageManager.shared.isLocalPlayerImageFromGameCenter && !Defaults.isFastlaneSnapshotInstance {
HStack {
Spacer()
Text("Set a custom racer photo\nin the Game Center settings")
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ extension MenuView {

PlayerCloudKitLiveRaceManager.shared.isCloudEnabled { isEnabled in
DispatchQueue.main.async {
if isEnabled {
if isEnabled || Defaults.isFastlaneSnapshotInstance {
self.animateMenuOut {
self.listenerUpdate?(.presentCreateRace)
}
Original file line number Diff line number Diff line change
@@ -209,10 +209,15 @@ final internal class MenuViewController: UIViewController {

func createRace() {
if Defaults.isFastlaneSnapshotInstance {
let controller = GameViewController(network: .solo(name: "_"), settings: WKRGameSettings())
for player in ["A", "C", "G", "M", "X"] {
let _ = WKRUIPlayerImageManager.shared.image(for: player)
}

let controller = GKHostViewController()
let nav = WKRUINavigationController(rootViewController: controller)
nav.modalPresentationStyle = .overCurrentContext
present(nav, animated: true, completion: nil)
nav.setNavigationBarHidden(true, animated: false)
present(nav, animated: false, completion: nil)
} else {
prepareForRace(completion: {
let controller = RaceChecksViewController(destination: .hostPrivate)
Original file line number Diff line number Diff line change
@@ -61,6 +61,6 @@ extension GKMatchRequest {
}

private static func publicRacePlayerGroup() -> Int {
return 10
return 11
}
}
Original file line number Diff line number Diff line change
@@ -102,7 +102,7 @@ class RaceCodeGenerator {
guard raceCode.count < 10 else { return -1 }

let formattedCode = raceCode.lowercased()
var playerGroup = formattedCode.count
var playerGroup = formattedCode.count + 1 // + X == force version compatibility due to App Store Connect being broken
for (index, char) in formattedCode.enumerated() {
let charValue: Int = (validCharactersArray.firstIndex(of: char) ?? 50) + 1
let offset = Int(pow(Double(10), Double(index * 2) + 1))
Original file line number Diff line number Diff line change
@@ -39,10 +39,6 @@ extension GameViewController {
navigationItem.leftBarButtonItem = nil
navigationItem.rightBarButtonItem = nil

if Defaults.isFastlaneSnapshotInstance {
navigationItem.leftBarButtonItem = helpBarButtonItem
navigationItem.rightBarButtonItem = quitBarButtonItem
}
navigationView.addSubview(navigationBarBottomLine)

setupElements()
@@ -64,7 +60,6 @@ extension GameViewController {
]
NSLayoutConstraint.activate(constraints)

// view.alpha = 0
navigationController.setNavigationBarHidden(true, animated: false)
}

@@ -117,9 +112,7 @@ extension GameViewController {
]
NSLayoutConstraint.activate(constraints)

if !Defaults.isFastlaneSnapshotInstance {
gameManager.webView = webView
}
gameManager.webView = webView
self.webView = webView
}

Original file line number Diff line number Diff line change
@@ -92,24 +92,13 @@ final internal class GameViewController: UIViewController {
// MARK: - View Life Cycle

override func viewDidLoad() {
super.viewDidLoad()
if Defaults.isFastlaneSnapshotInstance {
setupInterface()
let url = URL(string: "https://en.m.wikipedia.org/wiki/Walt_Disney_World_Monorail_System")!
prepareForScreenshots(for: url)
} else {
setupGameManager()
setupInterface()
}
setupGameManager()
setupInterface()
}

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

// if Defaults.isFastlaneSnapshotInstance {
// return
// }

if !isConfigured {
isConfigured = true
initalConfiguration()
68 changes: 59 additions & 9 deletions WikiRaces/WikiRaces.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1150"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -27,24 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "function version {&#10; echo &quot;$@&quot; | awk -F. &apos;{ printf(&quot;%d%03d%03d%03d\n&quot;, $1,$2,$3,$4); }&apos;;&#10;}&#10;&#10;# Don&#x2019;t run on iOS devices.&#10;if [[ &quot;${SDKROOT}&quot; != *&quot;simulator&quot;* ]]; then&#10; exit 0&#10;fi&#10;&#10;# Don&#x2019;t run on iOS versions before 13.&#10;if [ $(version &quot;${TARGET_DEVICE_OS_VERSION}&quot;) -ge $(version &quot;13&quot;) ]; then&#10; xcrun simctl boot &quot;${TARGET_DEVICE_IDENTIFIER}&quot;&#10;&#10; xcrun simctl status_bar &quot;${TARGET_DEVICE_IDENTIFIER}&quot; override \&#10; --time &quot;9:41 AM&quot; \&#10; --dataNetwork wifi \&#10; --wifiMode active \&#10; --wifiBars 3 \&#10; --cellularMode notSupported \&#10; --batteryState discharging \&#10; --batteryLevel 100&#10;fi&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "149FF88D1F362BF1000A5D96"
BuildableName = "WikiRacesScreenshots.xctest"
BlueprintName = "WikiRacesScreenshots"
ReferencedContainer = "container:WikiRaces.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<Testables>
<TestableReference
skipped = "NO">
2 changes: 1 addition & 1 deletion WikiRaces/WikiRaces/Info.plist
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>8776</string>
<string>8994</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
53 changes: 16 additions & 37 deletions WikiRaces/WikiRacesScreenshots/SnapshotHelper.swift
Original file line number Diff line number Diff line change
@@ -38,22 +38,13 @@ func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
}

enum SnapshotError: Error, CustomDebugStringConvertible {
case cannotDetectUser
case cannotFindHomeDirectory
case cannotFindSimulatorHomeDirectory
case cannotAccessSimulatorHomeDirectory(String)
case cannotRunOnPhysicalDevice

var debugDescription: String {
switch self {
case .cannotDetectUser:
return "Couldn't find Snapshot configuration files - can't detect current user "
case .cannotFindHomeDirectory:
return "Couldn't find Snapshot configuration files - can't detect `Users` dir"
case .cannotFindSimulatorHomeDirectory:
return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
case .cannotRunOnPhysicalDevice:
return "Can't use Snapshot on a physical device."
}
@@ -75,7 +66,7 @@ open class Snapshot: NSObject {
Snapshot.waitForAnimations = waitForAnimations

do {
let cacheDir = try pathPrefix()
let cacheDir = try getCacheDirectory()
Snapshot.cacheDirectory = cacheDir
setLanguage(app)
setLocale(app)
@@ -206,51 +197,39 @@ open class Snapshot: NSObject {
_ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
}

class func pathPrefix() throws -> URL? {
let homeDir: URL
class func getCacheDirectory() throws -> URL {
let cachePath = "Library/Caches/tools.fastlane"
// on OSX config is stored in /Users/<username>/Library
// and on iOS/tvOS/WatchOS it's in simulator's home dir
#if os(OSX)
guard let user = ProcessInfo().environment["USER"] else {
throw SnapshotError.cannotDetectUser
let homeDir = URL(fileURLWithPath: NSHomeDirectory())
return homeDir.appendingPathComponent(cachePath)
#elseif arch(i386) || arch(x86_64)
guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
throw SnapshotError.cannotFindSimulatorHomeDirectory
}

guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
throw SnapshotError.cannotFindHomeDirectory
}

homeDir = usersDir.appendingPathComponent(user)
let homeDir = URL(fileURLWithPath: simulatorHostHome)
return homeDir.appendingPathComponent(cachePath)
#else
#if arch(i386) || arch(x86_64)
guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
throw SnapshotError.cannotFindSimulatorHomeDirectory
}
guard let homeDirUrl = URL(string: simulatorHostHome) else {
throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
}
homeDir = URL(fileURLWithPath: homeDirUrl.path)
#else
throw SnapshotError.cannotRunOnPhysicalDevice
#endif
throw SnapshotError.cannotRunOnPhysicalDevice
#endif
return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
}
}

private extension XCUIElementAttributes {
var isNetworkLoadingIndicator: Bool {
if hasWhiteListedIdentifier { return false }
if hasAllowListedIdentifier { return false }

let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)

return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
}

var hasWhiteListedIdentifier: Bool {
let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
var hasAllowListedIdentifier: Bool {
let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]

return whiteListedIdentifiers.contains(identifier)
return allowListedIdentifiers.contains(identifier)
}

func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
@@ -300,4 +279,4 @@ private extension CGFloat {

// Please don't remove the lines below
// They are used to detect outdated configuration files
// SnapshotHelperVersion [1.211]
// SnapshotHelperVersion [1.23]
34 changes: 19 additions & 15 deletions WikiRaces/WikiRacesScreenshots/WikiRacesScreenshots.swift
Original file line number Diff line number Diff line change
@@ -9,29 +9,33 @@
import XCTest

class WikiRacesScreenshots: XCTestCase {


let app = XCUIApplication()

override func setUp() {
super.setUp()
continueAfterFailure = false

let app = XCUIApplication()

setupSnapshot(app)
app.launch()
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.

super.tearDown()
}


func testExample() {
let prefix = "2_DARK"
sleep(2)
XCUIDevice.shared.orientation = .landscapeLeft
snapshot("1_portrait")
XCUIApplication().buttons["GLOBAL RACE"].tap()
sleep(5)
snapshot("2_game")
snapshot(prefix + "_1_menu")

app.buttons["CREATE"].tap()
sleep(1)
snapshot(prefix + "_2_host")

app.buttons["play.fill"].tap()
sleep(6)
snapshot(prefix + "_3_voting")

sleep(12)
snapshot(prefix + "_4_game")
}

}
2 changes: 1 addition & 1 deletion WikiRaces/fastlane/Deliverfile
Original file line number Diff line number Diff line change
@@ -10,5 +10,5 @@ app_identifier "com.andrewfinke.wikiraces" # The bundle identifier of your app
skip_binary_upload true
skip_metadata true
skip_app_version_update true
overwrite_screenshots true
overwrite_screenshots false
run_precheck_before_submit false
16 changes: 13 additions & 3 deletions WikiRaces/fastlane/Snapfile
Original file line number Diff line number Diff line change
@@ -2,16 +2,26 @@

# A list of devices you want to take the screenshots from
devices([
"iPad Pro",
"iPad Pro (12.9-inch) (4th generation)",
"iPhone 11 Pro Max",
"iPhone 8 Plus",
])

languages([
"en-US"
"en-US",
"nl-NL",
"fr-FR",
"de-DE",
"ja",
"ru",
"es-ES",
])

scheme "WikiRacesScreenshots"

dark_mode false
override_status_bar true

dark_mode true

clear_previous_screenshots false

0 comments on commit f30f580

Please sign in to comment.