Refactored the LintLocalizableStrings
Added inline errors & warnings (regex could use some work to remove invalid cases) Added a build step to validate the strings are included in the app and it's extensions
This commit is contained in:
parent
bfc5375a30
commit
5917cf103f
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
#!/usr/bin/env xcrun --sdk macosx swift
|
#!/usr/bin/env xcrun --sdk macosx swift
|
||||||
|
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// This script is used to generate/update the set of Emoji used for reactions
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// OWSAssertionError but for this script
|
// OWSAssertionError but for this script
|
||||||
|
@ -250,6 +256,7 @@ extension EmojiGenerator {
|
||||||
// e.g. case grinning = "😀"
|
// e.g. case grinning = "😀"
|
||||||
writeBlock(fileName: "Emoji.swift") { fileHandle in
|
writeBlock(fileName: "Emoji.swift") { fileHandle in
|
||||||
fileHandle.writeLine("// swiftlint:disable all")
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
|
fileHandle.writeLine("// stringlint:disable")
|
||||||
fileHandle.writeLine("")
|
fileHandle.writeLine("")
|
||||||
fileHandle.writeLine("/// A sorted representation of all available emoji")
|
fileHandle.writeLine("/// A sorted representation of all available emoji")
|
||||||
fileHandle.writeLine("enum Emoji: String, CaseIterable, Equatable {")
|
fileHandle.writeLine("enum Emoji: String, CaseIterable, Equatable {")
|
||||||
|
@ -306,6 +313,9 @@ extension EmojiGenerator {
|
||||||
// if rawValue == "😀" { self.init(baseEmoji: .grinning, skinTones: nil) }
|
// if rawValue == "😀" { self.init(baseEmoji: .grinning, skinTones: nil) }
|
||||||
// else if rawValue == "🦻🏻" { self.init(baseEmoji: .earWithHearingAid, skinTones: [.light])
|
// else if rawValue == "🦻🏻" { self.init(baseEmoji: .earWithHearingAid, skinTones: [.light])
|
||||||
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
writeBlock(fileName: "EmojiWithSkinTones+String.swift") { fileHandle in
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
|
fileHandle.writeLine("// stringlint:disable")
|
||||||
|
fileHandle.writeLine("")
|
||||||
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
fileHandle.writeLine("extension EmojiWithSkinTones {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
switch desiredStructure {
|
switch desiredStructure {
|
||||||
|
@ -372,6 +382,7 @@ extension EmojiGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,6 +447,9 @@ extension EmojiGenerator {
|
||||||
|
|
||||||
static func writeSkinToneLookupFile(from emojiModel: EmojiModel) {
|
static func writeSkinToneLookupFile(from emojiModel: EmojiModel) {
|
||||||
writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in
|
writeBlock(fileName: "Emoji+SkinTones.swift") { fileHandle in
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
|
fileHandle.writeLine("// stringlint:disable")
|
||||||
|
fileHandle.writeLine("")
|
||||||
fileHandle.writeLine("extension Emoji {")
|
fileHandle.writeLine("extension Emoji {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
// SkinTone enum
|
// SkinTone enum
|
||||||
|
@ -498,6 +512,7 @@ extension EmojiGenerator {
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
}
|
}
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,6 +529,9 @@ extension EmojiGenerator {
|
||||||
]
|
]
|
||||||
|
|
||||||
writeBlock(fileName: "Emoji+Category.swift") { fileHandle in
|
writeBlock(fileName: "Emoji+Category.swift") { fileHandle in
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
|
fileHandle.writeLine("// stringlint:disable")
|
||||||
|
fileHandle.writeLine("")
|
||||||
fileHandle.writeLine("extension Emoji {")
|
fileHandle.writeLine("extension Emoji {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
|
|
||||||
|
@ -619,6 +637,7 @@ extension EmojiGenerator {
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
}
|
}
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,6 +645,9 @@ extension EmojiGenerator {
|
||||||
// Name lookup: Create a computed property mapping an Emoji enum element to the raw Emoji name string
|
// Name lookup: Create a computed property mapping an Emoji enum element to the raw Emoji name string
|
||||||
// e.g. case .grinning: return "GRINNING FACE"
|
// e.g. case .grinning: return "GRINNING FACE"
|
||||||
writeBlock(fileName: "Emoji+Name.swift") { fileHandle in
|
writeBlock(fileName: "Emoji+Name.swift") { fileHandle in
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
|
fileHandle.writeLine("// stringlint:disable")
|
||||||
|
fileHandle.writeLine("")
|
||||||
fileHandle.writeLine("extension Emoji {")
|
fileHandle.writeLine("extension Emoji {")
|
||||||
fileHandle.indent {
|
fileHandle.indent {
|
||||||
fileHandle.writeLine("var name: String {")
|
fileHandle.writeLine("var name: String {")
|
||||||
|
@ -639,6 +661,7 @@ extension EmojiGenerator {
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
}
|
}
|
||||||
fileHandle.writeLine("}")
|
fileHandle.writeLine("}")
|
||||||
|
fileHandle.writeLine("// swiftlint:disable all")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,264 +1,556 @@
|
||||||
#!/usr/bin/xcrun --sdk macosx swift
|
#!/usr/bin/xcrun --sdk macosx swift
|
||||||
|
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
//
|
//
|
||||||
// This script is based on https://github.com/ginowu7/CleanSwiftLocalizableExample the main difference
|
// This script is based on https://github.com/ginowu7/CleanSwiftLocalizableExample the main difference
|
||||||
// is canges to the localized usage regex
|
// is canges to the localized usage regex
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
let fileManager = FileManager.default
|
extension ProjectState {
|
||||||
let currentPath = (
|
/// Adding `// stringlint:disable` to the top of a source file (before imports) or after a string will mean that file/line gets
|
||||||
ProcessInfo.processInfo.environment["PROJECT_DIR"] ?? fileManager.currentDirectoryPath
|
/// ignored by this script (good for some things like the auto-generated emoji strings or debug strings)
|
||||||
|
static let lintSuppression: String = "stringlint:disable"
|
||||||
|
static let primaryLocalisationFile: String = "en"
|
||||||
|
static let validLocalisationSuffixes: Set<String> = ["Localizable.strings"]
|
||||||
|
static let validSourceSuffixes: Set<String> = [".swift", ".m"]
|
||||||
|
static let excludedPaths: Set<String> = [
|
||||||
|
"build/", // Files under the build folder (CI)
|
||||||
|
"Pods/", // The pods folder
|
||||||
|
"Protos/", // The protobuf files
|
||||||
|
".xcassets/", // Asset bundles
|
||||||
|
".app/", // App build directories
|
||||||
|
".appex/", // Extension build directories
|
||||||
|
"tests/", // Exclude test directories
|
||||||
|
"_SharedTestUtilities/", // Exclude shared test directory
|
||||||
|
"external/" // External dependencies
|
||||||
|
]
|
||||||
|
static let excludedPhrases: Set<String> = [ "", " ", ",", ", ", "null" ]
|
||||||
|
static let excludedUnlocalisedStringLineMatching: Set<MatchType> = [
|
||||||
|
.contains(ProjectState.lintSuppression),
|
||||||
|
.prefix("#import"),
|
||||||
|
.prefix("@available("),
|
||||||
|
.contains("fatalError("),
|
||||||
|
.contains("precondition("),
|
||||||
|
.contains("preconditionFailure("),
|
||||||
|
.contains("print("),
|
||||||
|
.contains("NSLog("),
|
||||||
|
.contains("SNLog("),
|
||||||
|
.contains("owsFailDebug("),
|
||||||
|
.contains("#imageLiteral(resourceName:"),
|
||||||
|
.contains("UIImage(named:"),
|
||||||
|
.contains("UIImage(systemName:"),
|
||||||
|
.contains("[UIImage imageNamed:"),
|
||||||
|
.contains("UIFont(name:"),
|
||||||
|
.contains(".accessibilityLabel ="),
|
||||||
|
.contains(".accessibilityIdentifier ="),
|
||||||
|
.contains("accessibilityIdentifier:"),
|
||||||
|
.contains("accessibilityLabel:"),
|
||||||
|
.contains("Accessibility(identifier:"),
|
||||||
|
.contains("Accessibility(label:"),
|
||||||
|
.containsAnd("identifier:", .previousLine(numEarlier: 1, .contains("Accessibility("))),
|
||||||
|
.containsAnd("label:", .previousLine(numEarlier: 1, .contains("Accessibility("))),
|
||||||
|
.containsAnd("label:", .previousLine(numEarlier: 2, .contains("Accessibility("))),
|
||||||
|
.contains("SQL("),
|
||||||
|
.regex(".*static var databaseTableName: String"),
|
||||||
|
.regex("Logger\\..*\\("),
|
||||||
|
.regex("OWSLogger\\..*\\("),
|
||||||
|
.regex("case .* = ")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the desired actions
|
||||||
|
let targetActions: Set<ScriptAction> = {
|
||||||
|
let args = CommandLine.arguments
|
||||||
|
|
||||||
|
// The first argument is the file name
|
||||||
|
guard args.count > 1 else { return [.lintStrings] }
|
||||||
|
|
||||||
|
return Set(args.suffix(from: 1).map { (ScriptAction(rawValue: $0) ?? .lintStrings) })
|
||||||
|
}()
|
||||||
|
|
||||||
|
print("------------ Searching Through Files ------------")
|
||||||
|
let projectState: ProjectState = ProjectState(
|
||||||
|
path: (
|
||||||
|
ProcessInfo.processInfo.environment["PROJECT_DIR"] ??
|
||||||
|
FileManager.default.currentDirectoryPath
|
||||||
|
),
|
||||||
|
loadSourceFiles: targetActions.contains(.lintStrings)
|
||||||
)
|
)
|
||||||
|
print("------------ Found \(projectState.localizationFiles.count) Localization File(s) ------------")
|
||||||
|
targetActions.forEach { $0.perform(projectState: projectState) }
|
||||||
|
|
||||||
/// List of files in currentPath - recursive
|
// MARK: - ScriptAction
|
||||||
var pathFiles: [String] = {
|
|
||||||
guard
|
enum ScriptAction: String {
|
||||||
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(
|
case validateFilesCopied = "validate"
|
||||||
at: URL(fileURLWithPath: currentPath),
|
case lintStrings = "lint"
|
||||||
includingPropertiesForKeys: [.isDirectoryKey],
|
|
||||||
options: [.skipsHiddenFiles]
|
|
||||||
),
|
|
||||||
let fileUrls: [URL] = enumerator.allObjects as? [URL]
|
|
||||||
else { fatalError("Could not locate files in path directory: \(currentPath)") }
|
|
||||||
|
|
||||||
return fileUrls
|
func perform(projectState: ProjectState) {
|
||||||
.filter {
|
// Perform the action
|
||||||
((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories
|
switch self {
|
||||||
!$0.path.contains("build/") && // Exclude files under the build folder (CI)
|
case .validateFilesCopied:
|
||||||
!$0.path.contains("Pods/") && // Exclude files under the pods folder
|
print("------------ Checking Copied Files ------------")
|
||||||
!$0.path.contains(".xcassets") && // Exclude asset bundles
|
guard
|
||||||
!$0.path.contains(".app/") && // Exclude files in the app build directories
|
let builtProductsPath: String = ProcessInfo.processInfo.environment["BUILT_PRODUCTS_DIR"],
|
||||||
!$0.path.contains(".appex/") && // Exclude files in the extension build directories
|
let productName: String = ProcessInfo.processInfo.environment["FULL_PRODUCT_NAME"],
|
||||||
!$0.path.localizedCaseInsensitiveContains("tests/") && // Exclude files under test directories
|
let enumerator: FileManager.DirectoryEnumerator = FileManager.default.enumerator(
|
||||||
!$0.path.localizedCaseInsensitiveContains("external/") && ( // Exclude files under external directories
|
at: URL(fileURLWithPath: "\(builtProductsPath)/\(productName)"),
|
||||||
// Only include relevant files
|
includingPropertiesForKeys: [.isDirectoryKey],
|
||||||
$0.path.hasSuffix("Localizable.strings") ||
|
options: [.skipsHiddenFiles]
|
||||||
NSString(string: $0.path).pathExtension == "swift" ||
|
),
|
||||||
NSString(string: $0.path).pathExtension == "m"
|
let fileUrls: [URL] = enumerator.allObjects as? [URL]
|
||||||
)
|
else { return Output.error("Could not retrieve list of files within built product") }
|
||||||
}
|
|
||||||
.map { $0.path }
|
let localizationFiles: Set<String> = Set(fileUrls
|
||||||
}()
|
.filter { $0.path.hasSuffix(".lproj") }
|
||||||
|
.map { $0.lastPathComponent.replacingOccurrences(of: ".lproj", with: "") })
|
||||||
|
let missingFiles: Set<String> = Set(projectState.localizationFiles
|
||||||
/// List of localizable files - not including Localizable files in the Pods
|
.map { $0.name })
|
||||||
var localizableFiles: [String] = {
|
.subtracting(localizationFiles)
|
||||||
return pathFiles.filter { $0.hasSuffix("Localizable.strings") }
|
|
||||||
}()
|
guard missingFiles.isEmpty else {
|
||||||
|
return Output.error("Translations missing from \(productName): \(missingFiles.joined(separator: ", "))")
|
||||||
|
}
|
||||||
/// List of executable files
|
break
|
||||||
var executableFiles: [String] = {
|
|
||||||
return pathFiles.filter {
|
case .lintStrings:
|
||||||
$0.hasSuffix(".swift") ||
|
guard !projectState.localizationFiles.isEmpty else {
|
||||||
$0.hasSuffix(".m")
|
return print("------------ Nothing to lint ------------")
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// Reads contents in path
|
|
||||||
///
|
|
||||||
/// - Parameter path: path of file
|
|
||||||
/// - Returns: content in file
|
|
||||||
func contents(atPath path: String) -> String {
|
|
||||||
guard let data = fileManager.contents(atPath: path), let content = String(data: data, encoding: .utf8) else {
|
|
||||||
fatalError("Could not read from path: \(path)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a list of strings that match regex pattern from content
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - pattern: regex pattern
|
|
||||||
/// - content: content to match
|
|
||||||
/// - Returns: list of results
|
|
||||||
func regexFor(_ pattern: String, content: String) -> [String] {
|
|
||||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
|
||||||
fatalError("Regex not formatted correctly: \(pattern)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: content.utf16.count))
|
|
||||||
|
|
||||||
return matches.map {
|
|
||||||
guard let range = Range($0.range(at: 0), in: content) else {
|
|
||||||
fatalError("Incorrect range match")
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(content[range])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func create() -> [LocalizationStringsFile] {
|
|
||||||
return localizableFiles.map(LocalizationStringsFile.init(path:))
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// - Returns: A list of LocalizationCodeFile - contains path of file and all keys in it
|
|
||||||
func localizedStringsInCode() -> [LocalizationCodeFile] {
|
|
||||||
return executableFiles.compactMap {
|
|
||||||
let content = contents(atPath: $0)
|
|
||||||
// Note: Need to exclude escaped quotation marks from strings
|
|
||||||
let matchesOld = regexFor("(?<=NSLocalizedString\\()\\s*\"(?!.*?%d)(.*?)\"", content: content)
|
|
||||||
let matchesNew = regexFor("\"(?!.*?%d)([^(\\\")]*?)\"(?=\\s*)(?=\\.localized)", content: content)
|
|
||||||
let allMatches = (matchesOld + matchesNew)
|
|
||||||
|
|
||||||
return allMatches.isEmpty ? nil : LocalizationCodeFile(path: $0, keys: Set(allMatches))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws error if ALL localizable files does not have matching keys
|
|
||||||
///
|
|
||||||
/// - Parameter files: list of localizable files to validate
|
|
||||||
func validateMatchKeys(_ files: [LocalizationStringsFile]) {
|
|
||||||
guard let base = files.first, files.count > 1 else { return }
|
|
||||||
|
|
||||||
let files = Array(files.dropFirst())
|
|
||||||
|
|
||||||
files.forEach {
|
|
||||||
guard let extraKey = Set(base.keys).symmetricDifference($0.keys).first else { return }
|
|
||||||
let incorrectFile = $0.keys.contains(extraKey) ? $0 : base
|
|
||||||
printPretty("error: Found extra key: \(extraKey) in file: \(incorrectFile.path)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws error if localizable files are missing keys
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - codeFiles: Array of LocalizationCodeFile
|
|
||||||
/// - localizationFiles: Array of LocalizableStringFiles
|
|
||||||
func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) {
|
|
||||||
guard let baseFile = localizationFiles.first else {
|
|
||||||
fatalError("Could not locate base localization file")
|
|
||||||
}
|
|
||||||
|
|
||||||
let baseKeys = Set(baseFile.keys)
|
|
||||||
|
|
||||||
codeFiles.forEach {
|
|
||||||
let extraKeys = $0.keys.subtracting(baseKeys)
|
|
||||||
if !extraKeys.isEmpty {
|
|
||||||
printPretty("error: Found keys in code missing in strings file: \(extraKeys) from \($0.path)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws warning if keys exist in localizable file but are not being used
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - codeFiles: Array of LocalizationCodeFile
|
|
||||||
/// - localizationFiles: Array of LocalizableStringFiles
|
|
||||||
func validateDeadKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) {
|
|
||||||
guard let baseFile = localizationFiles.first else {
|
|
||||||
fatalError("Could not locate base localization file")
|
|
||||||
}
|
|
||||||
|
|
||||||
let baseKeys: Set<String> = Set(baseFile.keys)
|
|
||||||
let allCodeFileKeys: [String] = codeFiles.flatMap { $0.keys }
|
|
||||||
let deadKeys: [String] = Array(baseKeys.subtracting(allCodeFileKeys))
|
|
||||||
.sorted()
|
|
||||||
.map { $0.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) }
|
|
||||||
|
|
||||||
if !deadKeys.isEmpty {
|
|
||||||
printPretty("warning: \(deadKeys) - Suggest cleaning dead keys")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol Pathable {
|
|
||||||
var path: String { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LocalizationStringsFile: Pathable {
|
|
||||||
let path: String
|
|
||||||
let kv: [String: String]
|
|
||||||
let duplicates: [(key: String, path: String)]
|
|
||||||
|
|
||||||
var keys: [String] {
|
|
||||||
return Array(kv.keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(path: String) {
|
|
||||||
let result = ContentParser.parse(path)
|
|
||||||
|
|
||||||
self.path = path
|
|
||||||
self.kv = result.kv
|
|
||||||
self.duplicates = result.duplicates
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes back to localizable file with sorted keys and removed whitespaces and new lines
|
|
||||||
func cleanWrite() {
|
|
||||||
print("------------ Sort and remove whitespaces: \(path) ------------")
|
|
||||||
let content = kv.keys.sorted().map { "\($0) = \(kv[$0]!);" }.joined(separator: "\n")
|
|
||||||
try! content.write(toFile: path, atomically: true, encoding: .utf8)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LocalizationCodeFile: Pathable {
|
|
||||||
let path: String
|
|
||||||
let keys: Set<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContentParser {
|
|
||||||
|
|
||||||
/// Parses contents of a file to localizable keys and values - Throws error if localizable file have duplicated keys
|
|
||||||
///
|
|
||||||
/// - Parameter path: Localizable file paths
|
|
||||||
/// - Returns: localizable key and value for content at path
|
|
||||||
static func parse(_ path: String) -> (kv: [String: String], duplicates: [(key: String, path: String)]) {
|
|
||||||
let content = contents(atPath: path)
|
|
||||||
let trimmed = content
|
|
||||||
.replacingOccurrences(of: "\n+", with: "", options: .regularExpression, range: nil)
|
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
let keys = regexFor("\"([^\"]*?)\"(?= =)", content: trimmed)
|
|
||||||
let values = regexFor("(?<== )\"(.*?)\"(?=;)", content: trimmed)
|
|
||||||
|
|
||||||
if keys.count != values.count {
|
|
||||||
fatalError("Error parsing contents: Make sure all keys and values are in correct format (this could be due to extra spaces between keys and values)")
|
|
||||||
}
|
|
||||||
|
|
||||||
var duplicates: [(key: String, path: String)] = []
|
|
||||||
let kv: [String: String] = zip(keys, values)
|
|
||||||
.reduce(into: [:]) { results, keyValue in
|
|
||||||
guard results[keyValue.0] == nil else {
|
|
||||||
duplicates.append((keyValue.0, path))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results[keyValue.0] = keyValue.1
|
print("------------ Processing \(projectState.localizationFiles.count) Localization File(s) ------------")
|
||||||
}
|
|
||||||
|
// Add warnings for any duplicate keys
|
||||||
|
projectState.localizationFiles.forEach { file in
|
||||||
|
// Show errors for any duplicates
|
||||||
|
file.duplicates.forEach { phrase, original in Output.duplicate(phrase, original: original) }
|
||||||
|
|
||||||
|
// Show warnings for any phrases missing from the file
|
||||||
|
let allKeys: Set<String> = Set(file.keyPhrase.keys)
|
||||||
|
let missingKeysFromOtherFiles: [String: [String]] = projectState.localizationFiles.reduce(into: [:]) { result, otherFile in
|
||||||
|
guard otherFile.path != file.path else { return }
|
||||||
|
|
||||||
|
let missingKeys: Set<String> = Set(otherFile.keyPhrase.keys)
|
||||||
|
.subtracting(allKeys)
|
||||||
|
|
||||||
|
missingKeys.forEach { missingKey in
|
||||||
|
result[missingKey] = ((result[missingKey] ?? []) + [otherFile.name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
missingKeysFromOtherFiles.forEach { missingKey, namesOfFilesItWasFound in
|
||||||
|
Output.warning(file, "Phrase '\(missingKey)' is missing (found in: \(namesOfFilesItWasFound.joined(separator: ", ")))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the source code
|
||||||
|
print("------------ Processing \(projectState.sourceFiles.count) Source File(s) ------------")
|
||||||
|
let allKeys: Set<String> = Set(projectState.primaryLocalizationFile.keyPhrase.keys)
|
||||||
|
|
||||||
|
projectState.sourceFiles.forEach { file in
|
||||||
|
// Add logs for unlocalised strings
|
||||||
|
file.unlocalizedPhrases.forEach { phrase in
|
||||||
|
Output.warning(phrase, "Found unlocalized string '\(phrase.key)'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add errors for missing localised strings
|
||||||
|
let missingKeys: Set<String> = Set(file.keyPhrase.keys).subtracting(allKeys)
|
||||||
|
missingKeys.forEach { key in
|
||||||
|
switch file.keyPhrase[key] {
|
||||||
|
case .some(let phrase): Output.error(phrase, "Localized phrase '\(key)' missing from strings files")
|
||||||
|
case .none: Output.error(file, "Localized phrase '\(key)' missing from strings files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return (kv, duplicates)
|
print("------------ Complete ------------")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printPretty(_ string: String) {
|
// MARK: - Functionality
|
||||||
print(string.replacingOccurrences(of: "\\", with: ""))
|
|
||||||
|
enum Regex {
|
||||||
|
/// Returns a list of strings that match regex pattern from content
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - pattern: regex pattern
|
||||||
|
/// - content: content to match
|
||||||
|
/// - Returns: list of results
|
||||||
|
static func matches(_ pattern: String, content: String) -> [String] {
|
||||||
|
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||||
|
fatalError("Regex not formatted correctly: \(pattern)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: content.utf16.count))
|
||||||
|
|
||||||
|
return matches.map {
|
||||||
|
guard let range = Range($0.range(at: 0), in: content) else {
|
||||||
|
fatalError("Incorrect range match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(content[range])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Processing
|
// MARK: - Output
|
||||||
|
|
||||||
let stringFiles: [LocalizationStringsFile] = create()
|
enum Output {
|
||||||
|
static func error(_ error: String) {
|
||||||
if !stringFiles.isEmpty {
|
print("error: \(error)")
|
||||||
print("------------ Found \(stringFiles.count) file(s) - checking for duplicate, extra, missing and dead keys ------------")
|
}
|
||||||
|
|
||||||
stringFiles.forEach { file in
|
static func error(_ location: Locatable, _ error: String) {
|
||||||
file.duplicates.forEach { key, path in
|
print("\(location.location): error: \(error)")
|
||||||
printPretty("error: Found duplicate key: \(key) in file: \(path)")
|
}
|
||||||
|
|
||||||
|
static func warning(_ location: Locatable, _ warning: String) {
|
||||||
|
print("\(location.location): warning: \(warning)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func duplicate(
|
||||||
|
_ duplicate: KeyedLocatable,
|
||||||
|
original: KeyedLocatable
|
||||||
|
) {
|
||||||
|
print("\(duplicate.location): error: duplicate key '\(original.key)'")
|
||||||
|
|
||||||
|
// Looks like the `note:` doesn't work the same as when XCode does it unfortunately so we can't
|
||||||
|
// currently include the reference to the original entry
|
||||||
|
// print("\(original.location): note: previously found here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ProjectState
|
||||||
|
|
||||||
|
struct ProjectState {
|
||||||
|
let primaryLocalizationFile: LocalizationStringsFile
|
||||||
|
let localizationFiles: [LocalizationStringsFile]
|
||||||
|
let sourceFiles: [SourceFile]
|
||||||
|
|
||||||
|
init(path: String, loadSourceFiles: Bool) {
|
||||||
|
guard
|
||||||
|
let enumerator: FileManager.DirectoryEnumerator = FileManager.default.enumerator(
|
||||||
|
at: URL(fileURLWithPath: path),
|
||||||
|
includingPropertiesForKeys: [.isDirectoryKey],
|
||||||
|
options: [.skipsHiddenFiles]
|
||||||
|
),
|
||||||
|
let fileUrls: [URL] = enumerator.allObjects as? [URL]
|
||||||
|
else { fatalError("Could not locate files in path directory: \(path)") }
|
||||||
|
|
||||||
|
// Get a list of valid URLs
|
||||||
|
let lowerCaseExcludedPaths: Set<String> = Set(ProjectState.excludedPaths.map { $0.lowercased() })
|
||||||
|
let validFileUrls: [URL] = fileUrls.filter { fileUrl in
|
||||||
|
((try? fileUrl.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) &&
|
||||||
|
!lowerCaseExcludedPaths.contains { fileUrl.path.lowercased().contains($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Localization files
|
||||||
|
let targetFileSuffixes: Set<String> = Set(ProjectState.validLocalisationSuffixes.map { $0.lowercased() })
|
||||||
|
self.localizationFiles = validFileUrls
|
||||||
|
.filter { fileUrl in targetFileSuffixes.contains { fileUrl.path.lowercased().contains($0) } }
|
||||||
|
.map { LocalizationStringsFile(path: $0.path) }
|
||||||
|
|
||||||
|
guard let primaryLocalizationFile: LocalizationStringsFile = self.localizationFiles.first(where: { $0.name == ProjectState.primaryLocalisationFile }) else {
|
||||||
|
fatalError("Could not locate primary localization file: \(ProjectState.primaryLocalisationFile)")
|
||||||
|
}
|
||||||
|
self.primaryLocalizationFile = primaryLocalizationFile
|
||||||
|
|
||||||
|
guard loadSourceFiles else {
|
||||||
|
self.sourceFiles = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source files
|
||||||
|
let lowerCaseSourceSuffixes: Set<String> = Set(ProjectState.validSourceSuffixes.map { $0.lowercased() })
|
||||||
|
self.sourceFiles = validFileUrls
|
||||||
|
.filter { fileUrl in lowerCaseSourceSuffixes.contains(".\(fileUrl.pathExtension)") }
|
||||||
|
.compactMap { SourceFile(path: $0.path) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol Locatable {
|
||||||
|
var location: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol KeyedLocatable: Locatable {
|
||||||
|
var key: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProjectState {
|
||||||
|
// MARK: - LocalizationStringsFile
|
||||||
|
|
||||||
|
struct LocalizationStringsFile: Locatable {
|
||||||
|
struct Phrase: KeyedLocatable {
|
||||||
|
let key: String
|
||||||
|
let value: String
|
||||||
|
let filePath: String
|
||||||
|
let lineNumber: Int
|
||||||
|
|
||||||
|
var location: String { "\(filePath):\(lineNumber)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let path: String
|
||||||
|
let keyPhrase: [String: Phrase]
|
||||||
|
let duplicates: [(Phrase, original: Phrase)]
|
||||||
|
|
||||||
|
var location: String { path }
|
||||||
|
|
||||||
|
init(path: String) {
|
||||||
|
let result = LocalizationStringsFile.parse(path)
|
||||||
|
|
||||||
|
self.name = (path
|
||||||
|
.replacingOccurrences(of: "/Localizable.strings", with: "")
|
||||||
|
.replacingOccurrences(of: ".lproj", with: "")
|
||||||
|
.components(separatedBy: "/")
|
||||||
|
.last ?? "Unknown")
|
||||||
|
self.path = path
|
||||||
|
self.keyPhrase = result.keyPhrase
|
||||||
|
self.duplicates = result.duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
static func parse(_ path: String) -> (keyPhrase: [String: Phrase], duplicates: [(Phrase, original: Phrase)]) {
|
||||||
|
guard
|
||||||
|
let data: Data = FileManager.default.contents(atPath: path),
|
||||||
|
let content: String = String(data: data, encoding: .utf8)
|
||||||
|
else { fatalError("Could not read from path: \(path)") }
|
||||||
|
|
||||||
|
let lines: [String] = content.components(separatedBy: .newlines)
|
||||||
|
var duplicates: [(Phrase, original: Phrase)] = []
|
||||||
|
var keyPhrase: [String: Phrase] = [:]
|
||||||
|
|
||||||
|
lines.enumerated().forEach { lineNumber, line in
|
||||||
|
guard
|
||||||
|
let key: String = Regex.matches("\"([^\"]*?)\"(?= =)", content: line).first,
|
||||||
|
let value: String = Regex.matches("(?<== )\"(.*?)\"(?=;)", content: line).first
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
// Remove the quotation marks around the key
|
||||||
|
let trimmedKey: String = String(key
|
||||||
|
.prefix(upTo: key.index(before: key.endIndex))
|
||||||
|
.suffix(from: key.index(after: key.startIndex)))
|
||||||
|
|
||||||
|
// Files are 1-indexed but arrays are 0-indexed so add 1 to the lineNumber
|
||||||
|
let result: Phrase = Phrase(
|
||||||
|
key: trimmedKey,
|
||||||
|
value: value,
|
||||||
|
filePath: path,
|
||||||
|
lineNumber: (lineNumber + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
switch keyPhrase[trimmedKey] {
|
||||||
|
case .some(let original): duplicates.append((result, original))
|
||||||
|
case .none: keyPhrase[trimmedKey] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (keyPhrase, duplicates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateMatchKeys(stringFiles)
|
// MARK: - SourceFile
|
||||||
|
|
||||||
// Note: Uncomment the below file to clean out all comments from the localizable file (we don't want this because comments make it readable...)
|
struct SourceFile: Locatable {
|
||||||
// stringFiles.forEach { $0.cleanWrite() }
|
struct Phrase: KeyedLocatable {
|
||||||
|
let term: String
|
||||||
let codeFiles: [LocalizationCodeFile] = localizedStringsInCode()
|
let filePath: String
|
||||||
validateMissingKeys(codeFiles, localizationFiles: stringFiles)
|
let lineNumber: Int
|
||||||
validateDeadKeys(codeFiles, localizationFiles: stringFiles)
|
|
||||||
|
var key: String { term }
|
||||||
|
var location: String { "\(filePath):\(lineNumber)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
let path: String
|
||||||
|
let keyPhrase: [String: Phrase]
|
||||||
|
let unlocalizedKeyPhrase: [String: Phrase]
|
||||||
|
let phrases: [Phrase]
|
||||||
|
let unlocalizedPhrases: [Phrase]
|
||||||
|
|
||||||
|
var location: String { path }
|
||||||
|
|
||||||
|
init?(path: String) {
|
||||||
|
guard let result = SourceFile.parse(path) else { return nil }
|
||||||
|
|
||||||
|
self.path = path
|
||||||
|
self.keyPhrase = result.keyPhrase
|
||||||
|
self.unlocalizedKeyPhrase = result.unlocalizedKeyPhrase
|
||||||
|
self.phrases = result.phrases
|
||||||
|
self.unlocalizedPhrases = result.unlocalizedPhrases
|
||||||
|
}
|
||||||
|
|
||||||
|
static func parse(_ path: String) -> (keyPhrase: [String: Phrase], phrases: [Phrase], unlocalizedKeyPhrase: [String: Phrase], unlocalizedPhrases: [Phrase])? {
|
||||||
|
guard
|
||||||
|
let data: Data = FileManager.default.contents(atPath: path),
|
||||||
|
let content: String = String(data: data, encoding: .utf8)
|
||||||
|
else { fatalError("Could not read from path: \(path)") }
|
||||||
|
|
||||||
|
// If the file has the lint supression before the first import then ignore the file
|
||||||
|
let preImportContent: String = (content.components(separatedBy: "import").first ?? "")
|
||||||
|
|
||||||
|
guard !preImportContent.contains(ProjectState.lintSuppression) else {
|
||||||
|
print("Explicitly ignoring \(path)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise continue and process the file
|
||||||
|
let lines: [String] = content.components(separatedBy: .newlines)
|
||||||
|
var keyPhrase: [String: Phrase] = [:]
|
||||||
|
var unlocalizedKeyPhrase: [String: Phrase] = [:]
|
||||||
|
var phrases: [Phrase] = []
|
||||||
|
var unlocalizedPhrases: [Phrase] = []
|
||||||
|
|
||||||
|
lines.enumerated().forEach { lineNumber, line in
|
||||||
|
let trimmedLine: String = line.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
// Ignore the line if it doesn't contain a quotation character (optimisation), it's
|
||||||
|
// been suppressed or it's explicitly excluded due to the rules at the top of the file
|
||||||
|
guard
|
||||||
|
trimmedLine.contains("\"") &&
|
||||||
|
!ProjectState.excludedUnlocalisedStringLineMatching
|
||||||
|
.contains(where: { $0.matches(trimmedLine, lineNumber, lines) })
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
// Split line based on commented out content and exclude the comment from the linting
|
||||||
|
let commentMatches: [String] = Regex.matches(
|
||||||
|
"//[^\\\"]*(?:\\\"[^\\\"]*\\\"[^\\\"]*)*",
|
||||||
|
content: line
|
||||||
|
)
|
||||||
|
let targetLine: String = (commentMatches.isEmpty ? line :
|
||||||
|
line.components(separatedBy: commentMatches[0])[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use regex to find `NSLocalizedString("", "")`, `"".localised()` and any other `""`
|
||||||
|
// values in the source code
|
||||||
|
//
|
||||||
|
// Note: It's more complex because we need to exclude escaped quotation marks from
|
||||||
|
// strings and also want to ignore any strings that have been commented out, Swift
|
||||||
|
// also doesn't support "lookbehind" in regex so we can use that approach
|
||||||
|
var isUnlocalized: Bool = false
|
||||||
|
var allMatches: Set<String> = Set(
|
||||||
|
Regex
|
||||||
|
.matches(
|
||||||
|
"NSLocalizedString\\(@{0,1}\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*(?:\\\")",
|
||||||
|
content: targetLine
|
||||||
|
)
|
||||||
|
.map { match in
|
||||||
|
match
|
||||||
|
.removingPrefixIfPresent("NSLocalizedString(@\"")
|
||||||
|
.removingPrefixIfPresent("NSLocalizedString(\"")
|
||||||
|
.removingSuffixIfPresent("\")")
|
||||||
|
.removingSuffixIfPresent("\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we didn't get any matches for the standard `NSLocalizedString` then try our
|
||||||
|
// custom extension `"".localized()`
|
||||||
|
if allMatches.isEmpty {
|
||||||
|
allMatches = allMatches.union(Set(
|
||||||
|
Regex
|
||||||
|
.matches(
|
||||||
|
"\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*\\\"\\.localized",
|
||||||
|
content: targetLine
|
||||||
|
)
|
||||||
|
.map { match in
|
||||||
|
match
|
||||||
|
.removingPrefixIfPresent("\"")
|
||||||
|
.removingSuffixIfPresent("\".localized")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we still don't have any matches then try to match any strings as unlocalized strings (handling
|
||||||
|
/// nested `"Test\"string\" value"`, empty strings and strings only composed of quotes `"""""""`)
|
||||||
|
///
|
||||||
|
/// **Note:** While it'd be nice to have the regex automatically exclude the quotes doing so makes it _far_ less
|
||||||
|
/// efficient (approx. by a factor of 8 times) so we remove those ourselves)
|
||||||
|
if allMatches.isEmpty {
|
||||||
|
// Find strings which are just not localised
|
||||||
|
let potentialUnlocalizedStrings: [String] = Regex
|
||||||
|
.matches("\\\"[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*(?:\\\")", content: targetLine)
|
||||||
|
// Remove the leading and trailing quotation marks
|
||||||
|
.map { $0.removingPrefixIfPresent("\"").removingSuffixIfPresent("\"") }
|
||||||
|
// Remove any empty strings
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
// Remove any string conversations (ie. `.map { "\($0)" }`
|
||||||
|
.filter { value in !value.hasPrefix("\\(") || !value.hasSuffix(")") }
|
||||||
|
|
||||||
|
allMatches = allMatches.union(Set(potentialUnlocalizedStrings))
|
||||||
|
isUnlocalized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any excluded phrases from the matches
|
||||||
|
allMatches = allMatches.subtracting(ProjectState.excludedPhrases.map { "\($0)" })
|
||||||
|
|
||||||
|
allMatches.forEach { match in
|
||||||
|
// Files are 1-indexed but arrays are 0-indexed so add 1 to the lineNumber
|
||||||
|
let result: Phrase = Phrase(
|
||||||
|
term: match,
|
||||||
|
filePath: path,
|
||||||
|
lineNumber: (lineNumber + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !isUnlocalized {
|
||||||
|
keyPhrase[match] = result
|
||||||
|
phrases.append(result)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unlocalizedKeyPhrase[match] = result
|
||||||
|
unlocalizedPhrases.append(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (keyPhrase, phrases, unlocalizedKeyPhrase, unlocalizedPhrases)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("------------ Complete ------------")
|
indirect enum MatchType: Hashable {
|
||||||
|
case prefix(String)
|
||||||
|
case contains(String)
|
||||||
|
case containsAnd(String, MatchType)
|
||||||
|
case regex(String)
|
||||||
|
case previousLine(numEarlier: Int, MatchType)
|
||||||
|
|
||||||
|
func matches(_ value: String, _ index: Int, _ lines: [String]) -> Bool {
|
||||||
|
switch self {
|
||||||
|
case .prefix(let prefix):
|
||||||
|
return value
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
.hasPrefix(prefix)
|
||||||
|
|
||||||
|
case .contains(let other): return value.contains(other)
|
||||||
|
case .containsAnd(let other, let otherMatch):
|
||||||
|
guard value.contains(other) else { return false }
|
||||||
|
|
||||||
|
return otherMatch.matches(value, index, lines)
|
||||||
|
|
||||||
|
case .regex(let regex): return !Regex.matches(regex, content: value).isEmpty
|
||||||
|
|
||||||
|
case .previousLine(let numEarlier, let type):
|
||||||
|
guard index >= numEarlier else { return false }
|
||||||
|
|
||||||
|
let targetIndex: Int = (index - numEarlier)
|
||||||
|
return type.matches(lines[targetIndex], targetIndex, lines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func removingPrefixIfPresent(_ value: String) -> String {
|
||||||
|
guard hasPrefix(value) else { return self }
|
||||||
|
|
||||||
|
return String(self.suffix(from: self.index(self.startIndex, offsetBy: value.count)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func removingSuffixIfPresent(_ value: String) -> String {
|
||||||
|
guard hasSuffix(value) else { return self }
|
||||||
|
|
||||||
|
return String(self.prefix(upTo: self.index(self.endIndex, offsetBy: -value.count)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -802,6 +802,11 @@
|
||||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
||||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
|
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
|
||||||
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; };
|
FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; };
|
||||||
|
FDC498B72AC15F7D00EDD897 /* AppNotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */; };
|
||||||
|
FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */; };
|
||||||
|
FDC498BB2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */; };
|
||||||
|
FDC498BE2AC1732E00EDD897 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD9401A32ABD04AC003A4834 /* Localizable.strings */; };
|
||||||
|
FDC498C22AC17BFC00EDD897 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FD9401A32ABD04AC003A4834 /* Localizable.strings */; };
|
||||||
FDC6D6F32860607300B04575 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* Environment.swift */; };
|
FDC6D6F32860607300B04575 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7542807C4BB004C14C5 /* Environment.swift */; };
|
||||||
FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; };
|
FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; };
|
||||||
FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; };
|
FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; };
|
||||||
|
@ -1940,6 +1945,9 @@
|
||||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
|
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
|
||||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
||||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
|
FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationCategory.swift; sourceTree = "<group>"; };
|
||||||
|
FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationAction.swift; sourceTree = "<group>"; };
|
||||||
|
FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationUserInfoKey.swift; sourceTree = "<group>"; };
|
||||||
FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
|
FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
|
||||||
FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = "<group>"; };
|
FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = "<group>"; };
|
||||||
FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = "<group>"; };
|
FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = "<group>"; };
|
||||||
|
@ -3102,6 +3110,7 @@
|
||||||
C36096BB25AD1BBB008B62B2 /* Notifications */ = {
|
C36096BB25AD1BBB008B62B2 /* Notifications */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FDC498B52AC15F6D00EDD897 /* Types */,
|
||||||
4539B5851F79348F007141FF /* PushRegistrationManager.swift */,
|
4539B5851F79348F007141FF /* PushRegistrationManager.swift */,
|
||||||
45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */,
|
45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */,
|
||||||
451A13B01E13DED2000A50FD /* AppNotifications.swift */,
|
451A13B01E13DED2000A50FD /* AppNotifications.swift */,
|
||||||
|
@ -4303,6 +4312,16 @@
|
||||||
path = _TestUtilities;
|
path = _TestUtilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FDC498B52AC15F6D00EDD897 /* Types */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FDC498B62AC15F7D00EDD897 /* AppNotificationCategory.swift */,
|
||||||
|
FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */,
|
||||||
|
FDC498BA2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift */,
|
||||||
|
);
|
||||||
|
path = Types;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FDDC08F029A300D500BF9681 /* Utilities */ = {
|
FDDC08F029A300D500BF9681 /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -4527,6 +4546,7 @@
|
||||||
453518641FC635DD00210559 /* Sources */,
|
453518641FC635DD00210559 /* Sources */,
|
||||||
453518651FC635DD00210559 /* Frameworks */,
|
453518651FC635DD00210559 /* Frameworks */,
|
||||||
453518661FC635DD00210559 /* Resources */,
|
453518661FC635DD00210559 /* Resources */,
|
||||||
|
FDC498C02AC1774500EDD897 /* Ensure Localizable.strings included */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -4550,6 +4570,7 @@
|
||||||
7BC01A37241F40AB00BC7C55 /* Sources */,
|
7BC01A37241F40AB00BC7C55 /* Sources */,
|
||||||
7BC01A38241F40AB00BC7C55 /* Frameworks */,
|
7BC01A38241F40AB00BC7C55 /* Frameworks */,
|
||||||
7BC01A39241F40AB00BC7C55 /* Resources */,
|
7BC01A39241F40AB00BC7C55 /* Resources */,
|
||||||
|
FDC498C12AC1775400EDD897 /* Ensure Localizable.strings included */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -4672,6 +4693,7 @@
|
||||||
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
||||||
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
||||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||||
|
FDC498BF2AC1747900EDD897 /* Ensure Localizable.strings included */,
|
||||||
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
|
@ -4964,6 +4986,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */,
|
4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */,
|
||||||
|
FDC498C22AC17BFC00EDD897 /* Localizable.strings in Resources */,
|
||||||
B8D07406265C683A00F77E07 /* ElegantIcons.ttf in Resources */,
|
B8D07406265C683A00F77E07 /* ElegantIcons.ttf in Resources */,
|
||||||
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */,
|
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -4973,6 +4996,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FDC498BE2AC1732E00EDD897 /* Localizable.strings in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -5429,6 +5453,66 @@
|
||||||
shellScript = "\"${SRCROOT}/Scripts/build_libSession_util.sh\"\n";
|
shellScript = "\"${SRCROOT}/Scripts/build_libSession_util.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
FDC498BF2AC1747900EDD897 /* Ensure Localizable.strings included */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Ensure Localizable.strings included";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
FDC498C02AC1774500EDD897 /* Ensure Localizable.strings included */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Ensure Localizable.strings included";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
FDC498C12AC1775400EDD897 /* Ensure Localizable.strings included */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Ensure Localizable.strings included";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" validate\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = {
|
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
|
@ -5468,7 +5552,7 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\"\n";
|
shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\" lint\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
@ -6083,6 +6167,7 @@
|
||||||
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */,
|
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */,
|
||||||
FD37E9DD28A384EB003AE748 /* PrimaryColorSelectionView.swift in Sources */,
|
FD37E9DD28A384EB003AE748 /* PrimaryColorSelectionView.swift in Sources */,
|
||||||
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,
|
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,
|
||||||
|
FDC498B72AC15F7D00EDD897 /* AppNotificationCategory.swift in Sources */,
|
||||||
FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */,
|
FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */,
|
||||||
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
|
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
|
||||||
C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */,
|
C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */,
|
||||||
|
@ -6100,6 +6185,7 @@
|
||||||
7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */,
|
7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */,
|
||||||
FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */,
|
FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */,
|
||||||
B877E24226CA12910007970A /* CallVC.swift in Sources */,
|
B877E24226CA12910007970A /* CallVC.swift in Sources */,
|
||||||
|
FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */,
|
||||||
7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */,
|
7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */,
|
||||||
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */,
|
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */,
|
||||||
FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */,
|
FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */,
|
||||||
|
@ -6161,6 +6247,7 @@
|
||||||
7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */,
|
7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */,
|
||||||
FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */,
|
FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */,
|
||||||
7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */,
|
7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */,
|
||||||
|
FDC498BB2AC1606C00EDD897 /* AppNotificationUserInfoKey.swift in Sources */,
|
||||||
C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */,
|
C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */,
|
||||||
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */,
|
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */,
|
||||||
3488F9362191CC4000E524CC /* MediaView.swift in Sources */,
|
3488F9362191CC4000E524CC /* MediaView.swift in Sources */,
|
||||||
|
@ -6507,7 +6594,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6579,7 +6666,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6644,7 +6731,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
|
@ -6718,7 +6805,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -7678,7 +7765,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7749,7 +7836,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 425;
|
CURRENT_PROJECT_VERSION = 426;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "OWSBackupSettingsViewController.h"
|
|
||||||
#import "OWSBackup.h"
|
|
||||||
#import "Session-Swift.h"
|
|
||||||
|
|
||||||
#import <PromiseKit/AnyPromise.h>
|
|
||||||
#import <SessionMessagingKit/Environment.h>
|
|
||||||
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
|
|
||||||
#import <SignalUtilitiesKit/UIColor+OWS.h>
|
|
||||||
#import <SignalUtilitiesKit/UIFont+OWS.h>
|
|
||||||
#import <SessionUtilitiesKit/UIView+OWS.h>
|
|
||||||
#import <SessionUtilitiesKit/MIMETypeUtil.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface OWSBackupSettingsViewController ()
|
|
||||||
|
|
||||||
@property (nonatomic, nullable) NSError *iCloudError;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
@implementation OWSBackupSettingsViewController
|
|
||||||
|
|
||||||
#pragma mark - Dependencies
|
|
||||||
|
|
||||||
- (OWSBackup *)backup
|
|
||||||
{
|
|
||||||
OWSAssertDebug(AppEnvironment.shared.backup);
|
|
||||||
|
|
||||||
return AppEnvironment.shared.backup;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
self.title = NSLocalizedString(@"SETTINGS_BACKUP", @"Label for the backup view in app settings.");
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(backupStateDidChange:)
|
|
||||||
name:NSNotificationNameBackupStateDidChange
|
|
||||||
object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(applicationDidBecomeActive:)
|
|
||||||
name:OWSApplicationDidBecomeActiveNotification
|
|
||||||
object:nil];
|
|
||||||
|
|
||||||
[self updateTableContents];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewDidAppear:animated];
|
|
||||||
|
|
||||||
[self updateTableContents];
|
|
||||||
[self updateICloudStatus];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateICloudStatus
|
|
||||||
{
|
|
||||||
__weak OWSBackupSettingsViewController *weakSelf = self;
|
|
||||||
[[self.backup ensureCloudKitAccess]
|
|
||||||
.then(^{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
weakSelf.iCloudError = nil;
|
|
||||||
[weakSelf updateTableContents];
|
|
||||||
})
|
|
||||||
.catch(^(NSError *error) {
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
weakSelf.iCloudError = error;
|
|
||||||
[weakSelf updateTableContents];
|
|
||||||
}) retainUntilComplete];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Table Contents
|
|
||||||
|
|
||||||
- (void)updateTableContents
|
|
||||||
{
|
|
||||||
OWSTableContents *contents = [OWSTableContents new];
|
|
||||||
|
|
||||||
BOOL isBackupEnabled = [OWSBackup.sharedManager isBackupEnabled];
|
|
||||||
|
|
||||||
if (self.iCloudError) {
|
|
||||||
OWSTableSection *iCloudSection = [OWSTableSection new];
|
|
||||||
iCloudSection.headerTitle = NSLocalizedString(
|
|
||||||
@"SETTINGS_BACKUP_ICLOUD_STATUS", @"Label for iCloud status row in the in the backup settings view.");
|
|
||||||
[iCloudSection
|
|
||||||
addItem:[OWSTableItem
|
|
||||||
longDisclosureItemWithText:[OWSBackupAPI errorMessageForCloudKitAccessError:self.iCloudError]
|
|
||||||
actionBlock:^{
|
|
||||||
[[UIApplication sharedApplication]
|
|
||||||
openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
|
||||||
}]];
|
|
||||||
[contents addSection:iCloudSection];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This UI is temporary.
|
|
||||||
// Enabling backup will involve entering and registering a PIN.
|
|
||||||
OWSTableSection *enableSection = [OWSTableSection new];
|
|
||||||
enableSection.headerTitle = NSLocalizedString(@"SETTINGS_BACKUP", @"Label for the backup view in app settings.");
|
|
||||||
[enableSection
|
|
||||||
addItem:[OWSTableItem switchItemWithText:
|
|
||||||
NSLocalizedString(@"SETTINGS_BACKUP_ENABLING_SWITCH",
|
|
||||||
@"Label for switch in settings that controls whether or not backup is enabled.")
|
|
||||||
isOnBlock:^{
|
|
||||||
return [OWSBackup.sharedManager isBackupEnabled];
|
|
||||||
}
|
|
||||||
target:self
|
|
||||||
selector:@selector(isBackupEnabledDidChange:)]];
|
|
||||||
[contents addSection:enableSection];
|
|
||||||
|
|
||||||
if (isBackupEnabled) {
|
|
||||||
// TODO: This UI is temporary.
|
|
||||||
// Enabling backup will involve entering and registering a PIN.
|
|
||||||
OWSTableSection *progressSection = [OWSTableSection new];
|
|
||||||
[progressSection
|
|
||||||
addItem:[OWSTableItem
|
|
||||||
labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_STATUS",
|
|
||||||
@"Label for backup status row in the in the backup settings view.")
|
|
||||||
accessoryText:NSStringForBackupExportState(OWSBackup.sharedManager.backupExportState)]];
|
|
||||||
if (OWSBackup.sharedManager.backupExportState == OWSBackupState_InProgress) {
|
|
||||||
if (OWSBackup.sharedManager.backupExportDescription) {
|
|
||||||
[progressSection
|
|
||||||
addItem:[OWSTableItem
|
|
||||||
labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_PHASE",
|
|
||||||
@"Label for phase row in the in the backup settings view.")
|
|
||||||
accessoryText:OWSBackup.sharedManager.backupExportDescription]];
|
|
||||||
if (OWSBackup.sharedManager.backupExportProgress) {
|
|
||||||
NSUInteger progressPercent
|
|
||||||
= (NSUInteger)round(OWSBackup.sharedManager.backupExportProgress.floatValue * 100);
|
|
||||||
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
|
|
||||||
[numberFormatter setNumberStyle:NSNumberFormatterPercentStyle];
|
|
||||||
[numberFormatter setMaximumFractionDigits:0];
|
|
||||||
[numberFormatter setMultiplier:@1];
|
|
||||||
NSString *progressString = [numberFormatter stringFromNumber:@(progressPercent)];
|
|
||||||
[progressSection
|
|
||||||
addItem:[OWSTableItem
|
|
||||||
labelItemWithText:NSLocalizedString(@"SETTINGS_BACKUP_PROGRESS",
|
|
||||||
@"Label for phase row in the in the backup settings view.")
|
|
||||||
accessoryText:progressString]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (OWSBackup.sharedManager.backupExportState) {
|
|
||||||
case OWSBackupState_Idle:
|
|
||||||
case OWSBackupState_Failed:
|
|
||||||
case OWSBackupState_Succeeded:
|
|
||||||
[progressSection
|
|
||||||
addItem:[OWSTableItem disclosureItemWithText:
|
|
||||||
NSLocalizedString(@"SETTINGS_BACKUP_BACKUP_NOW",
|
|
||||||
@"Label for 'backup now' button in the backup settings view.")
|
|
||||||
actionBlock:^{
|
|
||||||
[OWSBackup.sharedManager tryToExportBackup];
|
|
||||||
}]];
|
|
||||||
break;
|
|
||||||
case OWSBackupState_InProgress:
|
|
||||||
[progressSection
|
|
||||||
addItem:[OWSTableItem disclosureItemWithText:
|
|
||||||
NSLocalizedString(@"SETTINGS_BACKUP_CANCEL_BACKUP",
|
|
||||||
@"Label for 'cancel backup' button in the backup settings view.")
|
|
||||||
actionBlock:^{
|
|
||||||
[OWSBackup.sharedManager cancelExportBackup];
|
|
||||||
}]];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
[contents addSection:progressSection];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.contents = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)isBackupEnabledDidChange:(UISwitch *)sender
|
|
||||||
{
|
|
||||||
[OWSBackup.sharedManager setIsBackupEnabled:sender.isOn];
|
|
||||||
|
|
||||||
[self updateTableContents];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Events
|
|
||||||
|
|
||||||
- (void)backupStateDidChange:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
[self updateTableContents];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
[self updateICloudStatus];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
||||||
|
|
||||||
|
// swiftlint:disable all
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
extension Emoji {
|
extension Emoji {
|
||||||
enum Category: String, CaseIterable, Equatable {
|
enum Category: String, CaseIterable, Equatable {
|
||||||
case smileysAndPeople = "Smileys & People"
|
case smileysAndPeople = "Smileys & People"
|
||||||
|
@ -3816,3 +3819,4 @@ extension Emoji {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// swiftlint:disable all
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
||||||
|
|
||||||
|
// swiftlint:disable all
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
extension Emoji {
|
extension Emoji {
|
||||||
var name: String {
|
var name: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -1882,3 +1885,4 @@ extension Emoji {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// swiftlint:disable all
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
||||||
|
|
||||||
|
// swiftlint:disable all
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
extension Emoji {
|
extension Emoji {
|
||||||
enum SkinTone: String, CaseIterable, Equatable {
|
enum SkinTone: String, CaseIterable, Equatable {
|
||||||
case light = "🏻"
|
case light = "🏻"
|
||||||
|
@ -2738,3 +2741,4 @@ extension Emoji {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// swiftlint:disable all
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
||||||
|
|
||||||
// swiftlint:disable all
|
// swiftlint:disable all
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
/// A sorted representation of all available emoji
|
/// A sorted representation of all available emoji
|
||||||
enum Emoji: String, CaseIterable, Equatable {
|
enum Emoji: String, CaseIterable, Equatable {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
// This file is generated by EmojiGenerator.swift, do not manually edit it.
|
||||||
|
|
||||||
|
// swiftlint:disable all
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
extension EmojiWithSkinTones {
|
extension EmojiWithSkinTones {
|
||||||
init?(rawValue: String) {
|
init?(rawValue: String) {
|
||||||
guard rawValue.isSingleEmoji else { return nil }
|
guard rawValue.isSingleEmoji else { return nil }
|
||||||
|
@ -4726,3 +4729,4 @@ extension EmojiWithSkinTones {
|
||||||
return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue))
|
return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// swiftlint:disable all
|
||||||
|
|
|
@ -64,7 +64,7 @@ extension Emoji {
|
||||||
guard withDefaultEmoji else { return recentReactionEmoji }
|
guard withDefaultEmoji else { return recentReactionEmoji }
|
||||||
|
|
||||||
// Add in our default emoji if desired
|
// Add in our default emoji if desired
|
||||||
let defaultEmoji = ["😂", "🥰", "😢", "😡", "😮", "😈"]
|
let defaultEmoji = ["😂", "🥰", "😢", "😡", "😮", "😈"] // stringlint:disable
|
||||||
.filter { !recentReactionEmoji.contains($0) }
|
.filter { !recentReactionEmoji.contains($0) }
|
||||||
|
|
||||||
return Array(recentReactionEmoji
|
return Array(recentReactionEmoji
|
||||||
|
|
|
@ -56,32 +56,26 @@ class GiphyRendition: ProxiedContentAssetDescription {
|
||||||
|
|
||||||
private class func fileExtension(forFormat format: GiphyFormat) -> String {
|
private class func fileExtension(forFormat format: GiphyFormat) -> String {
|
||||||
switch format {
|
switch format {
|
||||||
case .gif:
|
case .gif: return "gif" // stringlint:disable
|
||||||
return "gif"
|
case .mp4: return "mp4" // stringlint:disable
|
||||||
case .mp4:
|
case .jpg: return "jpg" // stringlint:disable
|
||||||
return "mp4"
|
|
||||||
case .jpg:
|
|
||||||
return "jpg"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var utiType: String {
|
public var utiType: String {
|
||||||
switch format {
|
switch format {
|
||||||
case .gif:
|
case .gif: return kUTTypeGIF as String
|
||||||
return kUTTypeGIF as String
|
case .mp4: return kUTTypeMPEG4 as String
|
||||||
case .mp4:
|
case .jpg: return kUTTypeJPEG as String
|
||||||
return kUTTypeMPEG4 as String
|
|
||||||
case .jpg:
|
|
||||||
return kUTTypeJPEG as String
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isStill: Bool {
|
public var isStill: Bool {
|
||||||
return name.hasSuffix("_still")
|
return name.hasSuffix("_still") // stringlint:disable
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isDownsampled: Bool {
|
public var isDownsampled: Bool {
|
||||||
return name.hasSuffix("_downsampled")
|
return name.hasSuffix("_downsampled") // stringlint:disable
|
||||||
}
|
}
|
||||||
|
|
||||||
public func log() {
|
public func log() {
|
||||||
|
@ -279,11 +273,11 @@ enum GiphyAPI {
|
||||||
// MARK: - Search
|
// MARK: - Search
|
||||||
|
|
||||||
// This is the Signal iOS API key.
|
// This is the Signal iOS API key.
|
||||||
private static let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc"
|
private static let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc" // stringlint:disable
|
||||||
private static let kGiphyPageSize = 20
|
private static let kGiphyPageSize = 20
|
||||||
|
|
||||||
public static func trending() -> AnyPublisher<[GiphyImageInfo], Error> {
|
public static func trending() -> AnyPublisher<[GiphyImageInfo], Error> {
|
||||||
let urlString = "/v1/gifs/trending?api_key=\(kGiphyApiKey)&limit=\(kGiphyPageSize)"
|
let urlString = "/v1/gifs/trending?api_key=\(kGiphyApiKey)&limit=\(kGiphyPageSize)" // stringlint:disable
|
||||||
|
|
||||||
guard let url: URL = URL(string: "\(kGiphyBaseURL)\(urlString)") else {
|
guard let url: URL = URL(string: "\(kGiphyBaseURL)\(urlString)") else {
|
||||||
return Fail(error: HTTPError.invalidURL)
|
return Fail(error: HTTPError.invalidURL)
|
||||||
|
@ -319,10 +313,10 @@ enum GiphyAPI {
|
||||||
let url: URL = URL(
|
let url: URL = URL(
|
||||||
string: [
|
string: [
|
||||||
kGiphyBaseURL,
|
kGiphyBaseURL,
|
||||||
"/v1/gifs/search?api_key=\(kGiphyApiKey)",
|
"/v1/gifs/search?api_key=\(kGiphyApiKey)", // stringlint:disable
|
||||||
"&offset=\(kGiphyPageOffset)",
|
"&offset=\(kGiphyPageOffset)", // stringlint:disable
|
||||||
"&limit=\(kGiphyPageSize)",
|
"&limit=\(kGiphyPageSize)", // stringlint:disable
|
||||||
"&q=\(queryEncoded)"
|
"&q=\(queryEncoded)" // stringlint:disable
|
||||||
].joined()
|
].joined()
|
||||||
)
|
)
|
||||||
else {
|
else {
|
||||||
|
@ -370,7 +364,7 @@ enum GiphyAPI {
|
||||||
Logger.error("Invalid response.")
|
Logger.error("Invalid response.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let imageDicts = responseDict["data"] as? [[String: Any]] else {
|
guard let imageDicts = responseDict["data"] as? [[String: Any]] else { // stringlint:disable
|
||||||
Logger.error("Invalid response data.")
|
Logger.error("Invalid response data.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -381,7 +375,7 @@ enum GiphyAPI {
|
||||||
|
|
||||||
// Giphy API results are often incomplete or malformed, so we need to be defensive.
|
// Giphy API results are often incomplete or malformed, so we need to be defensive.
|
||||||
private static func parseGiphyImage(imageDict: [String: Any]) -> GiphyImageInfo? {
|
private static func parseGiphyImage(imageDict: [String: Any]) -> GiphyImageInfo? {
|
||||||
guard let giphyId = imageDict["id"] as? String else {
|
guard let giphyId = imageDict["id"] as? String else { // stringlint:disable
|
||||||
Logger.warn("Image dict missing id.")
|
Logger.warn("Image dict missing id.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -389,7 +383,7 @@ enum GiphyAPI {
|
||||||
Logger.warn("Image dict has invalid id.")
|
Logger.warn("Image dict has invalid id.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let renditionDicts = imageDict["images"] as? [String: Any] else {
|
guard let renditionDicts = imageDict["images"] as? [String: Any] else { // stringlint:disable
|
||||||
Logger.warn("Image dict missing renditions.")
|
Logger.warn("Image dict missing renditions.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -423,7 +417,7 @@ enum GiphyAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func findOriginalRendition(renditions: [GiphyRendition]) -> GiphyRendition? {
|
private static func findOriginalRendition(renditions: [GiphyRendition]) -> GiphyRendition? {
|
||||||
for rendition in renditions where rendition.name == "original" {
|
for rendition in renditions where rendition.name == "original" { // stringlint:disable
|
||||||
return rendition
|
return rendition
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -436,15 +430,15 @@ enum GiphyAPI {
|
||||||
renditionName: String,
|
renditionName: String,
|
||||||
renditionDict: [String: Any]
|
renditionDict: [String: Any]
|
||||||
) -> GiphyRendition? {
|
) -> GiphyRendition? {
|
||||||
guard let width = parsePositiveUInt(dict: renditionDict, key: "width", typeName: "rendition") else {
|
guard let width = parsePositiveUInt(dict: renditionDict, key: "width", typeName: "rendition") else { // stringlint:disable
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let height = parsePositiveUInt(dict: renditionDict, key: "height", typeName: "rendition") else {
|
guard let height = parsePositiveUInt(dict: renditionDict, key: "height", typeName: "rendition") else { // stringlint:disable
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Be lenient when parsing file sizes - we don't require them for stills.
|
// Be lenient when parsing file sizes - we don't require them for stills.
|
||||||
let fileSize = parseLenientUInt(dict: renditionDict, key: "size")
|
let fileSize = parseLenientUInt(dict: renditionDict, key: "size") // stringlint:disable
|
||||||
guard let urlString = renditionDict["url"] as? String else {
|
guard let urlString = renditionDict["url"] as? String else { // stringlint:disable
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard urlString.count > 0 else {
|
guard urlString.count > 0 else {
|
||||||
|
@ -460,13 +454,13 @@ enum GiphyAPI {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var format = GiphyFormat.gif
|
var format = GiphyFormat.gif
|
||||||
if fileExtension == "gif" {
|
if fileExtension == "gif" { // stringlint:disable
|
||||||
format = .gif
|
format = .gif
|
||||||
} else if fileExtension == "jpg" {
|
} else if fileExtension == "jpg" { // stringlint:disable
|
||||||
format = .jpg
|
format = .jpg
|
||||||
} else if fileExtension == "mp4" {
|
} else if fileExtension == "mp4" { // stringlint:disable
|
||||||
format = .mp4
|
format = .mp4
|
||||||
} else if fileExtension == "webp" {
|
} else if fileExtension == "webp" { // stringlint:disable
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
Logger.warn("Invalid file extension: \(fileExtension).")
|
Logger.warn("Invalid file extension: \(fileExtension).")
|
||||||
|
|
|
@ -145,7 +145,7 @@ public class MediaGalleryViewModel {
|
||||||
public struct GalleryDate: Differentiable, Equatable, Comparable, Hashable {
|
public struct GalleryDate: Differentiable, Equatable, Comparable, Hashable {
|
||||||
private static let thisYearFormatter: DateFormatter = {
|
private static let thisYearFormatter: DateFormatter = {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "MMMM"
|
formatter.dateFormat = "MMMM" // stringlint:disable
|
||||||
|
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
@ -153,7 +153,7 @@ public class MediaGalleryViewModel {
|
||||||
private static let olderFormatter: DateFormatter = {
|
private static let olderFormatter: DateFormatter = {
|
||||||
// FIXME: localize for RTL, or is there a built in way to do this?
|
// FIXME: localize for RTL, or is there a built in way to do this?
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "MMMM yyyy"
|
formatter.dateFormat = "MMMM yyyy" // stringlint:disable
|
||||||
|
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -93,8 +93,7 @@
|
||||||
/* Label indicating loading is in progress */
|
/* Label indicating loading is in progress */
|
||||||
"GALLERY_TILES_LOADING_OLDER_LABEL" = "Зареждане на по-стари файлове…";
|
"GALLERY_TILES_LOADING_OLDER_LABEL" = "Зареждане на по-стари файлове…";
|
||||||
/* Error displayed when there is a failure fetching a GIF from the remote service. */
|
/* Error displayed when there is a failure fetching a GIF from the remote service. */
|
||||||
"GIF_PICKER_ERROR_FETCH_FAILURE" = "Извличането на посочения GIF е неуспешно.
|
"GIF_PICKER_ERROR_FETCH_FAILURE" = "Извличането на посочения GIF е неуспешно. Моля, проверете интернет връзката си.";
|
||||||
Моля, проверете интернет връзката си.";
|
|
||||||
/* Generic error displayed when picking a GIF */
|
/* Generic error displayed when picking a GIF */
|
||||||
"GIF_PICKER_ERROR_GENERIC" = "Възникна неизвестна грешка.";
|
"GIF_PICKER_ERROR_GENERIC" = "Възникна неизвестна грешка.";
|
||||||
/* Shown when selected GIF couldn't be fetched */
|
/* Shown when selected GIF couldn't be fetched */
|
||||||
|
|
|
@ -486,8 +486,7 @@
|
||||||
"PRIVACY_CALLS_TITLE" = "Llamadas de voz y video";
|
"PRIVACY_CALLS_TITLE" = "Llamadas de voz y video";
|
||||||
"PRIVACY_CALLS_DESCRIPTION" = "Habilita llamadas de voz y video hacia y desde otros usuarios.";
|
"PRIVACY_CALLS_DESCRIPTION" = "Habilita llamadas de voz y video hacia y desde otros usuarios.";
|
||||||
"PRIVACY_CALLS_WARNING_TITLE" = "Llamadas de Voz y Video (Beta)";
|
"PRIVACY_CALLS_WARNING_TITLE" = "Llamadas de Voz y Video (Beta)";
|
||||||
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Su dirección IP es visible para su socio de llamadas y un servidor de Oxen Fundación
|
"PRIVACY_CALLS_WARNING_DESCRIPTION" = "Su dirección IP es visible para su socio de llamadas y un servidor de Oxen Fundación mientras usas llamadas beta. ¿Está seguro de que desea habilitar las llamadas de voz y videollamadas?";
|
||||||
mientras usas llamadas beta. ¿Está seguro de que desea habilitar las llamadas de voz y videollamadas?";
|
|
||||||
"NOTIFICATIONS_TITLE" = "Notificaciones";
|
"NOTIFICATIONS_TITLE" = "Notificaciones";
|
||||||
"NOTIFICATIONS_SECTION_STRATEGY" = "Estrategia de notificación";
|
"NOTIFICATIONS_SECTION_STRATEGY" = "Estrategia de notificación";
|
||||||
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Usar Modo Rápido";
|
"NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE" = "Usar Modo Rápido";
|
||||||
|
|
|
@ -24,68 +24,6 @@ import SessionSnodeKit
|
||||||
/// there is no need for an Adapter, and instead the appropriate NotificationActionHandler is
|
/// there is no need for an Adapter, and instead the appropriate NotificationActionHandler is
|
||||||
/// wired directly into the appropriate callback point.
|
/// wired directly into the appropriate callback point.
|
||||||
|
|
||||||
enum AppNotificationCategory: CaseIterable {
|
|
||||||
case incomingMessage
|
|
||||||
case incomingMessageFromNoLongerVerifiedIdentity
|
|
||||||
case errorMessage
|
|
||||||
case threadlessErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AppNotificationAction: CaseIterable {
|
|
||||||
case markAsRead
|
|
||||||
case reply
|
|
||||||
case showThread
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppNotificationUserInfoKey {
|
|
||||||
static let threadId = "Signal.AppNotificationsUserInfoKey.threadId"
|
|
||||||
static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw"
|
|
||||||
static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber"
|
|
||||||
static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId"
|
|
||||||
static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter"
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppNotificationCategory {
|
|
||||||
var identifier: String {
|
|
||||||
switch self {
|
|
||||||
case .incomingMessage:
|
|
||||||
return "Signal.AppNotificationCategory.incomingMessage"
|
|
||||||
case .incomingMessageFromNoLongerVerifiedIdentity:
|
|
||||||
return "Signal.AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity"
|
|
||||||
case .errorMessage:
|
|
||||||
return "Signal.AppNotificationCategory.errorMessage"
|
|
||||||
case .threadlessErrorMessage:
|
|
||||||
return "Signal.AppNotificationCategory.threadlessErrorMessage"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var actions: [AppNotificationAction] {
|
|
||||||
switch self {
|
|
||||||
case .incomingMessage:
|
|
||||||
return [.markAsRead, .reply]
|
|
||||||
case .incomingMessageFromNoLongerVerifiedIdentity:
|
|
||||||
return [.markAsRead, .showThread]
|
|
||||||
case .errorMessage:
|
|
||||||
return [.showThread]
|
|
||||||
case .threadlessErrorMessage:
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppNotificationAction {
|
|
||||||
var identifier: String {
|
|
||||||
switch self {
|
|
||||||
case .markAsRead:
|
|
||||||
return "Signal.AppNotifications.Action.markAsRead"
|
|
||||||
case .reply:
|
|
||||||
return "Signal.AppNotifications.Action.reply"
|
|
||||||
case .showThread:
|
|
||||||
return "Signal.AppNotifications.Action.showThread"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let kAudioNotificationsThrottleCount = 2
|
let kAudioNotificationsThrottleCount = 2
|
||||||
let kAudioNotificationsThrottleInterval: TimeInterval = 5
|
let kAudioNotificationsThrottleInterval: TimeInterval = 5
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,6 @@ private func redact(_ string: String) -> String {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
return string
|
return string
|
||||||
#else
|
#else
|
||||||
return "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]"
|
return "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" // stringlint:disable
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum AppNotificationAction: CaseIterable {
|
||||||
|
case markAsRead
|
||||||
|
case reply
|
||||||
|
case showThread
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppNotificationAction {
|
||||||
|
var identifier: String {
|
||||||
|
switch self {
|
||||||
|
case .markAsRead: return "Signal.AppNotifications.Action.markAsRead"
|
||||||
|
case .reply: return "Signal.AppNotifications.Action.reply"
|
||||||
|
case .showThread: return "Signal.AppNotifications.Action.showThread"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum AppNotificationCategory: CaseIterable {
|
||||||
|
case incomingMessage
|
||||||
|
case incomingMessageFromNoLongerVerifiedIdentity
|
||||||
|
case errorMessage
|
||||||
|
case threadlessErrorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppNotificationCategory {
|
||||||
|
var identifier: String {
|
||||||
|
switch self {
|
||||||
|
case .incomingMessage: return "Signal.AppNotificationCategory.incomingMessage"
|
||||||
|
case .incomingMessageFromNoLongerVerifiedIdentity:
|
||||||
|
return "Signal.AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity"
|
||||||
|
|
||||||
|
case .errorMessage: return "Signal.AppNotificationCategory.errorMessage"
|
||||||
|
case .threadlessErrorMessage: return "Signal.AppNotificationCategory.threadlessErrorMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions: [AppNotificationAction] {
|
||||||
|
switch self {
|
||||||
|
case .incomingMessage: return [.markAsRead, .reply]
|
||||||
|
case .incomingMessageFromNoLongerVerifiedIdentity: return [.markAsRead, .showThread]
|
||||||
|
case .errorMessage: return [.showThread]
|
||||||
|
case .threadlessErrorMessage: return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct AppNotificationUserInfoKey {
|
||||||
|
static let threadId = "Signal.AppNotificationsUserInfoKey.threadId"
|
||||||
|
static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw"
|
||||||
|
static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber"
|
||||||
|
static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId"
|
||||||
|
static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter"
|
||||||
|
}
|
|
@ -191,7 +191,7 @@ final class RegisterVC : BaseVC {
|
||||||
for index in indexesToShuffle {
|
for index in indexesToShuffle {
|
||||||
let startIndex = mangledHexEncodedPublicKey.index(mangledHexEncodedPublicKey.startIndex, offsetBy: index)
|
let startIndex = mangledHexEncodedPublicKey.index(mangledHexEncodedPublicKey.startIndex, offsetBy: index)
|
||||||
let endIndex = mangledHexEncodedPublicKey.index(after: startIndex)
|
let endIndex = mangledHexEncodedPublicKey.index(after: startIndex)
|
||||||
mangledHexEncodedPublicKey.replaceSubrange(startIndex..<endIndex, with: "0123456789abcdef__".shuffled()[0..<1])
|
mangledHexEncodedPublicKey.replaceSubrange(startIndex..<endIndex, with: "0123456789abcdef__".shuffled()[0..<1]) // stringlint:disable
|
||||||
}
|
}
|
||||||
count += 1
|
count += 1
|
||||||
if count < limit {
|
if count < limit {
|
||||||
|
|
|
@ -18,12 +18,12 @@ final class SeedVC: BaseVC {
|
||||||
let hasStoredPublicKey: Bool = (Identity.fetchUserPublicKey() != nil)
|
let hasStoredPublicKey: Bool = (Identity.fetchUserPublicKey() != nil)
|
||||||
let hasStoredEdKeyPair: Bool = (Identity.fetchUserEd25519KeyPair() != nil)
|
let hasStoredEdKeyPair: Bool = (Identity.fetchUserEd25519KeyPair() != nil)
|
||||||
let dbStates: [String] = [
|
let dbStates: [String] = [
|
||||||
"dbIsValid: \(dbIsValid)",
|
"dbIsValid: \(dbIsValid)", // stringlint:disable
|
||||||
"dbIsSuspendedUnsafe: \(dbIsSuspendedUnsafe)",
|
"dbIsSuspendedUnsafe: \(dbIsSuspendedUnsafe)", // stringlint:disable
|
||||||
"storedSeed: false",
|
"storedSeed: false", // stringlint:disable
|
||||||
"userPublicKey: \(hasStoredPublicKey)",
|
"userPublicKey: \(hasStoredPublicKey)", // stringlint:disable
|
||||||
"userPrivateKey: false",
|
"userPrivateKey: false", // stringlint:disable
|
||||||
"userEdKeyPair: \(hasStoredEdKeyPair)"
|
"userEdKeyPair: \(hasStoredEdKeyPair)" // stringlint:disable
|
||||||
]
|
]
|
||||||
|
|
||||||
SNLog("Failed to retrieve keys for mnemonic generation (\(dbStates.joined(separator: ", ")))")
|
SNLog("Failed to retrieve keys for mnemonic generation (\(dbStates.joined(separator: ", ")))")
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Sodium
|
import Sodium
|
||||||
|
|
|
@ -6,7 +6,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
enum _001_InitialSetupMigration: Migration {
|
enum _001_InitialSetupMigration: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "initialSetup"
|
static let identifier: String = "initialSetup" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ enum _001_InitialSetupMigration: Migration {
|
||||||
t.column(.variant, .integer).notNull()
|
t.column(.variant, .integer).notNull()
|
||||||
t.column(.creationDateTimestamp, .double).notNull()
|
t.column(.creationDateTimestamp, .double).notNull()
|
||||||
t.column(.shouldBeVisible, .boolean).notNull()
|
t.column(.shouldBeVisible, .boolean).notNull()
|
||||||
t.deprecatedColumn(name: "isPinned", .boolean).notNull()
|
t.deprecatedColumn(name: "isPinned", .boolean).notNull() // stringlint:disable
|
||||||
t.column(.messageDraft, .text)
|
t.column(.messageDraft, .text)
|
||||||
t.column(.notificationSound, .integer)
|
t.column(.notificationSound, .integer)
|
||||||
t.column(.mutedUntilTimestamp, .double)
|
t.column(.mutedUntilTimestamp, .double)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import SessionSnodeKit
|
||||||
/// before running the `YDBToGRDBMigration`
|
/// before running the `YDBToGRDBMigration`
|
||||||
enum _002_SetupStandardJobs: Migration {
|
enum _002_SetupStandardJobs: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "SetupStandardJobs"
|
static let identifier: String = "SetupStandardJobs" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AVKit
|
import AVKit
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionSnodeKit
|
||||||
/// This migration removes the legacy YapDatabase files
|
/// This migration removes the legacy YapDatabase files
|
||||||
enum _004_RemoveLegacyYDB: Migration {
|
enum _004_RemoveLegacyYDB: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "RemoveLegacyYDB"
|
static let identifier: String = "RemoveLegacyYDB" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SessionUtilitiesKit
|
||||||
/// This migration fixes a bug where certain message variants could incorrectly be counted as unread messages
|
/// This migration fixes a bug where certain message variants could incorrectly be counted as unread messages
|
||||||
enum _005_FixDeletedMessageReadState: Migration {
|
enum _005_FixDeletedMessageReadState: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "FixDeletedMessageReadState"
|
static let identifier: String = "FixDeletedMessageReadState" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// for open groups so they will fully re-fetch their mod/admin lists
|
/// for open groups so they will fully re-fetch their mod/admin lists
|
||||||
enum _006_FixHiddenModAdminSupport: Migration {
|
enum _006_FixHiddenModAdminSupport: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "FixHiddenModAdminSupport"
|
static let identifier: String = "FixHiddenModAdminSupport" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,13 @@ import SessionUtilitiesKit
|
||||||
/// This migration adds an index to the interaction table in order to improve the performance of retrieving the number of unread interactions
|
/// This migration adds an index to the interaction table in order to improve the performance of retrieving the number of unread interactions
|
||||||
enum _007_HomeQueryOptimisationIndexes: Migration {
|
enum _007_HomeQueryOptimisationIndexes: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "HomeQueryOptimisationIndexes"
|
static let identifier: String = "HomeQueryOptimisationIndexes" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
static func migrate(_ db: Database) throws {
|
static func migrate(_ db: Database) throws {
|
||||||
try db.create(
|
try db.create(
|
||||||
index: "interaction_on_wasRead_and_hasMention_and_threadId",
|
index: "interaction_on_wasRead_and_hasMention_and_threadId", // stringlint:disable
|
||||||
on: Interaction.databaseTableName,
|
on: Interaction.databaseTableName,
|
||||||
columns: [
|
columns: [
|
||||||
Interaction.Columns.wasRead.name,
|
Interaction.Columns.wasRead.name,
|
||||||
|
@ -23,7 +23,7 @@ enum _007_HomeQueryOptimisationIndexes: Migration {
|
||||||
)
|
)
|
||||||
|
|
||||||
try db.create(
|
try db.create(
|
||||||
index: "interaction_on_threadId_and_timestampMs_and_variant",
|
index: "interaction_on_threadId_and_timestampMs_and_variant", // stringlint:disable
|
||||||
on: Interaction.databaseTableName,
|
on: Interaction.databaseTableName,
|
||||||
columns: [
|
columns: [
|
||||||
Interaction.Columns.threadId.name,
|
Interaction.Columns.threadId.name,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SessionUtilitiesKit
|
||||||
/// This migration adds the new types needed for Emoji Reacts
|
/// This migration adds the new types needed for Emoji Reacts
|
||||||
enum _008_EmojiReacts: Migration {
|
enum _008_EmojiReacts: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "EmojiReacts"
|
static let identifier: String = "EmojiReacts" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
enum _009_OpenGroupPermission: Migration {
|
enum _009_OpenGroupPermission: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "OpenGroupPermission"
|
static let identifier: String = "OpenGroupPermission" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// searh (currently it's much slower than the global search)
|
/// searh (currently it's much slower than the global search)
|
||||||
enum _010_AddThreadIdToFTS: Migration {
|
enum _010_AddThreadIdToFTS: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "AddThreadIdToFTS"
|
static let identifier: String = "AddThreadIdToFTS" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 3
|
static let minExpectedRunDuration: TimeInterval = 3
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// message due to how one-to-one conversations work, by storing pending read receipts we should be able to prevent this case)
|
/// message due to how one-to-one conversations work, by storing pending read receipts we should be able to prevent this case)
|
||||||
enum _011_AddPendingReadReceipts: Migration {
|
enum _011_AddPendingReadReceipts: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "AddPendingReadReceipts"
|
static let identifier: String = "AddPendingReadReceipts" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SessionUtilitiesKit
|
||||||
/// This migration adds the FTS table back for internal test users whose FTS table was removed unintentionally
|
/// This migration adds the FTS table back for internal test users whose FTS table was removed unintentionally
|
||||||
enum _012_AddFTSIfNeeded: Migration {
|
enum _012_AddFTSIfNeeded: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "AddFTSIfNeeded"
|
static let identifier: String = "AddFTSIfNeeded" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// This migration goes through the current state of the database and generates config dumps for the user config types
|
/// This migration goes through the current state of the database and generates config dumps for the user config types
|
||||||
enum _014_GenerateInitialUserConfigDumps: Migration {
|
enum _014_GenerateInitialUserConfigDumps: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "GenerateInitialUserConfigDumps"
|
static let identifier: String = "GenerateInitialUserConfigDumps" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = true
|
static let needsConfigSync: Bool = true
|
||||||
static let minExpectedRunDuration: TimeInterval = 4.0
|
static let minExpectedRunDuration: TimeInterval = 4.0
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SessionUtilitiesKit
|
||||||
/// This migration adds a flag indicating whether a profile has indicated it is blocking community message requests
|
/// This migration adds a flag indicating whether a profile has indicated it is blocking community message requests
|
||||||
enum _015_BlockCommunityMessageRequests: Migration {
|
enum _015_BlockCommunityMessageRequests: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "BlockCommunityMessageRequests"
|
static let identifier: String = "BlockCommunityMessageRequests" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||||
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// results in migration issues when a user jumps between multiple versions)
|
/// results in migration issues when a user jumps between multiple versions)
|
||||||
enum _016_MakeBrokenProfileTimestampsNullable: Migration {
|
enum _016_MakeBrokenProfileTimestampsNullable: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .messagingKit
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
static let identifier: String = "MakeBrokenProfileTimestampsNullable"
|
static let identifier: String = "MakeBrokenProfileTimestampsNullable" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
static var requirements: [MigrationRequirement] = [.sessionUtilStateLoaded]
|
||||||
|
@ -17,7 +17,7 @@ enum _016_MakeBrokenProfileTimestampsNullable: Migration {
|
||||||
/// SQLite doesn't support altering columns after creation so we need to create a new table with the setup we
|
/// SQLite doesn't support altering columns after creation so we need to create a new table with the setup we
|
||||||
/// want, copy data from the old table over, drop the old table and rename the new table
|
/// want, copy data from the old table over, drop the old table and rename the new table
|
||||||
struct TmpProfile: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible {
|
struct TmpProfile: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible {
|
||||||
static var databaseTableName: String { "tmpProfile" }
|
static var databaseTableName: String { "tmpProfile" } // stringlint:disable
|
||||||
|
|
||||||
public typealias Columns = CodingKeys
|
public typealias Columns = CodingKeys
|
||||||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||||
|
|
|
@ -284,19 +284,19 @@ extension Attachment: CustomStringConvertible {
|
||||||
|
|
||||||
public static func emoji(for contentType: String) -> String {
|
public static func emoji(for contentType: String) -> String {
|
||||||
if MIMETypeUtil.isImage(contentType) {
|
if MIMETypeUtil.isImage(contentType) {
|
||||||
return "📷"
|
return "📷" // stringlint:disable
|
||||||
}
|
}
|
||||||
else if MIMETypeUtil.isVideo(contentType) {
|
else if MIMETypeUtil.isVideo(contentType) {
|
||||||
return "🎥"
|
return "🎥" // stringlint:disable
|
||||||
}
|
}
|
||||||
else if MIMETypeUtil.isAudio(contentType) {
|
else if MIMETypeUtil.isAudio(contentType) {
|
||||||
return "🎧"
|
return "🎧" // stringlint:disable
|
||||||
}
|
}
|
||||||
else if MIMETypeUtil.isAnimated(contentType) {
|
else if MIMETypeUtil.isAnimated(contentType) {
|
||||||
return "🎡"
|
return "🎡" // stringlint:disable
|
||||||
}
|
}
|
||||||
|
|
||||||
return "📎"
|
return "📎" // stringlint:disable
|
||||||
}
|
}
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct HTMLMetadata: Equatable {
|
public struct HTMLMetadata: Equatable {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Sodium
|
import Sodium
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
enum _001_InitialSetupMigration: Migration {
|
enum _001_InitialSetupMigration: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .snodeKit
|
static let target: TargetMigrations.Identifier = .snodeKit
|
||||||
static let identifier: String = "initialSetup"
|
static let identifier: String = "initialSetup" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// before running the `YDBToGRDBMigration`
|
/// before running the `YDBToGRDBMigration`
|
||||||
enum _002_SetupStandardJobs: Migration {
|
enum _002_SetupStandardJobs: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .snodeKit
|
static let target: TargetMigrations.Identifier = .snodeKit
|
||||||
static let identifier: String = "SetupStandardJobs"
|
static let identifier: String = "SetupStandardJobs" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
enum _004_FlagMessageHashAsDeletedOrInvalid: Migration {
|
enum _004_FlagMessageHashAsDeletedOrInvalid: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .snodeKit
|
static let target: TargetMigrations.Identifier = .snodeKit
|
||||||
static let identifier: String = "FlagMessageHashAsDeletedOrInvalid"
|
static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
|
|
||||||
/// This migration adds a flat to the `SnodeReceivedMessageInfo` so that when deleting interactions we can
|
/// This migration adds a flat to the `SnodeReceivedMessageInfo` so that when deleting interactions we can
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public extension Notification.Name {
|
public extension Notification.Name {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SessionUtilitiesKit
|
||||||
/// theme preferences
|
/// theme preferences
|
||||||
enum _001_ThemePreferences: Migration {
|
enum _001_ThemePreferences: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .uiKit
|
static let target: TargetMigrations.Identifier = .uiKit
|
||||||
static let identifier: String = "ThemePreferences"
|
static let identifier: String = "ThemePreferences" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
@ -18,11 +18,11 @@ enum _001_ThemePreferences: Migration {
|
||||||
let isExistingUser: Bool = Identity.userExists(db)
|
let isExistingUser: Bool = Identity.userExists(db)
|
||||||
let hadCustomLegacyThemeSetting: Bool = UserDefaults.standard.dictionaryRepresentation()
|
let hadCustomLegacyThemeSetting: Bool = UserDefaults.standard.dictionaryRepresentation()
|
||||||
.keys
|
.keys
|
||||||
.contains("appMode")
|
.contains("appMode") // stringlint:disable
|
||||||
let matchSystemNightModeSetting: Bool = (isExistingUser && !hadCustomLegacyThemeSetting)
|
let matchSystemNightModeSetting: Bool = (isExistingUser && !hadCustomLegacyThemeSetting)
|
||||||
let targetTheme: Theme = (!hadCustomLegacyThemeSetting ?
|
let targetTheme: Theme = (!hadCustomLegacyThemeSetting ?
|
||||||
Theme.classicDark :
|
Theme.classicDark :
|
||||||
(UserDefaults.standard.integer(forKey: "appMode") == 0 ?
|
(UserDefaults.standard.integer(forKey: "appMode") == 0 ? // stringlint:disable
|
||||||
Theme.classicLight :
|
Theme.classicLight :
|
||||||
Theme.classicDark
|
Theme.classicDark
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Foundation
|
||||||
|
|
||||||
public enum Hex {
|
public enum Hex {
|
||||||
public static func isValid(_ string: String) -> Bool {
|
public static func isValid(_ string: String) -> Bool {
|
||||||
let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF")
|
let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") // stringlint:disable
|
||||||
|
|
||||||
return string.uppercased().unicodeScalars.allSatisfy { allowedCharacters.contains($0) }
|
return string.uppercased().unicodeScalars.allSatisfy { allowedCharacters.contains($0) }
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ public extension Array where Element == UInt8 {
|
||||||
self.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount)
|
self.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount)
|
||||||
|
|
||||||
var buffer: UInt8?
|
var buffer: UInt8?
|
||||||
var skip = (hex.hasPrefix("0x") ? 2 : 0)
|
var skip = (hex.hasPrefix("0x") ? 2 : 0) // stringlint:disable
|
||||||
|
|
||||||
for char in hex.unicodeScalars.lazy {
|
for char in hex.unicodeScalars.lazy {
|
||||||
guard skip == 0 else {
|
guard skip == 0 else {
|
||||||
|
@ -73,7 +73,7 @@ public extension Array where Element == UInt8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toHexString() -> String {
|
func toHexString() -> String {
|
||||||
return map { String(format: "%02x", $0) }.joined()
|
return map { String(format: "%02x", $0) }.joined() // stringlint:disable
|
||||||
}
|
}
|
||||||
|
|
||||||
func toBase64(options: Data.Base64EncodingOptions = []) -> String {
|
func toBase64(options: Data.Base64EncodingOptions = []) -> String {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import YapDatabase
|
import YapDatabase
|
||||||
|
|
|
@ -5,7 +5,7 @@ import GRDB
|
||||||
|
|
||||||
enum _001_InitialSetupMigration: Migration {
|
enum _001_InitialSetupMigration: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .utilitiesKit
|
static let target: TargetMigrations.Identifier = .utilitiesKit
|
||||||
static let identifier: String = "initialSetup"
|
static let identifier: String = "initialSetup" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import GRDB
|
||||||
/// before running the `YDBToGRDBMigration`
|
/// before running the `YDBToGRDBMigration`
|
||||||
enum _002_SetupStandardJobs: Migration {
|
enum _002_SetupStandardJobs: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .utilitiesKit
|
static let target: TargetMigrations.Identifier = .utilitiesKit
|
||||||
static let identifier: String = "SetupStandardJobs"
|
static let identifier: String = "SetupStandardJobs" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import YapDatabase
|
||||||
|
|
||||||
enum _003_YDBToGRDBMigration: Migration {
|
enum _003_YDBToGRDBMigration: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .utilitiesKit
|
static let target: TargetMigrations.Identifier = .utilitiesKit
|
||||||
static let identifier: String = "YDBToGRDBMigration"
|
static let identifier: String = "YDBToGRDBMigration" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
// Map the Legacy types for the NSKeyedUnarchiver
|
// Map the Legacy types for the NSKeyedUnarchiver
|
||||||
NSKeyedUnarchiver.setClass(
|
NSKeyedUnarchiver.setClass(
|
||||||
SUKLegacy.KeyPair.self,
|
SUKLegacy.KeyPair.self,
|
||||||
forClassName: "ECKeyPair"
|
forClassName: "ECKeyPair" // stringlint:disable
|
||||||
)
|
)
|
||||||
|
|
||||||
dbConnection.read { transaction in
|
dbConnection.read { transaction in
|
||||||
|
|
|
@ -6,7 +6,7 @@ import YapDatabase
|
||||||
|
|
||||||
enum _004_AddJobPriority: Migration {
|
enum _004_AddJobPriority: Migration {
|
||||||
static let target: TargetMigrations.Identifier = .utilitiesKit
|
static let target: TargetMigrations.Identifier = .utilitiesKit
|
||||||
static let identifier: String = "AddJobPriority"
|
static let identifier: String = "AddJobPriority" // stringlint:disable
|
||||||
static let needsConfigSync: Bool = false
|
static let needsConfigSync: Bool = false
|
||||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
#import "OWSFileSystem.h"
|
#import "OWSFileSystem.h"
|
||||||
#import "AppContext.h"
|
#import "AppContext.h"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//
|
//
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SAMKeychain
|
import SAMKeychain
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
#import "AppContext.h"
|
#import "AppContext.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SignalCoreKit
|
import SignalCoreKit
|
||||||
|
|
|
@ -62,7 +62,7 @@ public enum SNUserDefaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension UserDefaults {
|
public extension UserDefaults {
|
||||||
public static let applicationGroup: String = "group.com.loki-project.loki-messenger"
|
static let applicationGroup: String = "group.com.loki-project.loki-messenger"
|
||||||
|
|
||||||
@objc static var sharedLokiProject: UserDefaults? {
|
@objc static var sharedLokiProject: UserDefaults? {
|
||||||
UserDefaults(suiteName: UserDefaults.applicationGroup)
|
UserDefaults(suiteName: UserDefaults.applicationGroup)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
#import "MIMETypeUtil.h"
|
#import "MIMETypeUtil.h"
|
||||||
#import "OWSFileSystem.h"
|
#import "OWSFileSystem.h"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//
|
//
|
||||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||||
//
|
//
|
||||||
|
// stringlint:disable
|
||||||
|
|
||||||
#import "AppVersion.h"
|
#import "AppVersion.h"
|
||||||
#import "NSUserDefaults+OWS.h"
|
#import "NSUserDefaults+OWS.h"
|
||||||
|
|
Loading…
Reference in New Issue